/* Schneelocke Saarinen's engine scripts for devices using the Black Gazza power grid. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ // you shouldn't have to change this. string configNotecardName = "config"; // name of notecard holding configuration data // don't change these (although it likely won't hurt if you do). // they're only up here so they can be used as default values for touchMode, anyway. integer TOUCH_NONE = 0; // touching object does nothing integer TOUCH_TOGGLE = 1; // touching object toggles power integer TOUCH_MENU = 2; // touching object brings up menu integer TOUCH_SMART = 3; // touching object for >smartTouchDelay brings up menu; otherwise, toggles power // these can be set in the config notecard. integer pwrChan = -139849; // channel used for BG grid communication integer defaultQueryChan = 1496704899; // channel used for user-initiated device queries integer debugMode = FALSE; // debug mode enabled? integer debugIDs = FALSE; // should debug message show IDs in addition to object/avatar names? integer adminMode = FALSE; // restrict device control to admin? integer touchMode = TOUCH_SMART; // smart touch mode float smartTouchDelay = 0.4; // seconds integer adminModeWarn = FALSE; // warn admin on unauthorized access? // these MUST be set in the config notecard string deviceName; // device name float amps; // current to draw /////////////////////////////////////////////////////////// // (PROBABLY) NO NEED TO CHANGE ANYTHING BELOW THIS LINE // /////////////////////////////////////////////////////////// // internal constants integer TEXTBOX_NONE = 0; // llTextBox: not in use integer TEXTBOX_AMPS = 1; // llTextBox: setting amps integer TEXTBOX_DEVICENAME = 2; // llTextBox: setting device name // internal variables integer poweron = FALSE; // power on? integer queryChan = 0; // current query channel key pwrGenID = NULL_KEY; // id of the power generator we're using key notecardGetLineID = NULL_KEY; // query ID, used by dataserver event integer notecardCurLine = 0; // current notecard line key powerRequesterID = NULL_KEY; // kludge: user who requested power from the BG grid. integer queryChanHandle = 0; // handle returned by llListen() float touchedTime = 0.0; // time object was touched integer menuChannel = -1; // llDialog channel integer textChannel = -1; // llTextBox channel integer textBoxMode = TEXTBOX_NONE; // llTextBox: used for what? //////////////////////////////////////////////////////////////////////////////// // device sub-system: handles the device's internal affairs and communication // //////////////////////////////////////////////////////////////////////////////// // DEVICE SETUP ROUTINES // this gets called when the device is reset. device_setup() { // set "not initialized" values deviceName = ""; amps = -1; // start reading configuration notecard. // rest of setup is handled by device_setup2, via dataserver event. notecardGetLineID = llGetNotecardLine(configNotecardName, 0); } // helper routine for device setup that gets called by dataserver event device_setup2(string data) { string property = ""; string value = ""; integer intval = 0; float floatval = 0.0; integer sepPos = -1; // data is the current line from the configuration notecard if(data != EOF) { data = llStringTrim(data, STRING_TRIM); if(llGetSubString(data, 0, 0) != "#") { // skip comments sepPos = llSubStringIndex(data, "="); if(sepPos != -1) { property = llToLower(llStringTrim(llGetSubString(data, 0, sepPos - 1), STRING_TRIM)); value = llStringTrim(llGetSubString(data, sepPos + 1, -1), STRING_TRIM); intval = (integer)value; floatval = (float)value; // we handle this as a special case first so we can avoid lowercasing the device name. if(property == "devicename") { device_rename(value, NULL_KEY); } else { value = llToLower(value); if(property == "amps") { if(floatval > 0.0) device_set_amps(floatval, NULL_KEY); else llOwnerSay("Cannot set current drawn to 0 or less (ignored)."); } else if(property == "gridchannel") { pwrChan = intval; } else if(property == "querychannel") { if(intval != 0) device_set_querychannel(intval); else llOwnerSay("Invalid query channel (ignored): " + value); } else if(property == "debug") { if(value == "true") device_send_message(TRUE, "debug", NULL_KEY); else if(value == "false") device_send_message(FALSE, "debug", NULL_KEY); else llOwnerSay("Malformed boolean value (ignored): " + property + "=" + value); } else if(property == "debugids") { if(value == "true") device_send_message(TRUE, "debugids", NULL_KEY); else if(value == "false") device_send_message(FALSE, "debugids", NULL_KEY); else llOwnerSay("Malformed boolean value (ignored): " + property + "=" + value); } else if(property == "adminlock") { if(value == "true") device_send_message(TRUE, "adminlock", NULL_KEY); else if(value == "false") device_send_message(FALSE, "adminlock", NULL_KEY); else llOwnerSay("Malformed boolean value (ignored): " + property + "=" + value); } else if(property == "adminlockwarn") { if(value == "true") device_send_message(TRUE, "adminlockwarn", NULL_KEY); else if(value == "false") device_send_message(FALSE, "adminlockwarn", NULL_KEY); else llOwnerSay("Malformed boolean value (ignored): " + property + "=" + value); } else if(property == "smarttouchdelay") { if(floatval >= 0.0) device_set_smarttouchdelay(floatval); else llOwnerSay("Cannot set smart touch delay to less than 0 (ignored)."); } else if(property == "touchmode") { if(value == "off") device_set_touchmode(TOUCH_NONE, NULL_KEY); else if(value == "toggle") device_set_touchmode(TOUCH_TOGGLE, NULL_KEY); else if(value == "menu") device_set_touchmode(TOUCH_MENU, NULL_KEY); else if(value == "smart") device_set_touchmode(TOUCH_SMART, NULL_KEY); else llOwnerSay("Unrecognized touch mode (ignored): " + value); } else { llOwnerSay("Unrecognized configuration property (ignored): " + property + "=" + value); } } } else if(data != "") { llOwnerSay("Malformed configuration line (ignored): " + data); } } // line 0 has already been read in state_entry event. notecardGetLineID = llGetNotecardLine(configNotecardName, ++notecardCurLine); } else { // EOF hit; entire config notecard has been read if((deviceName != "") && (amps != -1)) { device_set_objectdesc(); device_setup_menu(); grid_setup(); // no listener was set up, so we'll listen on the default channel. if(!queryChanHandle) device_set_querychannel(defaultQueryChan); llSay(PUBLIC_CHANNEL, "Ready."); } else { llOwnerSay("Could not initialize. Bad configuration data: deviceName=" + deviceName + ", amps=" + (string)amps); } } } // DEVICE MESSAGE SUBSYSTEM // handle device message (llMessageLinked). // this does not perform sanity checking on parameters; it is the sender's responsibility to only send sane commands. device_handle_message(integer num, string cmd, key id) { string result = ""; debugMsg("Handling device message: num=" + (string)num + ", str=" + cmd + ", id=" + id2string(id)); if(cmd == "debug") { debugMode = num; result += "Debug messages (to owner) "; if(debugMode) result += "enabled"; else result += "disabled"; } else if(cmd == "debugids") { debugIDs = num; result += "IDs in debug messages "; if(debugIDs) result += "enabled"; else result += "disabled"; } else if(cmd == "adminlock") { adminMode = num; result += "Admin lock "; if(adminMode) result += "enabled"; else result += "disabled"; } else if(cmd == "adminlockwarn") { adminModeWarn = num; result += "Warnings on unauthorized access "; if(adminModeWarn) result += "enabled"; else result += "disabled"; } else if(cmd == "reset") { llResetScript(); // this line is never reached } else if(llGetSubString(cmd, 0, 6) == "setname") { deviceName = llGetSubString(cmd, 8, -1); result += "Renamed to " + deviceName + "."; } else if((cmd == "power") || (cmd == "setamps")) { // ignored to avoid "unknown/unhandled message type" debug message. } else { // commands that start with 'user_' are explicitely intended for non-engine communication and thus ignored by the engine core. if(llGetSubString(cmd, 0, 4) != "user_") debugMsg("Unknown/unhandled message type: " + cmd); } if((result != "") && (id != NULL_KEY)) // 2-second delay! llInstantMessage(id, result); } // send device message device_send_message(integer num, string str, key id) { debugMsg("Sending device message: num=" + (string)num + ", str=" + str + ", id=" + id2string(id)); llMessageLinked(LINK_SET, num, str, id); } // DEVICE QUERY SUBSYSTEM // (re)set query interface channel. channel must not be 0. device_set_querychannel(integer channel) { debugMsg("Setting query channel to " + (string)channel); if(queryChanHandle) llListenRemove(queryChanHandle); queryChan = channel; queryChanHandle = llListen(queryChan, "", NULL_KEY, ""); } // handle device query device_handle_query(string msg, key id) { list queryWords = []; string command = ""; string opt1 = ""; string opt2 = ""; string optall = ""; integer forme = FALSE; integer forall = FALSE; debugMsg("Handling query from " + id2string(id) + ": " + msg); if(adminMode && !isAdmin(id)) { debugMsg("Admin lock engaged, ignoring query from " + id2string(id)); if(adminModeWarn) llOwnerSay("Unauthorized access attempt (query command) by " + id2string(id) + ": " + msg); // 2-second delay! llInstantMessage(id, "Device is in admin only mode."); return; } // split along whitespace queryWords = llParseString2List(llToLower(msg), [" "], []); command = llList2String(queryWords, 0); // options specified? if(llGetListLength(queryWords) > 1) { optall = llDumpList2String(llList2List(queryWords, 1, -1), " "); // options start with devicename if(llGetSubString(optall, 0, llStringLength(deviceName) - 1) == llToLower(deviceName)) { forme = TRUE; // further options specified? if(optall == llToLower(deviceName)) optall = ""; else optall = llStringTrim(llGetSubString(optall, llStringLength(deviceName), -1), STRING_TRIM); } else if(llGetSubString(optall, 0, 2) == "all") { forall = TRUE; // further options specified? if(optall == "all") optall = ""; else optall = llStringTrim(llGetSubString(optall, 3, -1), STRING_TRIM); } // at this point, optall contains the remaining options. queryWords = llParseString2List(llToLower(optall), [" "], []); opt1 = llToLower(llList2String(queryWords, 0)); opt2 = llToLower(llList2String(queryWords, 1)); } debugMsg("command=" + command + ", forme=" + (string)forme + ", forall=" + (string)forall + ", optall=" + optall + ", opt1=" + opt1 + ", opt2=" + opt2); if(!forme && !forall) return; // COMMANDS THAT CAN AFFECT ALL DEVICES GO IN THIS SECTION. if(command == "status") // 2-second delay! llInstantMessage(id, device_status(", ", id)); if(command == "config") // 2-second delay! llInstantMessage(id, device_config(", ", id)); if(command == "debug") if(isAdmin(id)) if(opt1 == "on") device_send_message(TRUE, "debug", id); else if(opt1 == "off") device_send_message(FALSE, "debug", id); else if(opt1 == "ids") if(opt2 == "on") device_send_message(TRUE, "debugids", id); else if(opt2 == "off") device_send_message(FALSE, "debugids", id); // for practical reasons, only the admin can enable adminlock mode. if(command == "adminlock") if(isAdmin(id)) if(opt1 == "on") device_send_message(TRUE, "adminlock", id); else if(opt1 == "off") device_send_message(FALSE, "adminlock", id); else if(opt1 == "warn") if(opt2 == "on") device_send_message(TRUE, "adminlockwarn", id); else if(opt2 == "off") device_send_message(FALSE, "adminlockwarn", id); if(command == "setquerychannel") if(isAdmin(id)) { integer newChannel = (integer)opt1; if(newChannel == 0) llInstantMessage(id, "Cannot set query channel to 0!"); else device_set_querychannel(newChannel); } if(command == "touchmode") if(isAdmin(id)) { if(opt1 == "off") device_set_touchmode(TOUCH_NONE, id); else if(opt1 == "toggle") device_set_touchmode(TOUCH_TOGGLE, id); else if(opt1 == "menu") device_set_touchmode(TOUCH_MENU, id); else if(opt1 == "smart") device_set_touchmode(TOUCH_SMART, id); else llInstantMessage(id, "Unrecognized touch mode: " + opt1); } if(command== "smarttouchdelay") { float floatval = (float)opt1; if(floatval >= 0.0) device_set_smarttouchdelay(floatval); else llInstantMessage(id, "Cannot set smart touch delay to less than 0!"); } if(command == "power") if(opt1 == "on") grid_powerup(id); else if(opt1 == "off") grid_powerdown(id); if(command == "reset") device_reset(id); if(!forme) return; // COMMANDS THAT CAN ONLY AFFECT A SPECIFIC DEVICE GO IN THIS SECTION. if(command == "menu") device_main_menu(id); if(command == "setamps") if(poweron) { // 2-second delay! llInstantMessage(id, "Cannot change amps while device is running!"); } else { float newamps = (float)opt1; if(newamps <= 0.0) // 2-second delay! llInstantMessage(id, "Cannot set current drawn to 0 or less!"); else // current gets sent as mA device_set_amps((float)opt1, id); } if(command == "rename") device_rename(optall, id); } // DEVICE MENU SUBSYSTEM device_setup_menu() { // this is from http://wiki.secondlife.com/wiki/LlDialog . menuChannel = ((integer)("0x" + llGetSubString((string)llGetKey(), -8, -1)) & 0x3FFFFFFF) ^ 0xBFFFFFFF; textChannel = menuChannel - 1; debugMsg("Menu interface listening on channel " + (string)menuChannel + "; textbox listening on channel " + (string)textChannel); llListen(menuChannel, "", NULL_KEY, ""); llListen(textChannel, "", NULL_KEY, ""); } // present main device menu to a user- device_main_menu(key user) { list buttons = []; if(poweron) buttons = ["Turn OFF"]; else buttons = ["Turn ON"]; buttons += ["Reset", "Set amps", "Rename" ]; if(isAdmin(user)) { if(debugMode) buttons += ["Debug OFF"]; else buttons += ["Debug ON"]; if(debugIDs) buttons += ["IDs OFF"]; else buttons += ["IDs ON"]; if(adminMode) buttons += ["Lock OFF"]; else buttons += ["Lock ON"]; if(adminModeWarn) buttons += ["Warn OFF"]; else buttons += ["Warn ON"]; } // 1-second delay! llDialog(user, device_status("\n", user), buttons, menuChannel); } // handle menu button clicked by a user. device_handle_menu(string msg, key user) { // this protects both against race conditions and against rogue menu channel talk. if(adminMode && !isAdmin(user)) { debugMsg("Admin lock engaged, ignoring button click from " + id2string(user)); // 2-second delay! if(adminModeWarn) llOwnerSay("Unauthorized access attempt (menu button click) by " + id2string(user) + ": " + msg); llInstantMessage(user, "Device is in admin only mode."); return; } debugMsg("Button " + msg + " clicked by " + id2string(user)); if(msg == "Reset") { device_reset(user); } else if(msg == "Set amps") { if(poweron) { // 1-second delay! llDialog(user, "Cannot change current drawn while device is running!", [], menuChannel); } else { // 1-second delay! textBoxMode = TEXTBOX_AMPS; llTextBox(user, "Enter new current drawn:", textChannel); } } else if(msg == "Rename") { // 1-second delay! textBoxMode = TEXTBOX_DEVICENAME; llTextBox(user, "Enter new device name:", textChannel); } else { // simple toggle button presses go here. if((msg == "Turn ON") || (msg == "Turn OFF")) { device_toggle_power(user); } else if(msg == "Debug OFF") { if(isAdmin(user)) device_send_message(FALSE, "debug", user); } else if(msg == "Debug ON") { if(isAdmin(user)) device_send_message(TRUE, "debug", user); } else if(msg == "IDs OFF") { if(isAdmin(user)) device_send_message(FALSE, "debugids", user); } else if(msg == "IDs ON") { if(isAdmin(user)) device_send_message(TRUE, "debugids", user); } else if(msg == "Lock OFF") { if(isAdmin(user)) device_send_message(FALSE, "adminlock", user); } else if(msg == "Lock ON") { if(isAdmin(user)) device_send_message(TRUE, "adminlock", user); } else if(msg == "Warn OFF") { if(isAdmin(user)) device_send_message(FALSE, "adminlockwarn", user); } else if(msg == "Warn ON") { if(isAdmin(user)) device_send_message(TRUE, "adminlockwarn", user); } // present menu to user again. // disabled for now, see thoughts in TODO.txt // device_main_menu(user); } } // handle textbox input. device_handle_textbox(string msg, key user) { // this protects both against race conditions and against rogue textbox channel talk. if(adminMode && !isAdmin(user)) { debugMsg("Admin lock engaged, ignoring textbox message from " + id2string(user)); if(adminModeWarn) llOwnerSay("Unauthorized access attempt (textbox input) by " + id2string(user) + ": " + msg); // 2-second delay! llInstantMessage(user, "Device is in admin only mode."); return; } debugMsg("Handling textbox message from " + id2string(user) + " in mode " + (string)textBoxMode + ": " + msg); if(textBoxMode == TEXTBOX_AMPS) { float newamps = (float)msg; if(newamps <= 0.0) // 1-second delay! llDialog(user, "Cannot set current drawn to 0 or less!", [], menuChannel); else device_set_amps(newamps, user); } else if(textBoxMode == TEXTBOX_DEVICENAME) { device_rename(msg, user); } textBoxMode = TEXTBOX_NONE; } // GENERAL DEVICE ROUTINES // call this to reset the device. Do NOT call llResetScript - use this instead. device_reset(key id) { debugMsg("Device reset by " + id2string(id)); grid_powerdown(id); device_send_message(0, "reset", id); } // call this to toggle the device's power status (on->off or off->on). device_toggle_power(key user) { debugMsg("Power toggled by " + id2string(user)); if(poweron) grid_powerdown(user); else grid_powerup(user); } // call this to rename the device. device_rename(string newName, key user) { debugMsg("Device renamed to " + newName + " by " + id2string(user)); newName = llStringTrim(newName, STRING_TRIM); if(newName != "") device_send_message(0, "setname " + newName, user); else llInstantMessage(user, "Cannot set device name to empty string!"); } // return current device status. // separator: string used to separate properties, e.g. ", " or "\n". // user: requesting user. Used to determine what information to return. string device_status(string separator, key user) { debugMsg("Device status requested by " + id2string(user)); string result = "Device: " + deviceName + separator + "amps: " + (string) amps + separator + "currently turned "; if(poweron) result += "ON"; else result += "OFF"; result += separator + "admin lock "; if(adminMode) result += "ON"; else result += "OFF"; if(isAdmin(user)) if(adminModeWarn) result += " (warnings ON)"; else result += " (warnings OFF)"; // debug status is admin's business only. if(isAdmin(user)) { result += separator + "debug mode "; if(debugMode) { result += "ON (IDs "; if(debugIDs) result += "ON"; else result += "OFF"; result += ")"; } else { result += "OFF"; } } return(result); } // return current device configuration. // separator: string used to separate properties, e.g. ", " or "\n". // user: requesting user. Used to determine what information to return. string device_config(string separator, key user) { debugMsg("Device config requested by " + id2string(user)); if(!isAdmin(user)) return ""; string result = "Device: " + deviceName + separator + "query channel: " + (string)queryChan + separator + "touch mode: "; if(touchMode == TOUCH_NONE) result += "off"; else if(touchMode == TOUCH_TOGGLE) result += "toggle"; else if(touchMode == TOUCH_MENU) result += "menu"; else if(touchMode == TOUCH_SMART) result += "smart"; result += separator + "smart touch delay: " + float2string1(smartTouchDelay) + "s"; return(result); } // Set smart touch delay, with appropriate rounding applied. device_set_smarttouchdelay(float newValue) { smartTouchDelay = ((float) llRound(newValue * 10)) / 10; debugMsg("Setting smart touch delay to " + (string)smartTouchDelay + " ("+ (string)newValue + ")"); } // Set touch mode. device_set_touchmode(integer mode, key user) { debugMsg("Touch mode set to " + (string)mode + " by " + id2string(user)); touchMode = mode; device_set_objectdesc(); } // Set amps. This should only be called if power is off; otherwise, it will only take effect when device is powercycled. device_set_amps(float newamps, key user) { debugMsg("Current drawn set to " + (string)newamps + " by " + id2string(user)); amps = newamps; device_send_message((integer)(amps * 1000), "setamps", user); } // internal helper: manage device power. // id is the requesting user, or NULL_KEY if none (e.g. in case of grid overload) device_set_power(integer newpower, key id) { debugMsg("Power set to " + (string)newpower + " by " + id2string(id)); if(newpower != poweron) { poweron = newpower; device_send_message(poweron, "power", id); } } // internal helper: set object desc based on touchmode. // this can't be done in device_set_touchmode since it also needs to be done if the default touchmode remains unchanged. device_set_objectdesc() { if(touchMode == TOUCH_NONE) llSetObjectDesc(""); else if(touchMode == TOUCH_TOGGLE) llSetObjectDesc("Touch to Toggle On/Off"); else if(touchMode == TOUCH_MENU) llSetObjectDesc("Touch for Menu"); else if(touchMode == TOUCH_SMART) llSetObjectDesc("Touch On/Off, Touch+Hold for Menu"); } ///////////////////////////// // BG power grid routines // ///////////////////////////// // BG grid: send grid message to specified id, or entire region if NULL_KEY grid_send_message(string msg, key id) { if(id == NULL_KEY) { debugMsg("Sending grid message: " + msg); llRegionSay(pwrChan, msg); } else { debugMsg("Sending grid message to " + id2string(id) + ": " + msg); llRegionSayTo(id, pwrChan, msg); } } // BG grid: handle a message from the BG power grid. grid_handle_message(string msg, key id) { debugMsg("Got grid message from " + id2string(id) + ": msg=" + msg); if(llGetSubString(msg, 0, 2) == "ACK") { if(poweron == TRUE) { // already got power; disconnect secondary generators grid_send_message("FIN", id); } else { // see comment in grid_powerup about powerRequesterID device_set_power(TRUE, powerRequesterID); powerRequesterID = NULL_KEY; pwrGenID = id; } } else if(msg == "OVERLOAD") { if(id == pwrGenID) grid_overload(); } else if(msg == "PING") { if(poweron == TRUE && id == pwrGenID) grid_send_message("PONG", id); } else if((msg == "PONG") || (msg == "FIN") || (msg == "ERR") || llGetSubString(msg, 0, 2) == "REQ") { // ignored; avoid triggering "unknown grid message" debug message // Concerning ignoring ERR, see TODO.txt } else { debugMsg("Unknown grid message from " + id2string(id) + ": " + msg); } } // BG grid: power down grid_powerdown(key id) { if(poweron) { debugMsg("Grid powering down, initiated by " + id2string(id)); grid_send_message("FIN", pwrGenID); device_set_power(FALSE, id); } else { debugMsg("Got powerdown request while already powered down, from " + id2string(id)); } } // BG grid: power up grid_powerup(key id) { if(!poweron) { debugMsg("Grid powering up, initiated by " + id2string(id)); // this is a kludge, but there's no other way to save who powered us on for when the grid replies. powerRequesterID = id; grid_send_message("FIN", pwrGenID); // this is probably not necessary, but Bo does it. grid_send_message("REQ " + (string) amps, NULL_KEY); } else { debugMsg("Got powerup request while already powered up, from " + id2string(id)); } } // BG grid: overload grid_overload() { llSay(0, "The " + deviceName + " spontaneously turns off."); device_set_power(FALSE, NULL_KEY); } // BG grid: setup grid_setup() { // listen for BG power grid messages llListen(pwrChan, "", NULL_KEY, ""); } //////////////////////////// // Misc. helper routines // //////////////////////////// // helper: send debug message debugMsg(string msg) { if(debugMode) { llOwnerSay("DEBUG: " + llGetScriptName() + ": " + msg); } } // helper: convert id to string, including name string id2string(key id) { if(id == NULL_KEY) return "noone in particular"; else if(debugIDs) return (string)id + " (" + llKey2Name(id) + ")"; else return llKey2Name(id); } // helper: convert float to string with only one decimal place. string float2string1(float floatVal) { string result = (string)floatVal; return llGetSubString(result, 0, llSubStringIndex(result, ".") + 1); } // helper: return TRUE if user administers device, FALSE otherwise. // For now, this is precisely the owner. integer isAdmin(key user) { return(user == llGetOwner()); } ///////////////////// // State handlers // ///////////////////// default { state_entry() { device_setup(); } changed(integer change) { // device inventory got changed; presumably, config notecard was edited. // reset entire device; re-reading of notecard is triggered on state entry. if(change & CHANGED_INVENTORY) { debugMsg("Inventory change detected, resetting device"); device_reset(NULL_KEY); } } touch_start(integer total_number) { llSetTimerEvent(0.1); } timer() { touchedTime += 0.1; } touch_end(integer total_number) { // who's touched us? key user = llDetectedKey(0); debugMsg("Touched by " + id2string(user) + " for " + (string)touchedTime + " seconds."); // this gets saved in a variable so we can reset touchedTime. integer toggleOrMenu = (touchMode == TOUCH_TOGGLE) || ((touchMode == TOUCH_SMART) && (touchedTime < smartTouchDelay)); // This needs to happen before the admin lock check etc. llSetTimerEvent(0.0); touchedTime = 0.0; // touchmode = off; do nothing. if(touchMode == TOUCH_NONE) return; if(adminMode && !isAdmin(user)) { debugMsg("Admin lock engaged, ignoring touch from " + id2string(user)); if(adminModeWarn) llOwnerSay("Unauthorized access attempt (touch) by " + id2string(user)); // 2-second delay! llInstantMessage(user, "Device is in admin only mode."); return; } else { if(toggleOrMenu) device_toggle_power(user); else device_main_menu(user); } } link_message(integer sender_num, integer num, string str, key id) { // handle messages from linked prims device_handle_message(num, str, id); } listen(integer channel, string name, key id, string msg) { if(channel == pwrChan) grid_handle_message(msg, id); else if(channel == queryChan) device_handle_query(msg, id); else if(channel == menuChannel) device_handle_menu(msg, id); else if(channel == textChannel) device_handle_textbox(msg, id); } dataserver(key queryID, string data) { debugMsg("Dataserver event, query=" + (string)queryID + ", data=" + data); // device setup if(queryID == notecardGetLineID) device_setup2(data); else debugMsg("Unknown/unexpected dataserver event: queryID=" + (string)queryID + ", data=" + data); } }