// Davan Camus, 2005 November // file: lamp_controller.lsl.c // // Control Script for on-at-dusk lighting // with various other controls // // Chat commands are only active temporarily, after a touch. // // commands are: // light on // on // light off // off // auto on // auto off // automatic // manual // blink // blink off // color (one of) white fluorescent sodium party red orange yellow green blue purple pink // guest off on // resume (lose guest setting, assume owner setting again) // status (current on/off, color, &c) // info (sales pitch) // debug on/off // bank sets the lighting-bank membership, for grouped controls (later version :) // // on/off commands default to "on" if argument omitted, so "auto" or "debug" turns on. // color by itself sets the color // // Menu tree, for clicking on lamp: // (Any menu, if isGuest, will include note about being temporary, credit me the creator) // Top: color choices (red, blue, white, sodium, fluorescent) // on, off, automatic, manual, More... // More...: Guest off // Guest 90s // // bank A // bank B // // // guest control can be activated for "temporary" changes, which revert after // a few minutes // // info shows: Number of days running, owner, creator, version //string gVersion = "0.64d [051127]"; string gVersion = "0.7 [061122]"; integer COMMAND_CHANNEL = 925; // always-on channel integer gListenerNumber = 0; integer gDialogCommandChannel; // temporary, during dialog up integer gDialogListenerNumber = 0; // temporary, during dialog up integer gDebug = 0; integer gCommandsEverReceived = 0; string gColorName; vector gColor; integer gOnOff; // 1 for on integer gAuto; // 1 for auto integer gAutoActive; // gate for gAuto -- when you manually turn light on or off, gAutoActive is disabled. // gAutoActive gets reset whenever day->night or night->day float gGuestTime; // how long since a guest setting... or -1 if no guest setting vector gGuestColor; // guest-chosen setting (if temporary guest-control on) integer gGuestOnOff; // guest-chosen setting (if temporary guest-control on) integer DUSKDAWNODDS = 5; // 1 in Xxx odds of going on (at timer rate), so several wont go on at once float gTime = 0; float gTickSize; // set whenever we set the timer event, so we know how long we've been // gGuestTimeout control guest access. 0 means "no guest access". positive: partial, in seconds. -1: full access! integer gGuestTimeout = 9; // how long a guest setting stays in effect, seconds! zero means: No Guest Access At all float gChatTime = 0; //chat commands only work while positive... doDialog resets it. float gChatTimeout = 50.0; // how long the channel is active after a dialog starts. integer gIsNight; // set in event handlers to avoid it changing while we are processing // the kinds of possible user: integer GUEST_NO_ACCESS = 0; integer GUEST_TIMED_ACCESS = 1; integer OWNER = 3; // wrapper for set timer event, where we keep track of it. setTimerEvent(float t) { gTickSize = t; llSetTimerEvent(t); } // d([this, that, the other]); prints debug stuff // (I find it easier to type the [list,notation] than all // that tedious casting-to-string and adding stuff d(list debugList) { if(!gDebug) return; else { string s = list2String(debugList); llOwnerSay("(dbg) "+s); } } // Take a list, type-and-cast appropriately, and concat it // all into a string. useful for debug displays. string list2String(list x) { string s = ""; integer len = llGetListLength(x); integer i; for(i = 0; i < len; i++) { integer t = llGetListEntryType(x,i); if(t == TYPE_STRING) s += llList2String(x,i); else if(t == TYPE_INTEGER) s += (string)llList2Integer(x,i); else if(t == TYPE_FLOAT) s += (string)llList2Float(x,i); else if(t == TYPE_FLOAT) s += (string)llList2Float(x,i); else if(t == TYPE_VECTOR) s += (string)llList2Vector(x,i); else if(t == TYPE_ROTATION) s += (string)llList2Rot(x,i); else s += "(item " + (string)i + ")"; } return s; } list COLORNAME2COLOR = [ "", // so we can use "0" to mean not a color, 1,3,5,7... are color indices. "red",<1,0,0>, "pink",<1,0.7,0.7>, "orange",<1,.5,0>, "yellow",<1,1,0>, "green",<0,1,0>, "blue",<0,0,1>, "purple",<0.8,0,1>, "white",<1,1,1>, "sodium",<1,0.8,0>, "fluorescent",<0.75,0.75,1>, "incandescent",<1,1,0.75>, "black",<0.5,0,1.0> ]; // return a color index (odd integer) or 0 of its not a known color name. integer name2ColorIndex(string colorName) { integer index = llListFindList(COLORNAME2COLOR,[llToLower(colorName)]); if((index & 1) == 0) // color name indices are all odd index = 0; else if(index < 0) index = 0; else index++; // bump forward to the color vector index. return index; } // Handles the commonest colors, or vector name2Color(string colorName) { vector color; integer colorIndex = name2ColorIndex(colorName); if(colorIndex) color = llList2Vector(COLORNAME2COLOR,colorIndex); else color = (vector)colorName; return color; } string integer2OnOff(integer n) { if(n) return "On"; else return "Off"; } sayBSetting(string setting,integer b) { string v = integer2OnOff(b); llSay(0,setting + ": " + v); } sayVSetting(string setting,vector v) { llSay(0,list2String([setting,": ",v])); } sayNSetting(string setting,integer v) { llSay(0,list2String([setting,": ",v])); } saySSetting(string setting,string v) { llSay(0,list2String([setting,": ",v])); } showStatus() { llSleep(llFrand(3)); // to stagger responses sayBSetting("State",gOnOff); saySSetting("Mode",getLampModeString()); sayBSetting("(Mode active)",gAutoActive); sayVSetting("Color",gColor); sayNSetting("Uptime",(integer)llGetTime()); sayNSetting("Commands Received",gCommandsEverReceived); sayNSetting("Memory",llGetFreeMemory()); if(gSunZ) sayNSetting("Sun Z Override",gSunZ); } integer gSunZ = 0; // 1->1, -1->-1, 0-> actual sun Z. float getSunZ() { float sunZ = gSunZ; if(!gSunZ) { vector sunV = llGetSunDirection(); sunZ = sunV.z; } return sunZ; } // Set the global night-flag. Should be called // as part of an event-handler and nowhere else. // we read/set it once to avoid edge-conditions // where the nightness changes during processing. // ALSO: if day <--> night, reset the automatic mode getIsNight() { integer wasNight = gIsNight; float sunZ = getSunZ(); if(sunZ < 0.0) gIsNight = TRUE; else gIsNight = FALSE; // did the day change? if(gIsNight != wasNight) gAutoActive = gAuto; // reassert auto mode } // turn auto onor off.. setLampAuto(integer auto) { gAuto = auto; gAutoActive = gAuto; // reassert effective auto mode, too if(gAutoActive) { assertDayNightAutoState(); // and immediately make sure the light is proper. } } string getLampModeString() { if(gAuto) return "Automatic"; else return "Manual"; } setLampState(integer onOff,integer userLevel) { d(["setLampState(",onOff,",",userLevel,")"]); if(userLevel == OWNER) { integer oldOnOff = gOnOff; gOnOff = onOff; gAutoActive = FALSE; // turn off auto til next sunrise/sunset // set the flippy toggles... which only matter if gAuto is on. // the flippy toggles (?cleverly?) prevent the automatic // on-at-dusk from immediately firing after you turn off the light, at night. // if we are changing state, then assert it if(gOnOff != oldOnOff) setLampAppearance(gOnOff,gAppearanceColor); } else { gGuestOnOff = onOff; gGuestTime = gGuestTimeout; // for guest, just assert the appearance like this setLampAppearance(onOff,gAppearanceColor); } } setLampColor(string colorName,integer userLevel) { vector color = name2Color(colorName); d(["setLampColor(",color,",",userLevel,")"]); if(userLevel != OWNER) { gGuestColor = color; gGuestTime = gGuestTimeout; } else { gColor = color; } // either way, present it. setLampAppearance(gAppearanceOnOff,color); } // do the actual materials/colors change, // applies to owner/guest transition, dusk, all of em. integer gAppearanceOnOff = 0; vector gAppearanceColor = <0,0,0>; setLampAppearance(integer onOff,vector color) { gAppearanceOnOff = onOff; gAppearanceColor = color; link(["state",color,onOff]); // pass it on to the bulbs } integer getUserLevel(key av) { if(av == llGetOwner()) return OWNER; // its a guest. what's the guest access level? if(gGuestTimeout == 0) // no access... return GUEST_NO_ACCESS; return GUEST_TIMED_ACCESS; } integer xcheckGuest(key av) { integer isGuest = (av != llGetOwner()); // isGuest = 1; return isGuest; } resumeFromGuestState() { setLampAppearance(gOnOff,gColor); // reassert main settings. gGuestTime = -1; } // All commands (link or chat) come through here doCommand(key av,string message) { gCommandsEverReceived++; // noListen(); // just accept the one command on dialog channel. // allow commands on this channel for a while, easier for debuggings. // even: reset the timeout! gChatTime = gChatTimeout; integer userLevel = getUserLevel(av); d(["userLevel: ",userLevel]); if(userLevel == GUEST_NO_ACCESS) return; if(message == "") { doDialog(av); return; } // If the command starts with a dot, then bring up the dialog (again) after. integer dialogAfter = 0; if(llGetSubString(message,0,0) == ".") { dialogAfter = 1; message = llGetSubString(message,1,-1); } list messageL = llParseString2List(message,[" "],[]); string cmd = llToLower(llList2String(messageL,0)); integer arg1 = llList2Integer(messageL,1); string arg1S = llList2String(messageL,1); integer arg1OnOff = 0; if(llToLower(arg1S) == "on" || ((integer)arg1S > 0) || arg1S == "" ) arg1OnOff = 1; integer handledMessage = 1; // innocent til guilty if(cmd == "color") { setLampColor(arg1S,userLevel); } else if(cmd == "light") { d(["light: ",arg1OnOff]); setLampState(arg1OnOff,userLevel); } else if(cmd == "on") { setLampState(1,userLevel); } else if(cmd == "off") { setLampState(0,userLevel); } else if(cmd == "resume") { resumeFromGuestState(); } else if(cmd == "status") { showStatus(); } else if(name2ColorIndex(cmd)) { setLampColor(cmd,userLevel); } else if(cmd == "colors...") { doColorOptions(av,userLevel); } else if(cmd == "main...") { doDialog(av); } else if(cmd == "more...") { doMoreOptions(av,userLevel); } else if(userLevel == OWNER) { // the rest of these commands are for authorized only. // guests can do only a little. if(cmd == "auto") { setLampAuto(arg1OnOff); } else if(cmd == "automatic") { setLampAuto(1); } else if(cmd == "manual") { setLampAuto(0); } else if(cmd == "guest") { if(arg1OnOff) { if(arg1 > 0) gGuestTimeout = arg1; // some-number else gGuestTimeout = -1; // "on" } else gGuestTimeout = 0; // "off" or 0 } else if(cmd == "link") { // send link message direct (for debug) or parse as commands link(llList2List(messageL,1,-1)); return; } else if(cmd == "sunz") // for debugging purposes only { gSunZ = arg1; } else if(cmd == "done") noListen(); else if(cmd == "debug") { gDebug = arg1OnOff; link(["debug",gDebug]); // pass debug message down to linked fellas } } if(dialogAfter) doDialog(av); } string getStatusString() { string s = "Current mode: " + getLampModeString(); s += "\nCurrent state: " + integer2OnOff(gOnOff); if(gAuto) { if(gOnOff) s += " (until next sunrise)"; else s += " (until next sunset)"; } s += "\nGuest control: "; if(gGuestTimeout > 0) s += (string)gGuestTimeout + " seconds"; else if(gGuestTimeout < 0) s += "On"; else s += "Off"; return s; } string getCreditString() { return"v" + gVersion + " by Davan Camus"; } string getDialogString(integer userLevel) { string s = "\"" + llGetObjectName() + "\" Control"; s += "\n\n" + getStatusString() + "\n\n" + getCreditString(); if(userLevel == OWNER) s += " (" + (string)gDialogCommandChannel + ")"; return s; } doColorOptions(key av,integer userLevel) { list choices = [ "Red", "Orange", "Yellow", "Green", "Blue", "Pink", "Sodium", "Fluorescent", "Incandescent", "White", "Black", "Main..." ]; string s = getDialogString(userLevel); llDialog(av,s,choices,gDialogCommandChannel); // a short-lived channel number } doMoreOptions(key av,integer userLevel) { doListen(); list choices; if(userLevel == OWNER) choices += [ "Guest 90","Guest 240","Guest 600", "Guest Off","Guest 10","Guest 30" ]; choices += ["Main...","Colors..."]; string s = getDialogString(userLevel); llDialog(av,s,choices,gDialogCommandChannel); // a short-lived channel number } doDialog(key av) { doListen(); // turn on the chat listener for the next command. integer userLevel = getUserLevel(av); if(userLevel == GUEST_NO_ACCESS) // no guest access? return; list choices = [ "Fluorescent","Sodium","White", "Blue","Red","Colors..." ]; if(userLevel == OWNER) choices += ["Automatic","Manual","More..."]; choices += ["On","Off"]; string s = getDialogString(userLevel); llDialog(av,s,choices,gDialogCommandChannel); // a short-lived channel number } link(list linkList) { string linkMessage = llDumpList2String(linkList,"++"); if(linkMessage != "") { d(["link message sending: ",linkMessage]); llMessageLinked(LINK_SET,COMMAND_CHANNEL,linkMessage,""); } } integer doListen() { noListen(); // clear out any old one... gDialogCommandChannel = COMMAND_CHANNEL + 100 + (integer)(llFrand(1000)); gDialogListenerNumber = llListen(gDialogCommandChannel,"","",""); gChatTime = gChatTimeout; // start the countdown return gDialogCommandChannel; } noListen() { gChatTime = 0; llListenRemove(gDialogListenerNumber); } init() { gDebug = 0; link(["debug",gDebug]); d(["listening on ",COMMAND_CHANNEL]); llListenRemove(gListenerNumber); gListenerNumber = llListen(COMMAND_CHANNEL,"","",""); setTimerEvent(4.0); noListen(); // disable dialog listener llSetTouchText("Control"); setLampAuto(1); gColor = <1,1,1>; // default white gOnOff = 1; setLampAppearance(gOnOff,gColor); } assertDayNightAutoState() { // just immediately make sure we're up to date on the whole on/off business... // (Did Not Check gAutoActive!) if((gOnOff == 1) && !gIsNight) { setLampState(0,OWNER); } else if((gOnOff == 0) && gIsNight) { setLampState(1,OWNER); } } default { on_rez(integer n) { llResetScript(); } state_entry() { init(); d(["state_entry"]); } touch_start(integer total_number) { getIsNight(); doDialog(llDetectedKey(0)); } listen(integer channel,string name,key id,string message) { getIsNight(); d(["listen (",name," on ",channel,"): ",message]); doCommand(id,message); } timer() { getIsNight(); gTime += gTickSize; // dialog timeout if(gChatTime > 0) { gChatTime -= gTickSize; // if(gChatTime <= 0) noListen(); // no more chat. } // guest-control timeout if(gGuestTime > 0) { gGuestTime -= gTickSize; if(gGuestTime <= 0) resumeFromGuestState(); } else { // dawn/dusk control (but not during guest-control...) if(gAutoActive) { if(llFrand(DUSKDAWNODDS) < 1.0) // to make groups of lights come on not-all-at-once... assertDayNightAutoState(); } } } } // end of file