Here's one. Features:
* Walks you the owner through configuration.
* Retains relative offsets (position and rotation) after linking/unlinking.
* Owner double-clicks to reset (requires re-configuring).
* Name-based access list notecard (re-reads automatically on change/replace).
* Static in-script list of names always allowed despite notecard existence and contents.
EDIT: Oh. Because this is a basic script with a dirt-simple user interface, it does NOT do the following:
* Open for avatars not in the current sim.
* Have an all-open unlocked state where anyone can open/close it.
* Modify the door prim in any way other than position and rotation (no hollowing, scaling, etc.).
It might be a good practice in scripting to change some of those.
//// Constants
// A double-click resets the object and forces it to restart configuration
float DOUBLE_CLICK_TIMEOUT = 0.3;
// Set to "" if an access notecard will not be used
string ACCESS_NOTECARD = "AccessList";
// Set to 0.0 if the door shouldn't close on its own
float CLOSE_DELAY = 3.0;
// Add names here and/or to the notecard identified by ACCESS_NOTECARD
list AUTH_USERS = [];
//// Variables
vector closedPos = ZERO_VECTOR;
rotation closedRot = ZERO_ROTATION;
vector openPosOffset = ZERO_VECTOR;
rotation openRotOffset = ZERO_ROTATION;
list authorizedUserNames = AUTH_USERS;
integer nextNoteLine = -1;
key noteLineRequest = NULL_KEY;
integer openConfigured = FALSE;
integer closeConfigured = FALSE;
integer waitingForDoubleClick = FALSE;
integer currentlyOpen = FALSE;
//// Functions
printHelp()
{
if (!closeConfigured)
{
llOwnerSay("Please place door in closed position, then touch.");
} else if (!openConfigured)
{
llOwnerSay("Please place door in open position, then touch.");
} else
{
llOwnerSay("Reading authorization list....");
}
}
initializeNotecardRead()
{
if (ACCESS_NOTECARD != "" &&
llGetInventoryType(ACCESS_NOTECARD) == INVENTORY_NOTECARD)
{
nextNoteLine = 0;
noteLineRequest = llGetNotecardLine(ACCESS_NOTECARD, nextNoteLine);
}
}
touchConfig()
{
if (!closeConfigured)
{
closedPos = llGetLocalPos();
closedRot = llGetLocalRot();
closeConfigured = TRUE;
openConfigured = FALSE;
currentlyOpen = FALSE;
llOwnerSay("Closed position configured.");
} else if (!openConfigured)
{
openPosOffset = (llGetLocalPos()-closedPos)/closedRot;
openRotOffset = llGetLocalRot()/closedRot;
openConfigured = TRUE;
currentlyOpen = TRUE;
llOwnerSay("Open position configured.");
}
}
integer configComplete()
{
return openConfigured && closeConfigured && (nextNoteLine < 0);
}
integer authorized(key user)
{
if (user == llGetOwner())
{
return TRUE;
}
string userName = llKey2Name(user);
return llListFindList(authorizedUserNames, [ userName ]) >= 0;
}
open()
{
if (currentlyOpen)
{
return;
}
rotation currRot = llGetLocalRot();
vector pos = llGetLocalPos()+openPosOffset*currRot;
rotation rot = openRotOffset*currRot;
if (llGetLinkNumber() > 1)
{
rot /= llGetRootRotation();
}
llSetPrimitiveParams([ PRIM_POSITION, pos, PRIM_ROTATION, rot ]);
currentlyOpen = TRUE;
llSetTimerEvent(CLOSE_DELAY);
}
close()
{
if (!currentlyOpen)
{
return;
}
llSetTimerEvent(0.0);
rotation rot = (ZERO_ROTATION/openRotOffset)*llGetLocalRot();
vector pos = llGetLocalPos()-openPosOffset*rot;
if (llGetLinkNumber() > 1)
{
rot /= llGetRootRotation();
}
llSetPrimitiveParams([ PRIM_POSITION, pos, PRIM_ROTATION, rot ]);
currentlyOpen = FALSE;
}
toggleOpenState()
{
if (currentlyOpen)
{
close();
} else
{
open();
}
}
//// States
default
{
state_entry()
{
initializeNotecardRead();
if (configComplete())
{
state operating;
} else
{
printHelp();
}
}
on_rez(integer startParam)
{
printHelp();
}
changed(integer changes)
{
if (changes & CHANGED_OWNER)
{
printHelp();
}
if (changes & CHANGED_INVENTORY)
{
authorizedUserNames = AUTH_USERS;
initializeNotecardRead();
if (configComplete())
{
state operating;
}
}
}
touch_start(integer nDetected)
{
key owner = llGetOwner();
integer i;
for (i = 0; i < nDetected; ++i)
{
if (llDetectedKey(i) == owner)
{
float time = llGetTime();
if (waitingForDoubleClick)
{
llResetScript();
} else
{
waitingForDoubleClick = TRUE;
llSetTimerEvent(DOUBLE_CLICK_TIMEOUT);
}
return;
}
}
}
timer()
{
llSetTimerEvent(0.0);
if (waitingForDoubleClick)
{
waitingForDoubleClick = FALSE;
touchConfig();
if (configComplete())
{
state operating;
} else
{
printHelp();
}
}
}
dataserver(key request, string data)
{
if (request != noteLineRequest)
{
return;
}
noteLineRequest = NULL_KEY;
if (data == EOF)
{
llOwnerSay("Authorization list loaded.");
nextNoteLine = -1;
if (configComplete())
{
state operating;
} else
{
return;
}
}
string name;
integer commentStart = llSubStringIndex(data, "#");
if (commentStart > 0)
{
name = llGetSubString(data, 0, commentStart-1);
} else if (commentStart == 0)
{
name = "";
} else
{
name = data;
}
name = llStringTrim(name, STRING_TRIM);
if (name != "")
{
authorizedUserNames =
(authorizedUserNames=[])+authorizedUserNames+
[ name ];
}
++nextNoteLine;
noteLineRequest = llGetNotecardLine(ACCESS_NOTECARD, nextNoteLine);
}
}
state operating
{
state_entry()
{
llOwnerSay("Door configured.");
}
changed(integer changes)
{
if (changes & CHANGED_INVENTORY)
{
state default;
}
}
touch_start(integer nDetected)
{
key owner = llGetOwner();
integer nToggles = 0;
integer i;
for (i = 0; i < nDetected; ++i)
{
key id = llDetectedKey(i);
if (waitingForDoubleClick)
{
if (id == owner)
{
llResetScript();
}
} else if (id == owner)
{
waitingForDoubleClick = TRUE;
llSetTimerEvent(DOUBLE_CLICK_TIMEOUT);
return;
} else if (authorized(id))
{
++nToggles;
}
}
if (nToggles%2 != 0)
{
toggleOpenState();
}
}
timer()
{
llSetTimerEvent(0.0);
if (waitingForDoubleClick)
{
waitingForDoubleClick = FALSE;
toggleOpenState();
} else
{
close();
}
}
}