/*
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?
integer allowQueryAll = TRUE; // should device react to " all" query commands?
// 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 //
///////////////////////////////////////////////////////////
// version
string version = "v12"; // engine script version.
// 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 == "allowqueryall") {
if(value == "true")
allowQueryAll = TRUE;
else if(value == "false")
allowQueryAll = FALSE;
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 && allowQueryAll))
return;
// COMMANDS THAT ARE AVAILABLE TO NON-ADMINS 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 == "power")
if(opt1 == "on")
grid_powerup(id);
else if(opt1 == "off")
grid_powerdown(id);
if(command == "reset")
device_reset(id);
if(llGetSubString(command, 0, 4) == "user_") {
if(opt2 != "")
command += " " + llDumpList2String(llList2List(queryWords, 1, -1), " ");
device_send_message((integer)opt1, command, id);
}
if(forme && (command == "menu"))
device_main_menu(id);
if(!isAdmin(id))
return;
// COMMANDS THAT REQUIRE ADMIN RIGHTS GO IN THIS SECTION
if(command == "debug")
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);
if(command == "adminlock")
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") {
integer newChannel = (integer)opt1;
if(newChannel == 0)
llInstantMessage(id, "Cannot set query channel to 0!");
else
device_set_querychannel(newChannel);
}
if(command == "touchmode")
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 == "allowqueryall")
if(opt1 == "on")
allowQueryAll = TRUE;
else if(opt1 == "off")
allowQueryAll = FALSE;
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(forme && (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(forme && (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"];
if(isAdmin(user)) {
buttons += ["Set amps", "Rename"];
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) {
integer isadmin = isAdmin(user);
// this protects both against race conditions and against rogue menu channel talk.
if(adminMode && !isadmin) {
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));
// COMMANDS AVAILABLE TO ALL USERS GO HERE
if(msg == "Reset")
device_reset(user);
else if((msg == "Turn ON") || (msg == "Turn OFF"))
device_toggle_power(user);
if(!isadmin)
return;
// COMMANDS AVAILABLE TO ADMINS ONLY GO HERE
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 {
if(msg == "Debug OFF")
device_send_message(FALSE, "debug", user);
else if(msg == "Debug ON")
device_send_message(TRUE, "debug", user);
else if(msg == "IDs OFF")
device_send_message(FALSE, "debugids", user);
else if(msg == "IDs ON")
device_send_message(TRUE, "debugids", user);
else if(msg == "Lock OFF")
device_send_message(FALSE, "adminlock", user);
else if(msg == "Lock ON")
device_send_message(TRUE, "adminlock", user);
else if(msg == "Warn OFF")
device_send_message(FALSE, "adminlockwarn", user);
else if(msg == "Warn ON")
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));
// this is done unconditionally, to make sure the grid knows we're down even if we somehow
// messed up our internal power state.
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);
}
// 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";
// certain info is admin's business only.
if(isAdmin(user)) {
if(adminModeWarn)
result += " (warnings ON)";
else
result += " (warnings OFF)";
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 + "version: " + version + separator + "free memory: " + (string)llGetFreeMemory() + separator + "query channel: " + (string)queryChan + separator + "grid channel: " + (string)pwrChan + 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 += " (smart touch delay: " + float2string1(smartTouchDelay) + "s)";
return(result);
}
// 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");
}
// DEVICE PROPERTY SETTER ROUTINES
// 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!");
}
// 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);
}
// Set 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);
}
}
/////////////////////////////
// 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() {
// overload message MUST be sent first to allow secondary scripts to know that the following power 0 is due to an overload.
device_send_message(0, "overload", NULL_KEY);
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);
}
}