// Library of generic functions for other scripts to use. string Version = "1chatter v0.14 test version"; // BEGIN boilerplate. integer DEBUG = FALSE; float Start; string ScriptName; key ScriptKey; key LibraryKey; key Owner; string URL; key grid; list Commands; integer cNAME = 0; integer cARGS = 1; integer cAUTH = 2; integer cSTRIDE = 3; list Scanners = []; // Current set of scans. integer nMENU = 0; // Menu that started the scan. integer nID = 1; // Avatar UUID we are doing this scan for. integer nTYPE = 2; // The type of scan. integer nTITLE = 3; // Title of the menu to show. integer nCMD = 4; // Command to run with the result. integer nSTRIDE = 5; // utilities commands, "l"ibrary. string lSEP = "$!#"; // Used to seperate lists when sending them as strings. integer lRESET = -1; integer lRESET_DONE = -2; integer lALIAS = -3; integer lALIAS_DONE = -4; integer lSETTING = -5; integer lSETTING_DONE = -6; integer lSUBSTITUTE = -7; integer lSUBSTITUTE_DONE = -8; integer lNEXT_WORD = -9; integer lNEXT_WORD_DONE = -10; integer lCHECK = -11; integer lCHECK_DONE = -12; integer lCONTROL = -13; integer lCONTROL_DONE = -14; integer lCMD = -15; integer lCMD_DONE = -16; integer lSETTINGS = -17; integer lSETTINGS_DONE = -18; integer lSCAN = -19; integer lDYNAMIC = -20; integer lMENU = -21; d(string m) {if (DEBUG) llInstantMessage(Owner, llGetScriptName() + ": " + m);} D(string m) {llRegionSay(DEBUG_CHANNEL, llGetScriptName() + ": " + m);} s(string m) {s(Owner, m);} s(key id, string m) {if (id == Owner) llOwnerSay(m); else llInstantMessage(id, m);} S(string m) {llInstantMessage(Owner, llGetScriptName() + ": " + m);} sendScript(integer cmd, list args) {sendScript(LibraryKey, cmd, ScriptName, args);} sendScript(key them, integer cmd, list args) {sendScript(them, cmd, inKey2Name(them), args);} sendScript(integer cmd, string name, list args) {sendScript(LibraryKey, cmd, name, args);} sendScript(key them, integer cmd, string name, list args) { llMessageLinked(LINK_SET, cmd, llDumpList2String([ScriptKey, name] + args, lSEP), them); } integer pingPrim(key k) { if (NULL_KEY != k) return (0 != llGetListLength(llGetObjectDetails(k, [OBJECT_DESC]))); return FALSE; } sendPrim(key them, string cmd, list args) {if (pingPrim(them)) osMessageObject(them, llDumpList2String([ScriptName] + args, lSEP));} string inKey2Name(key k) { if (k == LibraryKey) return "1chatter"; integer i = llGetInventoryNumber(INVENTORY_SCRIPT); while (i-- > 0) { string n = llGetInventoryName(INVENTORY_SCRIPT, i); if (llGetInventoryKey(n) == k) return n; } return k; } integer uSubStringLastIndex(string hay, string pin) { integer i2 = -1; integer i; if (pin == "") return 0; while (~i) { i = llSubStringIndex(llGetSubString(hay, ++i2, -1), pin); i2 += i; } return i2; } integer listFindString(list lst, string name, integer stride) { integer f = llListFindList(lst, [name]); integer ix = f / stride; // Round to nearest stride. ix = ix * stride; // Sanity check, make sure we found a name, not something else, else do it the slow way. if ((-1 != f) && (ix != f)) { integer l = llGetListLength(lst); integer i; f = -1; for (i = 0; i < l; i += stride) { if (llList2String(lst, i) == name) { f = i; i = l; } } } return f; } string getFor(string name) { string fr; integer i = llSubStringIndex(name, "."); if (-1 != i) fr = llGetSubString(name, 0, i); return fr; } string noFor(string name) { string r = name; integer i = llSubStringIndex(name, "."); if (-1 != i) r = llGetSubString(name, i + 1, -1); return r; } list validateName(string var, string type) { list v = llParseStringKeepNulls(var, ["."], []); if (2 != llGetListLength(v)) { d("Invalid " + type + " name - " + var); return ["", var]; } var = llList2String(v, 1); if ("setting" == type) var = llToUpper(var); return [llList2String(v, 0) + ".", var]; } list Aliases; integer aALIAS = 0; integer aNAME = 1; integer aSTRIDE = 2; string alias(string n) { integer a = listFindString(Aliases, llToLower(n), 2); if (-1 != a) n = llList2String(Aliases, a + 1); return n; } // Access "f"rom some source - integer fINT = 0; // Internal, not sure we need this, means the same script. integer fCARD = 1; // Read from a note card. integer fCHAT = 2; // Chat channel. integer fLINK = 3; // Link message. integer fMENU = 4; // Menu, in reality this is just a chat channel. integer fRLV = 5; // RLV, again from a chat channel. integer fRELAY = 6; // RLV relay. integer fOSM = 7; // osMessageObject() integer fHTTP = 8; // From the web. // LSL security is entirely hopeless, see the notecard "1ring security". // "a"ccess level - integer aNONE = 0; integer aBOSS = 1; integer aTRUST = 2; integer aGROUP = 4; integer aBOSSES = 3; integer aWEARER = 8; integer aPRIV = 9; integer aMORE = 11; integer aMOST = 15; integer aPUBLIC = 16; integer aALL = 31; list Access = [ "NONE", 0, "BOSS", 1, "TRUST", 2, "GROUP", 4, "BOSSES", 3, "WEARER", 8, "PRIV", 9, "MORE", 11, "MOST", 15, "PUBLIC", 16, "ALL", 31 ]; integer decodeAccess(string a) { integer r = aALL; integer f = listFindString(Access, a, 2); if (-1 != f) r = llList2Integer(Access, f + 1); return r; } integer access(key id, string what, string fr, integer auth, integer bitch) { list bosses = llParseString2List(getSetting(fr + "BOSS"), [","], []); integer l = llGetListLength(bosses); integer j = aPUBLIC; integer i; integer b = FALSE; //d("access " + id + " " + what + " FOR " + fr + " " + auth); if (id == Owner) j = j | aWEARER | aGROUP; if (llSameGroup(id)) j = j | aGROUP; if ("1" == getSetting(fr + "PUBLIC")) j = j | aTRUST; for (i = 0; i < l; ++i) { if (id == llList2Key(bosses, i)) { j = j | aBOSS | aTRUST; i = l; } } bosses = llParseString2List(getSetting(fr + "TRUSTEE"), [","], []); l = llGetListLength(bosses); for (i = 0; i < l; ++i) { if (id == llList2Key(bosses, i)) { j = j | aTRUST; i = l; } } bosses = llParseString2List(getSetting(fr + "BLOCKED"), [","], []); l = llGetListLength(bosses); for (i = 0; i < l; ++i) { if (id == llList2Key(bosses, i)) { b = TRUE; j = 0; i = l; } } j = auth & j; if (bitch && (0 == j)) { if (b) { s(Owner, "Blocked user " + llKey2Name(id) + " failed to use this."); s(id, "You are blocked from using this."); } else { string who; if (aNONE == auth) who = "no "; if (auth & aWEARER) who += "'" + llKey2Name(llGetKey()) + "' owner, "; if (auth & aBOSS) who += noFor(alias(fr + "boss")) + ", "; if (auth & aTRUST) who += noFor(alias(fr + "trusted")) + ", "; if (auth & aGROUP) who += "group, "; if (auth & aPUBLIC) who += "public, "; if (", " == llGetSubString(who, -2, -1)) who = llGetSubString(who, 0, -3) + " "; i = uSubStringLastIndex(who, ", "); if (-1 != i) who = llGetSubString(who, 0, i + 1) + "and " + llGetSubString(who, i + 2, -1); s(id, "The " + what + " is allowed only for " + who + "users."); } } return j; } string TRUEs = "t1aopswy"; string FALSEs = "f0bgnuz"; integer isBool(string b) { integer r = FALSE; if ("" != b) r = (-1 != llSubStringIndex(TRUEs, llToLower(llGetSubString(b, 0, 0)))); return r; } // "s"ettings list - list Settings; integer sNAME = 0; integer sTYPE = 1; integer sVALUE = 2; integer sAUTH = 3; integer sSTRIDE = 4; string getSetting(string var) { list v = validateName(var, "setting"); var = llList2String(v, 0) + llList2String(v, 1); string result; integer f = listFindString(Settings, var, sSTRIDE); if (-1 != f) result = llList2String(Settings, f + sVALUE); return result; } integer setSetting(key id, string var, string val, integer source) { list v = validateName(var, "setting"); string fr = llList2String(v, 0); var = fr + llList2String(v, 1); integer f = listFindString(Settings, var, sSTRIDE); if (-1 != f) { integer acs = access(id, var + " setting", fr, llList2Integer(Settings, f + sAUTH), TRUE); if (0 == acs) return acs; string type = llToUpper(llList2String(Settings, f + sTYPE)); if ("Y" == type) val = (string) isBool(val); Settings = llListReplaceList(Settings, [val], f + sVALUE, f + sVALUE); //d("setSetting " + var + " = " + val); if (fCARD != source) { string card = "." + llGetSubString(fr, 0, -2) + ".settings"; list data; if (NULL_KEY != llGetInventoryKey(card)) data = llParseString2List(osGetNotecard(card), ["\n"], []); integer l = llGetListLength(data); string ar = llList2String(v, 1); // TODO - could use a bit more sophistication here, but it works as is. for (f = 0; f < l; ++f) { list p = llParseString2List(llList2String(data, f), ["="], []); string k = llList2String(p, 0); if (k == var) { data = llListReplaceList(data, [k + "=" + val], f, f); f = l + 1; } if (k == ar) { data = llListReplaceList(data, [k + "=" + val], f, f); f = l + 1; } } if (f == l) data += [ar + "=" + val]; llRemoveInventory(card); osMakeNotecard(card, data); d("setSetting " + var + " saved to " + card); } // Hopefully this wont result in any recursive setSettings calls. doThing(id, "SET " + var + "=" + val, fr, llList2String(v, 1), val, source); return acs; } return 0; } // TODO - Hmmm, only called once. readSettings(string card) { float now = llGetTimeOfDay(); key boss = llGetOwner(); string fr = card + "."; card = "." + card + ".settings"; if (NULL_KEY != llGetInventoryKey(card)) { list d = llParseString2List(osGetNotecard(card), ["\n"], []); integer l = llGetListLength(d); integer i; for (i = 0; i < l; ++i) { string cmd = alias(llList2String(d, i)); integer j = llSubStringIndex(cmd, "="); if (-1 == j) j = llSubStringIndex(cmd, " "); if (-1 == j) j = llStringLength(cmd); string f = fr; string var = llStringTrim(llGetSubString(cmd, 0, j - 1), STRING_TRIM); if (-1 != llSubStringIndex(var, ".")) f = ""; setSetting(boss, f + var, llStringTrim(llGetSubString(cmd, j + 1, -1), STRING_TRIM), fCARD); } } else setSetting(boss, fr + "BOSS", Owner, fCARD); // d("Read " + card + " in " + (string) (llGetTimeOfDay() - now) + " seconds."); } dumpAliases(list s, string title) { integer l = llGetListLength(s); integer i; d("v--------------- " + title); for (i = 0; i < l; i += aSTRIDE) { d( llList2String(s, i + aALIAS) + "~" + llList2String(s, i + aNAME) ); } d("^---------------- LIBRARY"); } dumpCommands(list s, string title) { integer l = llGetListLength(s); integer i; d("v--------------- " + title); for (i = 0; i < l; i += cSTRIDE) { d( llList2String(s, i + cNAME) + "~" + llList2String(s, i + cARGS) + "~" + llList2String(s, i + cAUTH) ); } d("^---------------- LIBRARY"); } dumpSettings(list s, string title) { integer l = llGetListLength(s); integer i; d("v--------------- " + title); for (i = 0; i < l; i += sSTRIDE) { d( llList2String(s, i + sNAME) + "~" + llList2String(s, i + sTYPE) + "~" + llList2String(s, i + sVALUE) + "~" + llList2String(s, i + sAUTH) ); } d("^---------------- LIBRARY"); } // "e"vents float NextEvent = 307584000.0; // Ten years. list Events = []; integer eTIME = 0; // Time of it's next event. integer eSCRIPT = 1; // Rest of scriptlet after the delay command. integer eKEY = 2; // Key of script asking for this timer. integer eSTRIDE = 3; addEvent(float delay, string cmds, key id) { float now = llGetTimeOfDay(); list ev = [now + delay, cmds, id]; integer f = findEventByID(cmds); if (0.0 >= delay) { if (-1 != f) Events = llListReplaceList(Events, [], f, f + eSTRIDE - 1); } else if (-1 == f) Events += ev; else Events = llListReplaceList(Events, ev, f, f + eSTRIDE - 1); Events = llListSort(Events, eSTRIDE, TRUE); if (llList2Float(Events, 0) < NextEvent) { NextEvent = llList2Float(Events, 0); llSetTimerEvent(NextEvent - now); } } integer findEventByID(string id) { integer f = llListFindList(Events, [id]); integer ix = f / eSTRIDE; // Round to nearest stride. ix = ix * eSTRIDE; if ((-1 != f) && ((ix + eSCRIPT) == f)) return f - eSCRIPT; else return -1; } // For marking up menu buttons. string Enclosed = "ⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ⓪⓵⓶⓷⓸⓹⓺⓻⓼⓽ⒶⒷⒸⒹⒺⒻⒼⒽⒾⒿⓀⓁⓂⓃⓄⓅⓆⓇⓈⓉⓊⓋⓌⓍⓎⓏ"; string DownsideUp = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; string UpsideDown = "ɐqɔpǝɟɓɥᴉſʞןɯuodbɹsʇnʌʍxʎz0lᘔƐᔭ59Ɫ86∀qↃpƎℲ⅁HIᒋʞ⅂ꟽNOԀÒᴚS⊥∩ɅMX⅄Z"; string enclosed(string text) { string result; integer l = llStringLength(text); integer i; for (i = 0; i < l; ++i) { string c = llGetSubString(text, i, i); integer f = llSubStringIndex(DownsideUp, c); if (-1 != f) c = llGetSubString(Enclosed, f, f); result += c; } return result; } string upsideDown(string text) { string result; integer i = llStringLength(text) - 1; for (; i > -1; --i) { string c = llGetSubString(text, i, i); integer f = llSubStringIndex(DownsideUp, c); if (-1 != f) c = llGetSubString(UpsideDown, f, f); result += c; } return result; } // "m"enus string MainMenu; list Menus; // List of menus. integer mNAME = 0; // Name of menu. integer mTITLE = 1; // Title for the menu. integer mAUTH = 2; // Authorised users of menu - 0 = wearer, 1 = boss, 2 = all integer mENTRIES = 3; // | separated list of entries. integer mCMDS = 4; // | separated list of commands, matching the entries. integer mDYN = 5; // Dynamic menu, autoremove it when done. integer mSTRIDE = 6; integer findMenu(string name) { return listFindString(Menus, name, mSTRIDE); } saveMenu(string name, string title, integer auth, string entries, string commands, integer dyn) { integer f = findMenu(name); if (-1 == f) Menus += [name, title, auth, entries, commands, dyn]; else { if ("" == title) title = llList2String(Menus, f + mTITLE); if ("" == entries) entries = llList2String(Menus, f + mENTRIES); if ("" == commands) commands = llList2String(Menus, f + mCMDS); Menus = llListReplaceList(Menus, [name, title, auth, entries, commands, dyn], f, f + mSTRIDE - 1); } } addMenuItem(string fr, string menu, string cmd, list args) { integer f = findMenu(menu); if (-1 != f) { string e = llList2String(Menus, f + mENTRIES); string c = llList2String(Menus, f + mCMDS); if (llGetListLength(args) < 2) args += [" "]; if ("" != e) e += "|"; if ("" != c) c += "|"; if ("TOGGLE" == cmd) e += getToggle(fr, llList2String(args, 0)); else e += llList2String(args, 0); if ("TOMENU" == cmd) e += "…"; else if ("TOGGLE" == cmd) { string cm = llList2String(args, 0); if (-1 == listFindString(Settings, fr + llToUpper(cm), sSTRIDE)) d("ERROR! Not found setting " + fr + cm + "!"); } else if ("BUTTON" == cmd) { string cm = llList2String(args, 1); if (-1 == listFindString(Commands, fr + llToUpper(cm), cSTRIDE)) d("ERROR! Not found command " + fr + " " + cm + "!"); else { sendScript(getSetting(fr + "SCRIPTKEY"), lCHECK, fr, [fCARD, LibraryKey, menu, cm, llDumpList2String(llListReplaceList(args, [], 0, 1), "|")]); } } else { if (-1 == listFindString(Commands, fr + llToUpper(cmd), cSTRIDE)) d("ERROR! Not found command " + fr + cmd + "!"); else { sendScript(getSetting(fr + "SCRIPTKEY"), lCHECK, fr, [fCARD, LibraryKey, menu, cmd, llDumpList2String(llListReplaceList(args, [], 0, 0), "|")]); } } c += llDumpList2String(llListReplaceList(args, [], 0, 0), " "); saveMenu(menu, llList2String(Menus, f + mTITLE), llList2Integer(Menus, f + mAUTH), e, c, llList2Integer(Menus, f + mDYN)); } } dynamicMenu(key id, string menu, string name, string title, string entries, string command, integer ret) { //d("dynamicMenu " + menu + " -> " + name + "\n" + id + "@" + name); saveMenu(id + "@" + name, title, aALL, entries, command, ret); saveMuser(id, id + "@" + name, entries, -1, menu); showMenu(id); } string getToggle(string fr, string item) { string result = getSetting(fr + item); if ("1" != result) result = "☐"; else result = "▣"; return result + " " + item; } toggleMenu(key id, string fr, list entries, integer e, integer m) { integer on = 0; string this = llList2String(entries, e); string result = llStringTrim(llGetSubString(this, 1, -1), STRING_TRIM); string t = llGetSubString(this, 0, 0); if ("☐" == t) {t = "▣"; on = 1;} else if ("▣" == t) {t = "☐"; on = 0;} else if ("○" == t) {t = "◉"; on = 1;} else if ("◉" == t) {t = "○"; on = 0;} else {s(id, "Not a menu toggle - " + result); return;} if (0 == setSetting(id, fr + result, (string) on, fMENU)) return; //d("toggleMenu " + result + " " + on); // TODO - if it's a radio group, make sure one and only on is turned on. entries = llListReplaceList(entries, [t + " " + result], e, e); saveMenu( llList2String(Menus, m + mNAME), llList2String(Menus, m + mTITLE), llList2Integer(Menus, m + mAUTH), llDumpList2String(entries, "|"), llList2String(Menus, m + mCMDS), llList2Integer(Menus, m + mDYN)); } // Menu "u"sers list Musers; // List of menu users. integer uKEY = 0; // Key of user. integer uCHAN = 1; // Listen channel for user. integer uLSTN = 2; // Listen handle. integer uCURRENT = 3; // Current menu of user. integer uENTRIES = 4; // Current entries for currently in flight menu. integer uPAGE = 5; // Current page of long menu of user. integer uSTACK = 6; // | separated menu stack list. integer uTIME = 7; // Timestamp of last menu shown. integer uSTRIDE = 8; // page = -1 means the menu items fit on a single dialog, otherwise it's the page of items. saveMuser(key avatar, string current, string entries, integer page, string stack) { integer f = listFindString(Musers, avatar, uSTRIDE); float n = llGetTimeOfDay(); //d("saveMuser " + f + " " + llKey2Name(avatar) + " = " + current + " ~ " + stack + "\n" + entries); if (-1 == f) { stack = "|"; integer c = (integer) ("0x" + llGetSubString((string) avatar, -4, -1)) + (integer) llFrand(12345); integer h = llListen(c, "", avatar, ""); Musers += [avatar, c, h, current, entries, page, stack, n]; } else { string old = llList2String(Musers, f + uSTACK); if ("-1" == stack) stack = old; else if ("-1" == current) { list lst = llParseStringKeepNulls(old, ["|"], []); current = llList2String(lst, 0); lst = llListReplaceList(lst, [], 0, 0); if (1 == llGetListLength(lst)) { llListenRemove(llList2Integer(Musers, f + uLSTN)); Musers = llListReplaceList(Musers, [], f, f + uSTRIDE - 1); return; } stack = llDumpList2String(lst, "|"); entries = ""; } else if (-1 != page) stack = old; else if ("" == stack) ; else if ("" != old) stack += "|" + old; Musers = llListReplaceList(Musers, [avatar, llList2Integer(Musers, f + uCHAN), llList2Integer(Musers, f + uLSTN), current, entries, page, stack, n], f, f + uSTRIDE - 1); } //d("saveMu2er " + avatar + " = " + current + " ~ " + stack); } removeMenu(string r) { integer f = listFindString(Menus, r, mSTRIDE); if (-1 != f) Menus = llListReplaceList(Menus, [], f, f + mSTRIDE - 1); else D("removeMenu() " + r + " not found!"); } lastMenu(key id, string r) { saveMuser(id, "-1", "", -1, ""); if ("" != r) removeMenu(r); } dumpMenuUsers(list menus) { integer l = llGetListLength(menus); integer i; for (i = 0; i < l; i += uSTRIDE) { s( llList2String(menus, i + uKEY) + "~" + llList2String(menus, i + uTIME) + "~" + llList2String(menus, i + uCURRENT) ); } } dumpMenus(list menus) { integer l = llGetListLength(menus); integer i; for (i = 0; i < l; i += mSTRIDE) { d( llList2String(menus, i + mNAME) + "~" + llList2String(menus, i + mTITLE) + "~" + llList2String(menus, i + mAUTH) + "~" + llList2String(menus, i + mENTRIES) + "~" + llList2String(menus, i + mCMDS) + "~" + llList2String(menus, i + mDYN) ); } } showMenu(key id) { integer f = listFindString(Musers, id, uSTRIDE); if (-1 != f) { string menu = llList2String (Musers, f + uCURRENT); integer chan = llList2Integer(Musers, f + uCHAN); integer page = llList2Integer(Musers, f + uPAGE); string fr; if ("" != menu) { list v = validateName(menu, "menu"); fr = llList2String(v, 0); menu = fr + llList2String(v, 1); } integer m = findMenu(menu); //d("showMenu " + menu + "... " + fr); if (-1 != m) { integer dM = llList2Integer(Menus, m + mDYN); if (dM) fr = llGetSubString(fr, 37, -1); string version = getSetting(fr + "VERSION"); list entries = llParseStringKeepNulls(llList2String(Menus, m + mENTRIES), ["|"], []); list cmds = llParseStringKeepNulls(llList2String(Menus, m + mCMDS), ["|"], []); string title = llList2String(Menus, m + mTITLE) + "\n\n---\n"; if (access(id, menu + " menu", fr, llList2Integer(Menus, m + mAUTH), TRUE)) { integer l = llGetListLength(entries); integer n; if (dM) { for (; n < l; ++n) { string button = llGetSubString(llList2String(entries, n), 0, 23); title += button + "\n"; entries = llListReplaceList(entries, [button], n, n); } n = l; } // Show missing items as upside down text. // Show no access items enclosed. for (; n < l; ++n) { string button = llGetSubString(llList2String(entries, n), 0, 23); string first = llGetSubString(button, 0, 0); string last = llGetSubString(button, -1, -1); entries = llListReplaceList(entries, [button], n, n); if (("▲" == first) || ("◄" == first) || ("►" == last)) { } else if ("…" == last) { string t = button; button = llGetSubString(button, 0, -2); integer o = findMenu(fr + button); if (" " == llList2String(cmds, n)) { if (-1 == o) t = upsideDown(last + button); else if (!access(id, t, fr, llList2Integer(Menus, o + mAUTH), FALSE)) t = enclosed(button + last); else title += t + "\n"; entries = llListReplaceList(entries, [t], n, n); } else title += t + "\n"; } else if (("☐" == first) || ("▣" == first) || ("○" == first) || ("◉" == first)) { button = llStringTrim(llGetSubString(button, 1, -1), STRING_TRIM); string t = getToggle(fr, button); integer o = listFindString(Settings, fr + llToUpper(button), sSTRIDE); if (-1 == o) t = upsideDown(button + " " + llGetSubString(t, 0, 0)); else if (!access(id, t, fr, llList2Integer(Settings, o + sAUTH), FALSE)) t = enclosed(t); else title += t + "\n"; entries = llListReplaceList(entries, [t], n, n); } else { string frr = fr; string t = button; string c = llList2String(cmds, n); integer o = llSubStringIndex(c, " "); if (-1 != o) c = llGetSubString(c, 0, o - 1); o = llSubStringIndex(c, "."); if (-1 != o) { frr = llGetSubString(c, 0, o); c = llGetSubString(c, o + 1, -1); } o = listFindString(Commands, frr + llToUpper(c), cSTRIDE); if (("" == c) || (-1 == o)) t = upsideDown(t); else if (!access(id, c, frr, llList2Integer(Commands, o + cAUTH), FALSE)) t = enclosed(t); else title += t + "\n"; entries = llListReplaceList(entries, [t], n, n); } } string mn = menu; if (dM) mn = llGetSubString(mn, 37, -1); title = llGetSubString(version + " - " + mn + "\n\n" + title + "\n---\n", 0, 511); if (l > 11) { if (-1 == page) page = 1; integer offset = page * 9; llDialog(id, title, ["◄ Previous", "▲ Exit", "Next ►"] + llList2List(entries, offset-3, offset-1) + llList2List(entries, offset-6, offset-4) + llList2List(entries, offset-9, offset-7), chan); } else { llDialog(id, title, llList2List(entries, -2, -1) + ["▲ Exit"] + llList2List(entries, -5, -3) + llList2List(entries, -8, -6) + llList2List(entries, -11, -9), chan); } saveMuser(id, menu, llDumpList2String(entries, "|"), page, "-1"); } } else if ("" != menu) { d("'" + menu + "' menu not found!"); if (DEBUG) dumpMenus(Menus); } else { llListenRemove(llList2Integer(Musers, f + uLSTN)); Musers = llListReplaceList(Musers, [], f, f + uSTRIDE - 1); } } } // return TRUE if caller should showMenu() integer handleMenu(integer f, key id, string button) { string cmd = button; string data; string menu = llList2String(Musers, f + uCURRENT); string dM; string fr; list entries = llParseStringKeepNulls(llList2String(Musers, f + uENTRIES), ["|"], []); list lst; integer page = llList2Integer(Musers, f + uPAGE); integer m = findMenu(menu); // This was already checked before it was stuffed into Musers. integer dm = llList2Integer(Menus, m + mDYN); integer e = 0; //d("handleMenu(" + llKey2Name(id) + "," + button + ") " + menu + " -> " + cmd); if (dm) dM = menu; // Find the corresponding command for this button. lst = llParseStringKeepNulls(llList2String(Menus, m + mCMDS), ["|"], []); if (("◄ Previous" != button) && ("▲ Exit" != button) && ("Next ►" != button)) { if (llGetListLength(lst) != 1) e = llListFindList(entries, [button]); if (-1 == e) D("handleMenu() button |" + button + "| not found, OOPS! Was looking in -\n" + llList2String(Musers, f + uENTRIES)); else cmd = llList2String(lst, e); } f = listFindString(Aliases, llToLower(cmd), 2); if (-1 != f) cmd = llList2String(Aliases, f + 1); f = llSubStringIndex(cmd, "="); if (-1 != f) { data = llGetSubString(cmd, f + 1, -1); cmd = llGetSubString(cmd, 0, f - 1); } else { f = llSubStringIndex(cmd, " "); if (-1 != f) { data = llGetSubString(cmd, f + 1, -1); cmd = llGetSubString(cmd, 0, f - 1); } } f = llSubStringIndex(cmd, "."); if (-1 != f) { fr = llGetSubString(cmd, 0, f); cmd = llGetSubString(cmd, f + 1, -1); } else { f = llSubStringIndex(menu, "."); if (-1 != f) { fr = llGetSubString(menu, 0, f); f = llSubStringIndex(fr, "@"); if (-1 != f) fr = llGetSubString(fr, f + 1, -1); } } cmd = llToUpper(llStringTrim(cmd, STRING_TRIM)); data = llStringTrim(data, STRING_TRIM); if (!access(id, menu + " menu", fr, llList2Integer(Menus, m + mAUTH), TRUE)) return FALSE; string first = llGetSubString(button, 0, 0); string last = llGetSubString(button, -1, -1); d("handleMenu(" + llKey2Name(id) + "," + button + ") " + menu + " -> " + fr + cmd + " -> " + data); // Check if it's a special menu item type. // If this is a TOMENU entry and there was no command if (("…" == last) && (" " == llList2String(lst, e))) { data = llGetSubString(button, 0, -2); f = findMenu(fr + data); if (-1 != f) saveMuser(id, fr + data, "", -1, menu); } else if ("▲" == first) { f = doThing(id, menu + "->" + button, fr, cmd, data, fMENU); lastMenu(id, dM); return f; } else if ("◄" == first) { --page; if (0 > page) page = llGetListLength(entries) / 9; saveMuser(id, menu, "", page, ""); } else if ("►" == last) { ++page; if (llGetListLength(entries) < ((page - 1) * 9)) page = 1; saveMuser(id, menu, "", page, ""); } else if (("☐" == first) || ("▣" == first) || ("○" == first) || ("◉" == first)) { toggleMenu(id, fr, entries, e, m); return FALSE; } else if ("MENU" == cmd) { if ("" == getFor(data)) data = fr + data; saveMuser(id, data, "", -1, menu); } else { integer i = TRUE; f = listFindString(Commands, fr + cmd, cSTRIDE); if (-1 != f) i = doThing(id, menu + "->" + button, fr, cmd, data, fMENU); if ((dm != 2) && ("" != dM)) lastMenu(id, dM); return i; } return TRUE; } integer checkThing(key id, string button, string fr, string cmd, string data, integer source) { if ((fr != (ScriptName + ".") && ("*." != fr))) return FALSE; return TRUE; } integer doThing(key id, string command, string fr, string cmd, string data, integer source) { if ("*.." == fr) return TRUE; // Coz the card reader is about to send the lot anyway. if (fCARD == source) return TRUE; key them = getSetting(fr + "SCRIPTKEY"); //d("doThing(" + id + "," + command + " -> " + fr + " . " + cmd + " |" + data + "|"); if (("" == them) && ("*." != fr)) D("Client script " + llGetSubString(fr, 0, -1) + " not found!\ndoThing(" + id + " does " + command + " - " + fr + " " + cmd + " | " + data + ")"); else { integer f = listFindString(Commands, fr + cmd, cSTRIDE); if (-1 != f) { if (0 == access(id, cmd + " command", fr, llList2Integer(Commands, f + cAUTH), TRUE)) return TRUE; } if ("*." == fr) them = NULL_KEY; if ("TIMER" == cmd) { list dt = llParseString2List(data, [" "], []); float time = llList2Float(dt, 0); dt = llListReplaceList(dt, [], 0, 0); addEvent(time, fr + llDumpList2String(dt, " "), id); } else if ("OHSILLYPROBE" == cmd) llMessageLinked(LINK_SET, DEBUG_CHANNEL, "OHSILLYPROBE", llGetKey()); else { sendScript(them, lCMD, fr, [source, id, command, cmd, data]); return FALSE; } } return TRUE; } // TODO - Could use this from the various "save*()" functions? list saveThing(list in, integer stride, list add, integer l, string fr) { if (l == stride) { string first = llList2String(add, 0); if ("" != fr) { first = fr + first; add = llListReplaceList(add, [first], 0, 0); } integer f = listFindString(in, first, stride); if (-1 == f) in += add; else in = llListReplaceList(in, add, f, f + stride - 1); } else D("Wrong number of arguments, should be " + stride + " but is " + l + "!\n" + llDumpList2String(add, " ~ ")); return in; } readTheme(string card) { float now = llGetTimeOfDay(); string cd = card; card = "~" + cd + ".alias.data"; if (NULL_KEY != llGetInventoryKey(card)) Aliases += llList2List(llParseStringKeepNulls(osGetNotecard(card), ["\n"], []), 0, -2); card = "~" + cd + ".command.data"; if (NULL_KEY != llGetInventoryKey(card)) Commands += llList2List(llParseStringKeepNulls(osGetNotecard(card), ["\n"], []), 0, -2); card = "~" + cd + ".setting.data"; if (NULL_KEY != llGetInventoryKey(card)) Settings += llList2List(llParseStringKeepNulls(osGetNotecard(card), ["\n"], []), 0, -2); // s("Read " + cd + " data in " + (string) (llGetTimeOfDay() - now) + " seconds."); now = llGetTimeOfDay(); card = "." + cd + ".theme"; if (NULL_KEY != llGetInventoryKey(card)) { list data = llParseString2List(osGetNotecard(card), ["\n", ";"], []); integer l = llGetListLength(data); integer i; string fr; string menu; for (i = 0; i < l; ++i) { string line = llStringTrim(llList2String( llParseStringKeepNulls(llList2String(data, i), ["//", "#"], []), 0), STRING_TRIM); if ("" != line) { list args = llParseStringKeepNulls(line, ["|", ","], []); list first; integer m = llGetListLength(args); integer j; for (j = 0; j < m; ++j) first += [llStringTrim(llList2String(args, j), STRING_TRIM)]; args = first; first = llParseStringKeepNulls(llList2String(args, 0), [" ", "="], []); string cmd = llToUpper(llStringTrim(llList2String(first, 0), STRING_TRIM)); first = llListReplaceList(first, [], 0, 0); args = [llDumpList2String(first, " ")] + llListReplaceList(args, [], 0, 0); m = llGetListLength(args); if ("ALIAS" == cmd) { args = llListReplaceList(args, [llToLower(llList2String(args, 0))], 0, 0); Aliases = saveThing(Aliases, aSTRIDE, args, m, fr); } else if ("COMMAND" == cmd) Commands = saveThing(Commands, cSTRIDE, llListReplaceList(args, [decodeAccess(llList2String(args, 2))], cAUTH, cAUTH), m, fr); else if ("SETTING" == cmd) { // TODO - should do isBool() here if needed. Settings = saveThing(Settings, sSTRIDE, llListReplaceList(args, [decodeAccess(llList2String(args, 3))], sAUTH, sAUTH), m, fr); } else if ("FOR" == cmd) { fr = llList2String(args, 0) + "."; menu = ""; } else if ("MAIN" == cmd) MainMenu = fr + llList2String(args, 0); else if ("MENU" == cmd) { menu = llList2String(args, 0); if ("" != fr) menu = fr + menu; saveMenu(menu, llList2String(args, 1), decodeAccess(llList2String(args, 2)), "", "", FALSE); } else if (("BUTTON" == cmd) || ("TOGGLE" == cmd) || ("TOMENU" == cmd)) addMenuItem(fr, menu, cmd, args); else addMenuItem(fr, menu, cmd, [llList2String(args, 0), cmd + " " + llDumpList2String(args, ",")]); } } d("Read " + card + " in " + (string) (llGetTimeOfDay() - now) + " seconds."); } if ("" == MainMenu) MainMenu = llList2String(Menus, 0); } list wipe(list o, string fr, integer stride) { return wipe(o, fr, stride, 0); } list wipe(list o, string fr, integer stride, integer f) { integer l = llGetListLength(o); integer i; list n; for (i = 0; i < l; i += stride) { if ((fr + ".") != getFor(llList2String(o, i + f))) n += llList2List(o, i, i + stride - 1); } return n; } key URLrequestID; key URLCheckrequestID; integer Attached; float VelTime; vector VelPos; init() { Attached = (0 != llGetAttached()); if (Attached) { integer i = llGetInventoryNumber(INVENTORY_SCRIPT); while (i-- > 0) { string s = llGetInventoryName(INVENTORY_SCRIPT, i); if (osGetInventoryDesc(s) == "1chatter client") { d("RESETTING " + s); llSetScriptState(s, TRUE); // Coz sometimes OpenSim stops the AO script! llResetOtherScript(s); } } } llListen(DEBUG_CHANNEL, "", NULL_KEY, ""); VelTime = llGetTimeOfDay(); VelPos = llGetPos(); addEvent(300, "Musers", ScriptKey); addEvent(30, "*.OHSILLYPROBE", ScriptKey); URLrequestID = llRequestURL(); } default { state_entry() { Start = llGetTimeOfDay(); Owner = llGetOwner(); d("\n\n1chatter resetting client scripts @ " + (string) Start + "\n"); ScriptName = llGetScriptName(); ScriptKey = llGetInventoryKey(ScriptName); LibraryKey = ScriptKey; grid = osGetGridName(); init(); } on_rez(integer param) { // TODO - should clear any listeners first. Musers = []; } changed(integer change) { // if (change & CHANGED_INVENTORY) // llResetScript(); if (change & CHANGED_OWNER) llResetScript(); if (change & CHANGED_REGION) { string newGrid = osGetGridName(); if (newGrid != grid) { grid = newGrid; addEvent(30, "*.OHSILLYPROBE", ScriptKey); } llReleaseURL(URL); URL = ""; URLrequestID = llRequestURL(); } } dataserver(key id, string data) { list input = llParseStringKeepNulls(data, [lSEP], []); string fr = llList2String(input, 0); string cmd = llList2String(input, 1); if ("PIN" == fr) { fr = "*"; fr="1AOor2"; cmd = "PIN"; input = [fr, cmd, llList2String(input, 1)]; } else id = llGetOwnerKey(id); doThing(id, llDumpList2String(input, "|"), fr + ".", cmd, llDumpList2String(llList2List(input, 2, -1), ","), fOSM); } http_request(key id, string method, string body) { integer responseStatus = 400; string responseBody = "Unsupported method"; //d("HTTP " + method + " -> " + body); if (method == URL_REQUEST_DENIED) d("Error trying to get a URL - " + body); else if (method == URL_REQUEST_GRANTED) { URLrequestID = NULL_KEY; URL = body; llSleep(0.01); // The other scripts wont have recovered from the TP yet. sendScript(NULL_KEY, lCMD, "*.", [fHTTP, ScriptKey, method, "URL", URL]); // Check for dropped URL. // llSetTimerEvent(30.0); } else if (method == "POST") { list bdy = llParseStringKeepNulls(llUnescapeURL(body), ["|"], []); sendScript(NULL_KEY, lCMD, "*.", [fHTTP, ScriptKey, method, llList2String(bdy, 0), body]); // responseBody = llEscapeURL("|REGION" + llGetRegionName() + "|" + (string)llGetPos()); responseStatus = 200; responseBody = ""; llHTTPResponse(id, responseStatus, responseBody); } } http_response(key id, integer status, list metaData, string body) { if (id == URLCheckrequestID) { URLCheckrequestID = NULL_KEY; if (status != 200) d("HTTP error code " + status + "\n" + body); // else // llOwnerSay("self check worked - " + body); } else if (id == NULL_KEY) d("Too many HTTP requests too fast!"); } // Handle commands from other scripts. link_message(integer sender_num, integer num, string message, key id) { if ((id != ScriptKey) && (id != NULL_KEY)) return; list input = llParseStringKeepNulls(message, [lSEP], []); key them = llList2Key(input, 0); string fr = llList2Key(input, 1); //d("linky " + num + " " + message + " " + fr); if (lRESET == num) { if ("" != fr) { Aliases = wipe(Aliases, fr, aSTRIDE); Commands = wipe(Commands, fr, cSTRIDE); Events = wipe(Events, fr, eSTRIDE, eSCRIPT); Menus = wipe(Menus, fr, mSTRIDE); Musers = wipe(Musers, fr, uSTRIDE, uSTACK); Settings = wipe(Settings, fr, sSTRIDE); Settings = saveThing(Settings, sSTRIDE, ["PREFIX", "S", fr, aALL], 4, fr + "."); Settings = saveThing(Settings, sSTRIDE, ["SCRIPTKEY", "K", them, aALL], 4, fr + "."); Settings = saveThing(Settings, sSTRIDE, ["VERSION", "S", "", aALL], 4, fr + "."); readTheme(fr); sendScript(them, lALIAS_DONE, Aliases); sendScript(them, lRESET_DONE, Settings); sendScript(them, lCMD, "*.", [fHTTP, ScriptKey, URL_REQUEST_GRANTED, "URL", URL]); } // resetPrimShit(); } else if (lSETTINGS == num) { if ("" != fr) { readSettings(fr); sendScript(them, lSETTINGS_DONE, Settings); } } else if (lSETTING == num) { if (id == ScriptKey) { integer source = llList2Integer(input, 2); key a = llList2Key(input, 3); if ("" != fr) { integer l = llGetListLength(input); integer i; for (i = 4; i < l; i += 2) { setSetting(a, fr + "." + llList2String(input, i), llList2String(input, i + 1), source); } // TODO - track the listen handle, delete old one here. integer channel = (integer) getSetting("1ring.CHANNEL"); if (channel) llListen(channel, "", "", ""); //dumpSettings(Settings, "SETTING"); } } } else if (lCHECK_DONE == num) { //d("lCHECK_DONE " + llKey2Name(llList2Key(input, 3)) + " -> " + llList2String(input, 2) + "| returned " + llList2String(input, 4)); if (!llList2Integer(input, 4)) S("ERROR - Invalid command for " + llList2String(input, 2)); } else if (lCMD == num) { doThing(llList2Key(input, 3), llList2String(input, 4), fr + ".", llList2String(input, 5), llList2String(input, 6), llList2Integer(input, 2)); } else if (lCMD_DONE == num) { //d("lCMD_DONE " + llKey2Name(llList2Key(input, 3)) + " -> " + llList2String(input, 2) + "| returned " + llList2String(input, 4)); if (0 != llList2Integer(input, 4)) showMenu(llList2Key(input, 3)); } else if (lDYNAMIC == num) { string menu = llList2String(input, 3); integer f = llSubStringIndex(menu, "."); if (("" != menu) && (-1 == f)) menu = fr + "." + menu; dynamicMenu(llList2Key(input, 2), menu, fr + "." + llList2String(input, 4), llList2String(input, 5), llList2String(input, 6), llList2String(input, 7), llList2Integer(input, 8)); } else if (lMENU == num) { //d("lMENU " + llKey2Name(llList2Key(input, 2)) + " -> " + fr + "." + llList2String(input, 3) + " " + llList2String(input, 4)); key a = llList2String(input, 2); string mn = llList2String(input, 3); string t = llList2String(input, 4); string menu; integer f = listFindString(Musers, a, uSTRIDE); integer m = findMenu(fr + "." + mn); if (-1 != f) menu = llList2String(Musers, f + uCURRENT); if (-1 != m) { if ("" != t) Menus = llListReplaceList(Menus, t, m + mTITLE, m + mTITLE); if ((fr + "." + mn) == menu) menu = "-1"; saveMuser(a, fr + "." + mn, "", -1, menu); showMenu(a); } } else if (lSCAN == num) { d("lSCAN " + llDumpList2String(input, " ~ ")); key a = llList2Key(input, 2); integer f = listFindString(Musers, a, uSTRIDE); if (-1 != f) { Scanners += [llList2String(Musers, f + uCURRENT), a] + llList2List(input, 3, -1); if (nSTRIDE == llGetListLength(Scanners)) llSensor("", NULL_KEY, llList2Integer(input, 3), 100.0, PI); } } } listen(integer channel, string name, key id, string message) { //d("listen " + channel + ", " + llKey2Name(id) + "-> " + message + " " + llGetListLength(Musers)); // Catch OhSillyThreatLevel messages, from the probe. if (channel == DEBUG_CHANNEL) { llOwnerSay(message); // key root = llList2Key(llGetObjectDetails(id, [OBJECT_ROOT]), 0); // if (llGetLinkKey(LINK_ROOT) == root) { integer f = llSubStringIndex(message, "permission denied. "); if (-1 != f) { list e = llParseStringKeepNulls(llGetSubString(message, 0, -f), [" "], []); string function = llList2String(e, 0); if (("OSSL" == llList2String(e, 0)) && ("Runtime" == llList2String(e, 1)) && ("Error:" == llList2String(e, 2))) function = llList2String(e, 3); llMessageLinked(LINK_SET, DEBUG_CHANNEL, function, "0"); llOwnerSay("Found naughty function " + function); return; } } } // See if it's a prefix on the prefix channel, strip off the prefix and feed the remains to doThing(). if (channel == (integer) getSetting("1ring.CHANNEL")) { d("listen 0ring.CHANNEL " + message); string p = getSetting("1ring.PREFIX"); if (llGetSubString(message, 0, llStringLength(p) - 1) == p) { string button = llGetSubString(message, llStringLength(p), -1); list lst = llParseStringKeepNulls(llStringTrim(button, STRING_TRIM), [" "], []); string cmd = llList2String(lst, 0); string data = llDumpList2String(llListReplaceList(lst, [], 1, 1), " "); d("listen 1ring.CHANNEL " + message + " -> " + button + " " + cmd + " " + data); doThing(id, message, "1ring.", cmd, data, fCHAT); return; } } integer l = llGetListLength(Musers); integer i; for (i = 0; i < l; i += uSTRIDE) { if (channel == llList2Integer(Musers, i + uCHAN)) { if (handleMenu(i, id, message)) showMenu(id); return; } } } no_sensor() { if (0 != llGetListLength(Scanners)) { key id = llList2Key(Scanners, nID); Scanners = llListReplaceList(Scanners, [], 0, nSTRIDE - 1); if (0 != llGetListLength(Scanners)) llSensor("", NULL_KEY, llList2Integer(Scanners, nTYPE), 100.0, PI); showMenu(id); } } sensor(integer num) { if (0 != llGetListLength(Scanners)) { string cmd = llList2String(Scanners, nCMD); list items = []; list keys = []; key id = llList2Key(Scanners, nID); string menu = llList2String(Scanners, nMENU); integer i; for (i = 0; i < num; ++i) { items += [llGetSubString(llDetectedName(i), 0, 23)]; keys += [cmd + " " + llDetectedKey(i)]; } Scanners = llListReplaceList(Scanners, [], 0, nSTRIDE - 1); if (0 != llGetListLength(Scanners)) llSensor("", NULL_KEY, llList2Integer(Scanners, nTYPE), 100.0, PI); dynamicMenu(id, menu, menu, llList2String(Scanners, nTITLE), llDumpList2String(items, "|"), llDumpList2String(keys, "|"), 2); } } timer() { float now = llGetTimeOfDay(); integer i; while (NextEvent <= now) { string script = llList2String(Events, eSCRIPT); key them = llList2Key(Events, eKEY); Events = llListReplaceList(Events, [], 0, eSTRIDE - 1); if (0 != llGetListLength(Events)) NextEvent = llList2Float(Events, 0); else NextEvent = 307584000.0; now = llGetTimeOfDay(); if ("Musers" == script) { integer f = llGetListLength(Musers); list t = []; for (i = 0; i < f; i += uSTRIDE) { key a = llList2Key(Musers, i + uKEY); // Test if they are still in the sim. if (ZERO_VECTOR != llGetAgentSize(a)) { if (now < (llList2Float(Musers, i + uTIME) + 300.0)) t += llList2List(Musers, i, i + uSTRIDE - 1); else { // s(a, "You have taken too long to respond to the menu, it's disabled now."); // a = NULL_KEY; } } else a = NULL_KEY; if (NULL_KEY == a) // Remove those no longer in the sim, or menu timeouts. llListenRemove(llList2Integer(Musers, i + uLSTN)); } Musers = t; addEvent(300, script, them); } else { list t = llParseStringKeepNulls(script, ["."], []); string fr = llList2Key(t, 0); list dt = llParseString2List(llList2String(t, 1), [" "], []); string cmd = llList2String(dt, 0); dt = llListReplaceList(dt, [], 0, 0); d("timer " + script + "->" + fr + " ... " + cmd + "=" + llDumpList2String(dt, " ") + " " + them); if (them == ScriptKey) //integer doThing(key id, string command, string fr, string cmd, string data, integer source) doThing(them, cmd, "*.", cmd, "", fINT); else sendScript(them, lCMD, fr + ".", [fINT, them, "TimerEvent", cmd, llDumpList2String(dt, " ")]); } } llSetTimerEvent(NextEvent - now); now = llGetTimeOfDay(); float time = now - VelTime; vector vp = llGetPos(); float dist = llVecDist(VelPos, vp); float speed = dist / time; VelTime = now; VelPos = vp; // llSetText("Velocity " + (string) speed + " m/s\n@ " + (string) now, <1.0, 1.0, 1.0>, 1.0); llSetText("", <1.0, 1.0, 1.0>, 1.0); } touch_start(integer num) { for (--num; 0 <= num; --num) { key id = llDetectedKey(num); string menu = MainMenu; list v = validateName(menu, "menu"); string fr = llList2String(v, 0); menu = fr + llList2String(v, 1); if (access(id, menu + " menu", fr, llList2Integer(Menus, 0 + mAUTH), TRUE)) { saveMuser(id, menu, "", -1, ""); showMenu(id); } } } }