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 < notecardCount; g_ConfigurationStage++) { string notecardName = llList2String(CONFIGURATION_NOTECARDS, g_ConfigurationStage); if (startReadingNotecard(notecardName)) { return TRUE; } } // No notecards to load return FALSE; } // Verifies a notecard exists in prim inventory, and warns if the script cannot // retrieve a UUID for it. Returns TRUE if the notecard exists, FALSE otherwise. integer startReadingNotecard(string notecardName) { // Check that the notecard exists, is a notecard, and actually has an // asset backing it. if (llGetInventoryType(notecardName) != INVENTORY_NOTECARD) { return FALSE; } if (llGetInventoryKey(notecardName) == NULL_KEY) { llOwnerSay("I can't retrieve the key for the notecard \"" + notecardName + "\"; this could indicate it's not full perms, or that's it's empty. If it's empty, the asset server will error when I try reading it. I just want you to know this isn't my fault."); } g_NotecardQuery = llGetNotecardLine(notecardName, g_NotecardLine++); return TRUE; } // Handles chiming (text & audio notification) of being clicked while in // the closed state. triggerChime(string avatarName) { if (g_ChimeActive) { llSay(0, avatarName + " is at the door."); if (g_SoundVolume > 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 < notecardCount; g_ConfigurationStage++) { string notecardName = llList2String(CONFIGURATION_NOTECARDS, g_ConfigurationStage); if (startReadingNotecard(notecardName)) { return; } } llSetText("Completing setup...", <1.0, 1.0, 1.0>, 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.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 < numTouching; i++) { key detectedKey = llDetectedKey(i); string detectedName = llDetectedName(i); if (g_IsClosed) { triggerChime(detectedName); } if (hasAccess(detectedKey, detectedName, llDetectedGroup(i))) { if (g_IsLocked) { llSay(0, "Lock overridden by priority access."); } state swinging; } else if (g_IsLocked) { llInstantMessage(detectedKey, "Sorry, the door is locked."); } else { llInstantMessage(detectedKey, "Sorry, you do not have permission to open this door."); } } } } state swinging { state_entry() { rotation delta = llEuler2Rot(g_SwingVector); if (g_IsClosed) { if (g_SoundVolume > 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; } }