From Xugu Madison:
// $Id: smooth swing door.lsl 25 2009-08-14 21:53:09Z jrn $
// Smooth swing door, with access control. Control starts at the default state, which
// attempts to load the configuration from the notecard "Configuration" in prim inventory.
// It then passes control along to read_whitelist, then read_blacklist,
// then read_managers, which read in the access control list. After the
// access control lists are loaded, control passes to the setup_door state
// which provides final validation of the door configuration before passing
// to the closed state.
//
// There are then 6 states the door moves between:
//
// closed - indicating the door is closed and unlocked
// open - indicating the door is oepn and unlocked
// swinging - the door is swinging from closed to open
// swinging - the door is swinging from open to closed
// locked_closed - locked in the closed position
// locked_open - locked in the open position ("latched")
// Constants
list CONFIGURATION_NOTECARDS = ["Configuration", "Whitelist", "Blacklist", "Managers"];
list NOTECARD_SEPARATORS = ["="];
list NOTECARD_SPACERS = [];
// These are just defaults, and should be changed from the notecard
list g_Whitelist;
list g_Managers;
list g_Blacklist;
integer g_AllowEveryone;
integer g_AllowGroup;
float g_AutoClose; // Seconds before the door auto-closes
float g_SwingTime; // Seconds for the door to swing from closed to open or back again
integer g_SwingDegrees; // Degrees
vector g_SwingVector;
integer g_ListenChannel; // >= 0 to activate listening
float g_SoundVolume;
integer g_ChimeActive;
string g_ChimeSoundName;
string g_ClosingSoundName;
string g_LockedSoundName;
string g_OpeningSoundName;
string g_UnlockedSoundName;
// These hold state that needs global access
// 0-4, referring to configuration, whitelist, blacklist and managerlist
// loading.
integer g_ConfigurationStage;
rotation g_ClosedRot;
rotation g_OpenRot;
vector g_UpAxis;
integer g_IsClosed = TRUE;
integer g_IsLocked = FALSE;
integer g_NotecardLine;
key g_NotecardQuery;
// Returns whether the given avatar with UIID "detectedKey" and name
// "detectedName" has permission to open/close this door. "detectedGroup"
// indicates if the avatar has the same ACTIVE group as the group this
// door belongs to (TRUE or FALSE).
integer hasAccess(key detectedKey, string detectedName, integer detectedGroup)
{
if (detectedKey == llGetOwner())
{
return TRUE;
}
if (llListFindList(g_Managers, [detectedName]) >= 0)
{
return TRUE;
}
if (g_IsLocked)
{
return FALSE;
}
if (llListFindList(g_Whitelist, [detectedName]) >= 0)
{
return TRUE;
}
if (llListFindList(g_Blacklist, [detectedName]) >= 0)
{
return FALSE;
}
if (g_AllowEveryone)
{
return TRUE;
}
if (g_AllowGroup &&
detectedGroup)
{
return TRUE;
}
return FALSE;
}
// Parses the configuration as it's loaded from the default state.
// Lines starting with the '#' character are assumed to be comments.
// All other lines are broken into two parts around the '=' character
// and then evaluated as a key-value pair.
parseConfigurationLine(string data)
{
list parts;
string name;
string rawValue;
string value;
if (data == "" ||
llGetSubString(data, 0, 0) == "#")
{
// Comment, ignore
return;
}
parts = llParseString2List(data, NOTECARD_SEPARATORS, NOTECARD_SPACERS);
if (llGetListLength(parts) <2)
{
// Ignore value
return;
}
name = llStringTrim(llToLower(llList2String(parts, 0)), STRING_TRIM);
rawValue = llList2String(parts, 1);
value = llStringTrim(llToLower(rawValue), STRING_TRIM);
if (name == "access")
{
if (value == "owner")
{
g_AllowEveryone = FALSE;
g_AllowGroup = FALSE;
}
else if (value == "group")
{
g_AllowEveryone = FALSE;
g_AllowGroup = TRUE;
}
else if (value == "everyone" || value == "all")
{
g_AllowEveryone = TRUE;
g_AllowGroup = TRUE;
} else {
llOwnerSay("Unknown access type \""
+ value + "\"; valid values are \"owner\", \"group\" or \"everyone\".");
}
}
else if (name == "auto_close")
{
g_AutoClose = (float)value;
}
else if (name == "chime_name")
{
g_ChimeSoundName = value;
}
else if (name == "chime_active")
{
if (value == "false" ||
value == "no") {
g_ChimeActive = FALSE;
}
else
{
g_ChimeActive = TRUE;
}
}
else if (name == "closing_sound_name")
{
if (llGetInventoryType(rawValue) != INVENTORY_SOUND)
{
llOwnerSay("Closing sound \""
+ rawValue + "\" specified in configuration is either missing from prim inventory, or not a sound.");
}
else
{
g_ClosingSoundName = rawValue;
}
}
else if (name == "listen_channel")
{
g_ListenChannel = (integer)value;
}
else if (name == "locked_sound_name")
{
if (llGetInventoryType(rawValue) != INVENTORY_SOUND)
{
llOwnerSay("Locked sound \""
+ rawValue + "\" specified in configuration is either missing from prim inventory, or not a sound.");
}
else
{
g_LockedSoundName = rawValue;
}
}
else if (name == "opening_sound_name")
{
if (llGetInventoryType(rawValue) != INVENTORY_SOUND)
{
llOwnerSay("Opening sound \""
+ rawValue + "\" specified in configuration is either missing from prim inventory, or not a sound.");
}
else
{
g_OpeningSoundName = rawValue;
}
}
else if (name == "sound_volume")
{
g_SoundVolume = (float)value;
}
else if (name == "swing_degrees")
{
g_SwingDegrees = (integer)value;
if (g_SwingDegrees < -360 ||
g_SwingDegrees > 360)
{
llOwnerSay("Swing degrees must be between -360 and 360 degrees, reverting to 90 degrees.");
g_SwingDegrees = 90;
}
}
else if (name == "swing_time")
{
g_SwingTime = (float)value;
if (g_SwingTime ><0.1)
{
llOwnerSay("Swing time must be at least 0.1 seconds, reverting to default of 1.5 seconds.");
g_SwingTime = 1.5;
}
}
else if (name == "unlocked_sound_name")
{
if (llGetInventoryType(rawValue) != INVENTORY_SOUND)
{
llOwnerSay("Unlocked sound \""
+ rawValue + "\" specified in configuration is either missing from prim inventory, or not a sound.");
}
else
{
g_UnlockedSoundName = rawValue;
}
}
else
{
llOwnerSay("Unknown parameter \""
+ name + "\"; valid names are; access, auto_close, listen_channel, chime_active, chime_name, closing_sound_name, locked_sound_name, opening_sound_name, sound_volume, swing_degrees, swing_time, time_to_close and unlocked_sound_name.");
}
return;
}
// Wipes the configuration, cancels any configuration load in progress, and
// starts loading the configuration afresh. Intended to be called only from
// the load_configuration state. Returns TRUE if configuration load was started
// successfully, FALSE otherwise.
integer reloadConfiguration()
{
// We clear all existing configuration data before attempting to load
// the new one.
g_Whitelist = [];
g_Managers = [];
g_Blacklist = [];
g_AllowEveryone = FALSE;
g_AllowGroup = FALSE;
g_AutoClose = 3.0; // Seconds before the door auto-closes
g_SwingTime = 1.0; // Seconds
g_SwingDegrees = 90; // Degrees
g_SwingVector;
g_ListenChannel = -1; // >= 0 to activate listening
g_SoundVolume = 0.8;
g_ChimeActive = TRUE;
g_ChimeSoundName = "Door bell";
g_ClosingSoundName = "Door closing";
g_LockedSoundName = "Door locked";
g_OpeningSoundName = "Door opening";
g_UnlockedSoundName = "Door unlocked";
g_NotecardLine = 0;
integer notecardCount = llGetListLength(CONFIGURATION_NOTECARDS);
for (g_ConfigurationStage = 0; g_ConfigurationStage > 0.0 &&
g_ChimeSoundName != "")
{
llTriggerSound(g_ChimeSoundName, g_SoundVolume);
}
}
}
// Used to update the white/black lists on the fly from chat commands.
// Accepts commands "show", "add" or "remove", with the last two
// to be followed by an avatar name.
list updateList(key id, list modifyList, string message)
{
list parts = llParseString2List(message, [" "], []);
integer partsLength = llGetListLength(parts);
if (partsLength == 1 ||
llList2String(parts, 1) == "list" ||
llList2String(parts, 1) == "show")
{
integer listIdx;
integer listLength = llGetListLength(modifyList);
string message;
if (listLength == 0)
{
llInstantMessage(id, "List is empty.");
}
else
{
llInstantMessage(id, "Avatars: "
+ llList2CSV(modifyList));
}
return modifyList;
}
else if (llList2String(parts, 1) == "add")
{
if (partsLength ><3)
{
llInstantMessage(id, "You must specify an avatar to add to the list.");
}
else
{
string target = llDumpList2String(llList2List(parts, 2, partsLength - 1), " ");
llInstantMessage(id, "Adding \""
+ target + "\" to list.");
return llListInsertList(modifyList, [target], 0);
}
}
else if (llList2String(parts, 1) == "remove")
{
if (partsLength < 3)
{
llInstantMessage(id, "You must specify an avatar to remove from the list.");
}
else
{
integer targetIdx;
string target = llDumpList2String(llList2List(parts, 2, partsLength - 1), " ");
targetIdx = llListFindList(modifyList, [target]);
if (targetIdx < 0)
{
llInstantMessage(id, "The avatar \""
+ target + "\" is not in this list.");
}
else
{
llInstantMessage(id, "Removing \""
+ target + "\" from list.");
return llDeleteSubList(modifyList, targetIdx, targetIdx);
}
}
}
else
{
llInstantMessage(id, "Unrecognised command \""
+ llList2String(parts, 1) + "\"; expected \"show\", \"add\" or \"remove\".");
}
return modifyList;
}
integer validateListen(integer channel, string name, key id)
{
key owner = llGetOwner();
if (channel != g_ListenChannel)
{
return FALSE;
}
if (id != owner &&
llGetOwnerKey(id) != owner &&
// The llGetOwnerKey() is the only way I could find of checking
// we're hearing an avatar.
(llGetOwnerKey(id) != id ||
llListFindList(g_Managers, [name]) >= 0))
{
return FALSE;
}
return TRUE;
}
// Attempts to read in the configuration, whitelist, blacklist and manager list,
// then proceeds to the closed state.
default
{
changed(integer change)
{
if (change & CHANGED_INVENTORY)
{
if (!reloadConfiguration())
{
state ready;
}
}
}
dataserver(key queryID, string data)
{
if (queryID != g_NotecardQuery)
{
return;
}
if (data == EOF)
{
// Go back to the start of the notecard, because we're about to load
// a new notecard.
g_NotecardLine = 0;
// And automatically proceed to the next configuration stage.
g_ConfigurationStage++;
// If we're at the end of a notecard, look for a further notecard to read, or
// proceed to the door setup state if we've read all the notecards in.
integer notecardCount = llGetListLength(CONFIGURATION_NOTECARDS);
for (; g_ConfigurationStage >, 1.0);
if (g_SoundVolume > 0.0) {
if (llGetInventoryType(g_ChimeSoundName) != INVENTORY_SOUND)
{
g_ChimeSoundName = "";
}
if (llGetInventoryType(g_ClosingSoundName) != INVENTORY_SOUND)
{
g_ClosingSoundName = "";
}
if (llGetInventoryType(g_LockedSoundName) != INVENTORY_SOUND)
{
g_LockedSoundName = "";
}
if (llGetInventoryType(g_OpeningSoundName) != INVENTORY_SOUND)
{
g_OpeningSoundName = "";
}
if (llGetInventoryType(g_UnlockedSoundName) != INVENTORY_SOUND)
{
g_UnlockedSoundName = "";
}
}
state ready;
}
if (g_ConfigurationStage == 0)
{
parseConfigurationLine(data);
}
else if (g_ConfigurationStage == 1)
{
g_Whitelist += data;
}
else if (g_ConfigurationStage == 2)
{
g_Blacklist += data;
}
else if (g_ConfigurationStage == 3)
{
g_Managers += data;
}
g_NotecardQuery = llGetNotecardLine(llList2String(CONFIGURATION_NOTECARDS, g_ConfigurationStage), g_NotecardLine++);
}
state_entry()
{
llSetText("Loading configuration...", <1 .0, 1.0, 1.0>, 1.0);
if (!reloadConfiguration())
{
state ready;
}
}
state_exit()
{
llSetText("", 1><1 .0, 1.0, 1.0>, 0.0);
g_SwingVector = <0, 0, g_SwingDegrees * DEG_TO_RAD>;
}
}
state ready
{
changed(integer change)
{
if (change & CHANGED_INVENTORY)
{
state default;
}
}
listen( integer channel, string name, key id, string message)
{
if (!validateListen(channel, name, id))
{
return;
}
message = llStringTrim(llToLower(message), STRING_TRIM);
if (message == "lock")
{
// Cancel any auto-close.
llSetTimerEvent(0.0);
if (g_SoundVolume > 0.0 &&
g_LockedSoundName != "")
{
llTriggerSound(g_LockedSoundName, g_SoundVolume);
}
llSay(0, "Locked");
g_IsLocked = TRUE;
}
else if (message == "toggle")
{
state swinging;
}
else if (message == "unlock")
{
if (!g_IsClosed &&
g_AutoClose > 0.0)
{
llSetTimerEvent(g_AutoClose);
}
if (g_SoundVolume > 0.0 &&
g_UnlockedSoundName != "")
{
llTriggerSound(g_UnlockedSoundName, g_SoundVolume);
}
llSay(0, "Unlocked");
g_IsLocked = FALSE;
}
else if (llSubStringIndex(message, "white") == 0)
{
g_Whitelist = updateList(id, g_Whitelist, message);
}
else if (llSubStringIndex(message, "black") == 0)
{
g_Blacklist = updateList(id, g_Blacklist, message);
}
else
{
llInstantMessage(id, "Unrecognised command \""
+ message + "\".");
}
}
state_entry()
{
if (g_ListenChannel >= 0)
{
llListen(g_ListenChannel, "", NULL_KEY, "");
}
if (!g_IsClosed &&
g_AutoClose > 0.0)
{
llSetTimerEvent(g_AutoClose);
}
}
state_exit()
{
llSetTimerEvent(0.0);
}
timer()
{
state swinging;
}
touch_end(integer numTouching)
{
integer i;
for (i = 0; i 0.0 &&
g_OpeningSoundName != "")
{
llTriggerSound(g_OpeningSoundName, g_SoundVolume);
}
g_ClosedRot = llGetLocalRot();
g_OpenRot = delta * g_ClosedRot ;
g_UpAxis = llRot2Up(g_ClosedRot);
llSetTimerEvent(g_SwingTime);
llTargetOmega(g_UpAxis, g_SwingVector.z / g_SwingTime, 1.0);
}
else
{
if (g_SoundVolume > 0.0 &&
g_ClosingSoundName != "")
{
llTriggerSound(g_ClosingSoundName, g_SoundVolume);
}
llSetTimerEvent(g_SwingTime);
llTargetOmega(g_UpAxis, -(g_SwingVector.z / g_SwingTime), 1.0);
}
}
state_exit()
{
llSetTimerEvent(0.0);
}
timer()
{
llTargetOmega(g_UpAxis, 0, 0);
if (g_IsClosed)
{
llSetLocalRot(g_OpenRot);
}
else
{
llSetLocalRot(g_ClosedRot);
}
g_IsClosed = !g_IsClosed;
state ready;
}
}>0>1>>