From 1e5b49a55b7ebd03c6e57be993668f38ac20f841 Mon Sep 17 00:00:00 2001 From: onefang Date: Sun, 30 Jun 2019 11:16:41 +1000 Subject: Include the actual source code this time. --- 1AOor2.lsl | 1835 ++++++++++++++++++++++++++++++++++++++++++++ 1chatter.lsl | 1597 ++++++++++++++++++++++++++++++++++++++ 1ring.lsl | 1424 ++++++++++++++++++++++++++++++++++ OhSillyThreatDetector.lsl | 59 ++ onefang's leash holder.lsl | 44 ++ 5 files changed, 4959 insertions(+) create mode 100644 1AOor2.lsl create mode 100644 1chatter.lsl create mode 100644 1ring.lsl create mode 100644 OhSillyThreatDetector.lsl create mode 100644 onefang's leash holder.lsl diff --git a/1AOor2.lsl b/1AOor2.lsl new file mode 100644 index 0000000..7b460b6 --- /dev/null +++ b/1AOor2.lsl @@ -0,0 +1,1835 @@ + +// AO and couples interactions. An Animation Overrider with a swimmer and a smiler, which are also AO type things. +// evry byt cnts + +string Version = "1AOor2 v0.1 test version"; + +// BEGIN boilerplate. +integer DEBUG = FALSE; +float Start; +string ScriptName; +key ScriptKey; +key LibraryKey; +key Owner; +string URL; + +// Settings. +list Aliases; +list Settings; +integer sNAME = 0; +integer sTYPE = 1; +integer sVALUE = 2; +integer sAUTH = 3; +integer sSTRIDE = 4; + +// Access "f"rom some source - +integer fINT = 0; +integer fCARD = 1; +integer fCHAT = 2; +integer fLINK = 3; +integer fMENU = 4; +integer fRLV = 5; +integer fRELAY = 6; +integer fOSM = 7; +integer fHTTP = 8; + +// utilities commands, "l"ibrary. +string lSEP = "$!#"; +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 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; + +// OhSillyThreat detector +list OhSillyThreats = []; +list OhSillyTreats = // OpenSim threat level. +[ +// "osKey2Name", // low +// "osGetAvatarList", // none + "osGetNotecard", // very high (describes what they where when making this decision) +// "osMakeNotecard", // high (describes what they where when making this decision) +// "osGetRezzingObject", // none + "osMessageObject", // low + "osAvatarPlayAnimation", // very high + "osAvatarStopAnimation", // very high + "osForceOtherSit", // very high + "osSetSpeed" // moderate +]; + +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);} +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); +} +sendPrim(key them, string cmd, list args) +{ + osMessageObject(them, llDumpList2String([ScriptName, cmd] + args, lSEP)); +} +addEvent(float delay, string cmds) +{ + sendScript(lCMD, [fINT, ScriptKey, "TimerEvent", "TIMER", ((string) delay) + " " + cmds]); +} +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 listFindString(list lst, string name, integer stride) +{ + integer f = llListFindList(lst, [name]); + integer ix = f / stride; + ix = ix * stride; + 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; +} + +// Note these next two are different from the ones in 1chatter. +string alias(string n) +{ + list v = validateName(n, "alias"); + n = llList2String(v, 1); + integer a = listFindString(Aliases, llList2String(v, 0) + llToLower(n), 2); + if (-1 != a) n = llList2String(Aliases, a + 1); + return n; +} + +list validateName(string var, string type) +{ + list v = llParseStringKeepNulls(var, ["."], []); + if (2 != llGetListLength(v)) v = ScriptName + v; + var = llList2String(v, 1); + if ("setting" == type) var = llToUpper(var); + return [llList2String(v, 0) + ".", var]; +} + +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; +} + +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) + { + Settings = llListReplaceList(Settings, [val], f + sVALUE, f + sVALUE); + if (llGetSubString(fr, 0, -2) == ScriptName) + { + if (fINT != source) + { + doThing(id, "SET " + var + "=" + val, fr, llList2String(v, 1), val, fINT); + sendScript(lSETTING, [source, id, llList2String(v, 1), val]); + } + } + } +} + +doSettings(key id, list settings) +{ + integer l = llGetListLength(settings); + integer i; + for (i = 0; i < l; i += sSTRIDE) + { + string var = llList2String(settings, i + sNAME); + list v = validateName(var, "setting"); + string fr = llList2String(v, 0); + var = llList2String(v, 1); + string val = llList2String(settings, i + sVALUE); + doThing(id, "SET " + var + "=" + val, fr, var, val, fINT); + } +} + +list readCard(string card) +{ + list r; + card = "~" + card + ".data"; + if (NULL_KEY != llGetInventoryKey(card)) + r = llList2List(llParseStringKeepNulls(osGetNotecard(card), ["\n"], []), 0, -2); + return r; +} + +dynamicMenu(key id, string menu, string name, string title, string entries, string command) +{ + sendScript(lDYNAMIC, [id, menu, name, title, entries, command]); +} + +integer Chosen; +linky(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); + if (lCMD == num) + { +//d("link CHAT " + llDumpList2String(input, " ~ ")); + if ((fr == (ScriptName + ".")) || (fr == "*.")) + { + key a = llList2Key(input, 3); + string button = llList2String(input, 4); + integer r = doThing(a, button, fr, + llList2String(input, 5), llList2String(input, 6), llList2Integer(input, 2)); + sendScript(lCMD_DONE, [button, a, r]); + } + } + else if (lRESET_DONE == num) + { +d("linky RESET_DONE"); + LibraryKey = them; + Settings = llList2List(input, 2, -1); + setSetting(ScriptKey, ScriptName + ".VERSION", Version, fCARD); + sendScript(lSETTINGS, []); + } + else if (lALIAS_DONE == num) Aliases = llList2List(input, 2, -1); + else if (lSETTINGS_DONE == num) + { + Settings = llList2List(input, 2, -1); + doSettings(id, Settings); + laterInit(); + s(Owner, "Finished starting up " + getSetting("VERSION") + " in " + (string) (llGetTimeOfDay() - Start)); + } + else if (DEBUG_CHANNEL == num) + { + key root = llList2Key(llGetObjectDetails(id, [OBJECT_ROOT]), 0); + integer f = llListFindList(OhSillyThreats, [message]); + if (-1 == f) OhSillyThreats += [message]; + else d("OhSillyThreats detected the function " + message + "() again!" ); + D("Oh Silly threat system prevented " + message + "()" + + "\n in " + id + " \t" + llKey2Name(id) + + "\n part of " + root + " \t" + llKey2Name(root)); + } +} +// END boilerplate, mostly. + +integer doThing(key id, string button, string fr, string cmd, string data, integer source) +{ + if ("Set " == llGetSubString(button, 0, 3)) + { + integer set = listFindString(Settings, fr + cmd, sSTRIDE); + if (-1 != set) setSetting(id, fr + cmd, data, fINT); + } + if ((fr != (ScriptName + ".") && ("*." != fr))) return TRUE; + integer st = findSitter(id); + integer f; + string menu; + f = llSubStringIndex(button, "->"); + if (-1 != f) + { + menu = llGetSubString(button, 0, f - 1); + button = llGetSubString(button, f + 2, -1); + } +//d("doThing " + button + " = |" + cmd + "| -> " + data ); + if ("Keys" == cmd) updateControls(); + else if ("CONTROLS" == cmd) + { + list p = llParseStringKeepNulls(data, [","], []); + doControl(llList2Key(p, 0), llList2Integer(p, 1), llList2Integer(p, 2)); + } + else if ("checkAO" == cmd) checkAO(); + else if ("SMILE" == cmd) + { // The built in express_* animations are too short, but live with it. + osAvatarStopAnimation(llGetOwner(), Smile); + Smile = llList2String(Smiles, (integer) llFrand(2.5)); + addEvent(3.0 + llFrand(5.0), cmd); + osAvatarPlayAnimation(llGetOwner(), Smile); + } + else if ("LESS_-" == cmd) + { + integer distance = llList2Integer(Sitters, st + pDIST); + --distance; + if (0 > distance) distance = 0; + Sitters = llListReplaceList(Sitters, [distance], st + pDIST, st + pDIST); + showMenu(id); + return FALSE; + } + else if ("MORE_+" == cmd) + { + integer distance = llList2Integer(Sitters, st + pDIST); + ++distance; + if (llGetListLength(Distances) <= distance) distance = llGetListLength(Distances) - 1; + Sitters = llListReplaceList(Sitters, [distance], st + pDIST, st + pDIST); + showMenu(id); + return FALSE; + } + else if ("NEXT_AVATAR" == cmd) + { + key them = llList2Key(Sitters, st + pADJ); + integer t = findSitter(them); + if (-1 != t) + { + t += pSTRIDE; + if (llGetListLength(Sitters) <= t) + t = -1; + } + else t = 0; + if (-1 != t) + Sitters = llListReplaceList(Sitters, [llList2Key(Sitters, t + pKEY)], st + pADJ, st + pADJ); + else + Sitters = llListReplaceList(Sitters, [ScriptKey], st + pADJ, st + pADJ); + showMenu(id); + return FALSE; + } + else if ( + ("FORWARD" == cmd) || ("BACKWARDS" == cmd) || ("LEFT" == cmd) || ("RIGHT" == cmd) + || ("UP" == cmd) || ("DOWN" == cmd) || ("TURN_LEFT" == cmd) || ("TURN_RIGHT" == cmd)) + { + integer i = llSubStringIndex(cmd, "_"); + if (-1 != i) cmd = llGetSubString(cmd, 0, i - 1) + " " + llGetSubString(cmd, i + 1, -1); + adjust(id, llList2Float(Distances, llList2Integer(Sitters, st + pDIST)), llToLower(cmd)); + } + else if ("AO" == cmd) + { + if ("0" != data) + { + integer l = llGetListLength(Sitters); + Pose = ""; + for (f = 0; f < l; f += pSTRIDE) + updateSitter(llList2Key(Sitters, f + pKEY)); + checkAO(); + } + } + else if ("SYNC" == cmd) + { + integer l = llGetListLength(Sitters); + for (f = 0; f < l; f += pSTRIDE) + { + if (("R" != data) || (0 == llGetListLength(isPoseAO(f)))) + Sitters = llListReplaceList(Sitters, [""], f + pSTATE, f + pSTATE); + } + if ("R" != data) + vAnim = data; + checkAO(); + } + else if ("POSE" == cmd) + { + list p = llParseStringKeepNulls(data, [","], []); + if (Attached) + { + What = llList2String(p, 0); + Whats = llList2List(p, 1, -1); + Chosen = 0; + list b = osGetAvatarList(); + integer l = llGetListLength(b); + integer i; + string keys; + string names; + for (i = 0; i < l; i += 3) + { + keys += "|COUPLE_WITH " + llList2String(b, i); + names += "|" + llList2String(b, i + 2); + } + dynamicMenu(Owner, "couples", What, " Pick someone to '" + What + "' with -", + llGetSubString(names, 1, -1), llGetSubString(keys, 1, -1)); + PIN = (integer) llFrand(DEBUG_CHANNEL - 2) + 2; + vector RefPos = llGetPos(); + rotation RefRot = llGetRot(); + llRezObject("1AOor2 prim", ZERO_VECTOR * RefRot + RefPos, ZERO_VECTOR, (ZERO_ROTATION) * RefRot, PIN); + return FALSE; + } + else + newPose(id, llList2String(p, 0), llList2List(p, 1, -1)); + } + else if ("COUPLE_WITH" == cmd) + { + Stalkee = data; + if (osIsNpc(Stalkee)) + { + Chosen = 1; + s(Owner, llKey2Name(data) + " is an NPC, forcing them to '" + What + "' with you."); + } + else + { + s(Owner, "Asking " + llKey2Name(Stalkee) + " if they would like to do '" + What + "' with you."); + dynamicMenu(Stalkee, "", What, + llKey2Name(Owner) + " would like to do '" + What + "' with you.", + "Yes|No", "COUPLE_YES " + Stalkee + "|COUPLE_NO " + Stalkee); + sendScript(lCMD, "1ring", [fINT, id, Stalkee, "GOTO", Stalkee]); + addEvent(120.0, "COUPLE_TIMEOUT"); + } + return FALSE; + } + else if ("PIN" == cmd) + { + list p = llParseStringKeepNulls(data, [","], []); + integer i = llGetInventoryNumber(INVENTORY_NOTECARD); + TheirKey = id; + while (i-- > 0) + llGiveInventory(id, llGetInventoryName(INVENTORY_NOTECARD, i)); + i = llGetInventoryNumber(INVENTORY_ANIMATION); + while (i-- > 0) + llGiveInventory(id, llGetInventoryName(INVENTORY_ANIMATION, i)); + llGiveInventory(id, "soap-bubble"); + llRemoteLoadScriptPin(id, "1chatter", llList2Integer(p, 0), TRUE, bREZ); + llRemoteLoadScriptPin(id, ScriptName, llList2Integer(p, 0), TRUE, bREZ); + } + else if ("REZ_DONE" == cmd) + { + if (1 == Chosen) + { + sendPrim(TheirKey, "SIT", [Stalkee, What, llDumpList2String(Whats, "|")]); + sendPrim(TheirKey, "COUPLES", [Owner]); + Chosen = 0; + } + else if (-1 == Chosen) killPrim(); + else + addEvent(0.5, cmd); + } + else if ("COUPLES" == cmd) + { + if (NULL_KEY != TheirKey) + sendPrim(TheirKey, "COUPLES", [id]); + else + { + if ("" != data) id = data; + sendScript(lMENU, [id, "couples", ""]); + } + return FALSE; + } + else if ("COUPLE_NO" == cmd) + { + Chosen = -1; + addEvent(0.0, "COUPLE_TIMEOUT"); + sendScript(lCMD, "1ring", [fINT, id, Stalkee, "STOP", ""]); + s(Owner, llKey2Name(id) + " said no to your offer of '" + What + "'."); + } + else if ("COUPLE_YES" == cmd) + { + Chosen = 1; + addEvent(0.0, "COUPLE_TIMEOUT"); + sendScript(lCMD, "1ring", [fINT, id, Stalkee, "STOP", ""]); + s(Owner, llKey2Name(id) + " said yes to your offer of '" + What + "."); + } + else if ("COUPLE_TIMEOUT" == cmd) + { + if (NULL_KEY != Stalkee) + { + Chosen = -1; + s(Owner, llKey2Name(Stalkee) + " didn't answer your offer of '" + What + "."); + s(Stalkee, "You failed to answer " + llKey2Name(Owner) + "'s offer to '" + What + "' with you."); + } + } + else if ("SIT" == cmd) + { + if (!Attached) + { + list det = llGetObjectDetails(Owner, [OBJECT_POS, OBJECT_ROT]); + list p = llParseStringKeepNulls(data, [","], []); + llSetPrimitiveParams([PRIM_POSITION, llList2Vector(det, 0), PRIM_ROTATION, llList2Rot(det, 1)]); + Stalkee = llList2Key(p, 0); + d("Forcing sit of " + llKey2Name(Stalkee) + " (" + Stalkee + ") for " + llList2Key(p, 1)); + // This function only works if no one else is sitting on the object, which is why we sit them first. + // RLV has no such limitation. + if (osIsNpc(Stalkee)) + osNpcSit(Stalkee, llGetKey(), OS_NPC_SIT_NOW); + else + { + llInstantMessage(Stalkee, "Please sit on the big gold heart."); +// osForceOtherSit(Stalkee/*, llGetKey()*/); + } + newPose(id, llList2Key(p, 1), llList2List(p, 2, -1)); + } + } + else if ("SIT_DONE" == cmd) + { +d("SIT_DONE for " + llKey2Name(data)); + if(data == Stalkee) + { + llOwnerSay("@sit:" + TheirKey + "=force"); + s(Owner, "Switching AO to 1AOor2 couples object."); + Controller = Owner; + d("SIT_DONE is requesting camera and controls from " + llKey2Name(Controller)); + llRequestPermissions(Controller, PERMISSION_CONTROL_CAMERA | PERMISSION_TAKE_CONTROLS); + } + } + else if ("DIE" == cmd) die(); + else if ("DIE_DONE" == cmd) + { + TheirKey = NULL_KEY; + if (99.0 <= TPangle) + { + s(Owner, "Switching AO to 1ring object."); + oldController(Owner); + } + } + else if ("ADJUST" == cmd) + { + if (Attached) s("You need to be in a couples interaction with someone to use the ADJUST function."); + else {showMenu(id); return FALSE;} + } + else if ("SAVE" == cmd) + { + if (Attached) s(Owner, "You need to be in a couples interaction with someone to use the SAVE function."); + else sendPrim(BossKey, "SAVE_POSES", [llDumpList2String(reportPose(), "\n")]); + } + else if ("SAVE_POSES" == cmd) + { + integer i = llGetInventoryNumber(INVENTORY_NOTECARD); + list cards = []; + s(Owner, "Backing up old cards."); + while (i-- > 0) + { + string item = llGetInventoryName(INVENTORY_NOTECARD, i); + + if ((llSubStringIndex(item, ".POSITIONS") == 0) ) cards += [llGetInventoryName(INVENTORY_NOTECARD, i)]; + } + i = llGetListLength(cards); + while (i-- > 0) + { + string item = llList2String(cards, i); + osMakeNotecard(".backup" + item, llParseStringKeepNulls(osGetNotecard(item), ["\n"], [])); + llRemoveInventory(item); + } + osMakeNotecard(".POSITIONS", data); + s(Owner, "Current positions saved to the .POSITIONS notecard."); + } + else if ("TPRIM" == cmd) + { + if (Attached) + { + vector cr = <1.0, 0.0 ,0.0> * llGetRootRotation(); + TPangle = llAtan2(cr.x, cr.y); + llOwnerSay("@tpto:" + data + "=force"); + } + else + { + list crl = llParseString2List(data, ["/"], []); + vector cr = ; + integer x = (integer)(cr.x / 256); + integer y = (integer)(cr.y / 256); + cr.x = cr.x % 256; + cr.y = cr.y % 256; + osTeleportAgent(Stalkee, x, y, cr, <1.0,1.0,1.0>); + } + } + else if ("RLV" == cmd) + { + list dt = llParseString2List(data, ["|"], []); +d("RLV command requested " + llList2String(dt, 0)); + llOwnerSay("@" + llList2String(dt, 0)); + if (2 < llGetListLength(dt)) + addEvent(llList2Float(dt, 1), "RLV " + llDumpList2String(llList2List(dt, 2, -1), "|")); + } + else if ("URL" == cmd) + { + URL = data; +d("New URL " + URL); + } + else if ("▲" == cmd) + { + if (Attached) + Chosen = -1; + else + { + if (-1 != st) + { + if ("" != llList2Key(Sitters, st + pADJ)) + { + Sitters = llListReplaceList(Sitters, [""], st + pADJ, st + pADJ); + s(id, "Switched out of adjusting mode."); + addEvent(Tick * Smooth, "Keys"); + } + } + } + } + else if (-1 == listFindString(Settings, fr + cmd, sSTRIDE)) + { + if (fMENU == source) d("Unknown menu command '" + cmd + "' from - " + button); + else d("Unknown command '" + cmd + "' from - " + button); + } + return (source == fMENU); +} + +float TPangle = 999.0; + +killPrim() +{ +d("killPrim"); + if (NULL_KEY != TheirKey) + sendPrim(TheirKey, "DIE", []); + Stalkee = NULL_KEY; + What = ""; + Chosen = 0; +} + +// General variables +integer Attached; +integer Link; +list Distances = [0.01, 0.05, 0.1, 0.5, 1.0, 2.0]; +vector position = <0.0, 0.0, -0.0001>; +vector rotat = ZERO_VECTOR; + +// Rezonater +key BossKey = NULL_KEY; +key TheirKey = NULL_KEY; +integer bREZ = -501; +string HoverText = "loveness"; +string Sit0Text = "carry them"; +string Sit1Text = "be carried"; + +// DrivableBox +list Sitters = []; +integer pKEY = 0; // Key of the sitter. +integer pLINK = 1; // The "prim" link the sitter is. +integer pSTATE = 2; // The current AOstate for this person. +integer pNEW = 3; // New state for this person. +integer pANIMS = 4; // Current set of animations for this person. +integer pADJ = 5; // Key of who they are adjusting, "" means not adjusting, NULL_KEY will mean adjust all. +integer pDIST = 6; // For the adjusting this person is doing, not for who they are adjusting. +integer pKEYS = 7; // KeysLevel for this sitter. +integer pSTRIDE = 8; +list Poses; // List of poses. +integer psNAME = 0; // Name of pose. +integer psANIM = 1; // | separated animations. +integer psEMOTE = 2; // | separated emotions and timers list. +integer psPOSROT= 3; // | separated position and rotation pairs. +integer psSTRIDE= 4; + +// Smiler +integer SmileCounter = 0; +string Smile = "express_toothsmile"; +list Smiles = +[ + "express_smile", + "express_toothsmile", + "express_wink_emote", + "express_tongue_out" +]; + +// Couples +list Whats; +string What; +string Pose; +key Stalkee; +integer PIN; +key Leader = NULL_KEY; +float LeaderOffset = 0.0; +key Controller = NULL_KEY; + +// AO +integer Lag = 100; +integer Swimming; +integer Bobbing; +float Tick = 0.2; +float AOspeed = 1.0; +list flyStates; +list initialStates; // "Taking Off", "hover_up", +list ANIMATIONS; + +integer checkAnim(string a) +{ + if (("" != a) && (NULL_KEY == llGetInventoryKey(a)) && (-1 == llListFindList(ANIMATIONS, [a]))) + { + s(Owner, "Missing animation - " + a + "!"); + return FALSE; + } + return TRUE; +} + +newPose(key id, string p, list ps) +{ + integer f = findPose(p); + integer l = llGetListLength(Sitters); + if (-1 != f) + { + setSetting(id, "AO", "0", fINT); + Pose = p; + Poses = llListReplaceList(Poses, llDumpList2String(ps, "|"), f + psANIM, f + psANIM); + for (f = 0; f < l; f += pSTRIDE) + updateSitter(llList2Key(Sitters, f + pKEY)); + checkAO(); + } +} + +integer findSitter(key id) {return listFindString(Sitters, id, pSTRIDE);} + +string prStr(string str) +{ + integer ix = llSubStringIndex(str, ">"); + vector p = (vector) llGetSubString(str, 0, ix); + vector r = (vector) llGetSubString(str, ix + 1, -1); + return vround(p, r); +} + +string vround(vector p, vector r) +{ // OpenSim likes to swap these around, which triggers the ball movement saving. + if (-179.9 >= r.x) r.x += 360.0; if (-179.9 >= r.y) r.y += 360.0; if (-179.9 >= r.z) r.z += 360.0; + if ( 179.9 <= r.x) r.x -= 360.0; if ( 179.9 <= r.y) r.y -= 360.0; if ( 179.9 <= r.z) r.z -= 360.0; + return "<" + round(p.x, 3) + "," + round(p.y, 3) + "," + round(p.z, 3) + + "><" + round(r.x, 1) + "," + round(r.y, 1) + "," + round(r.z, 1) + ">"; +} + +string round(float number, integer places) +{ + float shifted; + integer rounded; + string s; + shifted = number * llPow(10.0, (float) places); + rounded = llRound(shifted); + s = (string) ((float) rounded / llPow(10.0, (float)places)); + rounded = llSubStringIndex(s, "."); + if (-1 != rounded) s = llGetSubString(s, 0, llSubStringIndex(s, ".") + places); + else + { + s += ".00000000"; + s = llGetSubString(s,0,llSubStringIndex(s, ".") + places); + } + return s; +} + +readPos(list cards) +{ + integer i; + integer l = llGetListLength(cards); + cards = llListSort(cards, 1, TRUE); + for (i = 0; i < l; i++) + { + string card = llList2String(cards, i); + list crd = llParseStringKeepNulls(osGetNotecard(card), ["\n"], []); + integer m = llGetListLength(crd); + integer j; + d("Reading '" + card + "'."); + for (j = 0; j < m; j++) + { + string data = llList2String(crd, j); + if (llGetSubString(data, 0, 0) != "/") + { // skip comments + data = llStringTrim(data, STRING_TRIM); + integer ix = llSubStringIndex(data, "{"); + integer jx = llSubStringIndex(data, "} <"); + if (ix != -1 && jx != -1) + { + string name = llStringTrim(llGetSubString(data, ix + 1, jx - 1), STRING_TRIM); + string ldata = llGetSubString(data, jx + 2, -1); + list posrots = llParseString2List(ldata, ["<"], []); + string pr = ""; + jx = llGetListLength(posrots); + for (ix = 0; ix < jx; ix += 2) + pr += "|" + prStr("<" + llStringTrim(llList2String(posrots, ix), STRING_TRIM) + + "<" + llStringTrim(llList2String(posrots, ix + 1), STRING_TRIM)); + savePose(name, "", "", llGetSubString(pr, 1, -1)); + } + } + } + } +} + +integer findPose(string name) {return listFindString(Poses, name, psSTRIDE);} + +savePose(string name, string anim, string exp, string posRot) +{ + integer f = findPose(name); + posRot = normPose(posRot); + if (-1 != f) + { + if ("" == anim) + { + anim = llList2String(Poses, f + psANIM); + exp = llList2String(Poses, f + psEMOTE); + } + else if ("" == posRot) posRot = llList2String(Poses, f + psPOSROT); + Poses = llListReplaceList(Poses, [name, anim, exp, posRot], f, f + psSTRIDE - 1); + } + else + Poses += [name, anim, exp, posRot]; +} + +string normPose(string posRot) +{ + // Subtract the x,y of the first from both to normalise it around the prim. + // NOTE - only handles the first two. + list p = llParseStringKeepNulls(posRot, ["|"], []); + string pr0 = llList2String(p, 0); + integer jx0 = llSubStringIndex(pr0, "><"); + vector minp = (vector) llGetSubString(pr0, 0, jx0); + vector minr = (vector) llGetSubString(pr0, jx0 + 1, -1); + string pr1 = llList2String(p, 1); + integer jx1 = llSubStringIndex(pr1, "><"); + vector maxp = (vector) llGetSubString(pr1, 0, jx1); + vector maxr = (vector) llGetSubString(pr1, jx1 + 1, -1); + maxp.x = maxp.x - minp.x; + maxp.y = maxp.y - minp.y; + minp.x = 0.0; minp.y = 0.0; + maxr.z = maxr.z - minr.z; + minr.z = 0.0; + return prStr(((string) minp) + ((string) minr)) + "|" + prStr(((string) maxp) + ((string) maxr)); +} + +list reportPose() +{ + list result = []; + integer l = llGetListLength(Poses); + integer i; + for (i = 0; i < l; i += psSTRIDE) + { + list prs = llParseString2List(llList2String(Poses, i + psPOSROT), ["|"], []); + string r = "{" + llList2String(Poses, i) + "} "; + integer m = llGetListLength(prs); + integer j; + + for (j = 0; j < m; j++) + r += llList2String(prs, j); + result += [r]; + } + return result; +} + +lookAtMe(key id) +{ + if ((llGetPermissionsKey() == id) && (llGetPermissions() & PERMISSION_CONTROL_CAMERA)) + { + d("setting camera for " + llKey2Name(id)); + llClearCameraParams(); + llSetCameraParams( + [ CAMERA_ACTIVE, 1, CAMERA_FOCUS_OFFSET, <0.0, 0.0, 1.0>, CAMERA_PITCH, 12.5, + CAMERA_BEHINDNESS_ANGLE, 0.1, CAMERA_BEHINDNESS_LAG, 0.0, CAMERA_DISTANCE, 2.75, + CAMERA_FOCUS_LAG, 0.0 , CAMERA_FOCUS_THRESHOLD, 0.0, + CAMERA_POSITION_LAG, 0.0, CAMERA_POSITION_THRESHOLD, 0.0 + ]); + } + else + { + if (Controller == id) + { + d("lookAtMe() requesting camera and controls from " + llKey2Name(id)); + llRequestPermissions(id, PERMISSION_CONTROL_CAMERA | PERMISSION_TAKE_CONTROLS); + } + else + { + d("lookAtMe() requesting camera from " + llKey2Name(id)); + llRequestPermissions(id, PERMISSION_CONTROL_CAMERA); + } + } +} + +float ControlTime = 0.0; +integer HeldKeys; +integer LevelKeys; +doControl(key id, integer level, integer edge) +{ + if (99.0 > TPangle) return; + if (NULL_KEY != TheirKey) + { + sendPrim(TheirKey, "CONTROLS", [id, level, edge]); + if ("Sitting" != llGetAnimation(id)) + oldController(id); + return; + } +// integer start = level & edge; +// integer end = ~level & edge; + integer held = level & ~edge; +// integer untouched = ~(level | edge); +// llSay(0, "doControl " + llKey2Name(id) + " " + llList2CSV([level, edge, start, end, held, untouched])); + integer f = findSitter(id); + if (-1 != f) + { + integer a = ("" != llList2Key(Sitters, f + pADJ)); + if ((held == (CONTROL_BACK | CONTROL_FWD)) || + (held == (CONTROL_DOWN | CONTROL_UP)) || + (held == (CONTROL_LEFT | CONTROL_RIGHT)) || + (held == (CONTROL_ROT_LEFT | CONTROL_ROT_RIGHT))) + { + if ((llGetTimeOfDay() - ControlTime) >= 1.0) + { + if (held != HeldKeys) + { + if (a) + { + Sitters = llListReplaceList(Sitters, [""], f + pADJ, f + pADJ); + s(id, "Switched out of adjusting mode."); + addEvent(Tick * Smooth, "Keys"); + } + else + showMenu(id); + } + HeldKeys = held; + } + level = 0; + } + else if (0 != HeldKeys) + { + HeldKeys = 0; + level = 0; + } + Sitters = llListReplaceList(Sitters, [level], f + pKEYS, f + pKEYS); + if (0 != edge) ControlTime = llGetTimeOfDay(); + if (level != LevelKeys) + updateControls(); + LevelKeys = level; + } +} + +float SPEED = 3.8; +float ROTAT = 8.0; +float Smooth = 0.2; // 0.2 is good. +string vAnim; +updateControls() +{ + if (Attached) return; + integer mode = vNONE; integer reverse = 1; float speed = 0.0; float rot = 0.0; + integer l = llGetListLength(Sitters); integer i; + integer keys; + for (i = l - pSTRIDE; i >= 0; i -= pSTRIDE) + { + key k = llList2Key(Sitters, i + pKEY); + keys = llList2Integer(Sitters, i + pKEYS); + if ("" == llList2Key(Sitters, i + pADJ)) + { + if (keys & (CONTROL_DOWN |CONTROL_UP)) mode = vUP; + if (keys & (CONTROL_BACK | CONTROL_FWD)) mode = vFWD; + if (keys & (CONTROL_LEFT | CONTROL_RIGHT)) mode = vLEFT; + if (keys & (CONTROL_ROT_LEFT | CONTROL_ROT_RIGHT)) mode = vFWD; + if (keys & (CONTROL_BACK | CONTROL_DOWN | CONTROL_RIGHT)) + { + speed = 0.0 - (SPEED * Smooth); + reverse = -1; + } + if (keys & (CONTROL_FWD | CONTROL_UP | CONTROL_LEFT)) speed = SPEED * Smooth; + if (keys & CONTROL_ROT_LEFT) rot = (reverse * PI) / (ROTAT / Smooth); + if (keys & CONTROL_ROT_RIGHT) rot = ((0 - reverse) * PI) / (ROTAT / Smooth); + } + else + { + float d = llList2Float(Distances, llList2Integer(Sitters, i + pDIST)); + if (keys & CONTROL_FWD) adjust(k, d, "forward"); + if (keys & CONTROL_BACK) adjust(k, d, "backwards"); + if (keys & CONTROL_LEFT) adjust(k, d, "left"); + if (keys & CONTROL_RIGHT) adjust(k, d, "right"); + if (keys & CONTROL_ROT_LEFT) adjust(k, d, "turn left"); + if (keys & CONTROL_ROT_RIGHT) adjust(k, d, "turn right"); + if (keys & CONTROL_UP) adjust(k, d, "up"); + if (keys & CONTROL_DOWN) adjust(k, d, "down"); + } + } + updateVehicle(keys, mode, speed * AOspeed, rot); + checkLag(); + float t = Tick; + if (vNONE != mode) addEvent(t * Smooth, "Keys"); + else if (vFALL == vMODE) addEvent(t * Smooth * 1.5, "Keys"); +} + +adjust(key id, float dist, string direction) +{ + integer f = findSitter(id); + integer i = -1; + list p; + i = findPose(Pose); + if (-1 != i) + p = llParseStringKeepNulls(llList2String(Poses, i + psPOSROT), ["|"], []); + else + return; + if (-1 != f) + { + key them = llList2Key(Sitters, f + pADJ); + integer l; integer t; + if (ScriptKey == them) + { + t = 0; + l = llGetListLength(Sitters); + } + else + { + t = findSitter(them); + l = t + pSTRIDE; + } + if (-1 != t) + { + if (("turn left" == direction) || ("turn right" == direction)) dist = dist * 90.0; + for (; t < l; t += pSTRIDE) + { + them = llList2Key(Sitters, t + pKEY); + integer lnk = llList2Integer(Sitters, t + pLINK); + if (-1 == lnk) return; + string prn = llList2String(p, t / pSTRIDE); + integer ix = llSubStringIndex(prn, ">"); + vector pos = (vector) llGetSubString(prn, 0, ix); + vector rot = (vector) llGetSubString(prn, ix + 1, -1); + if ("forward" == direction) pos.x = pos.x + dist; + else if ("backwards" == direction) pos.x = pos.x - dist; + else if ("left" == direction) pos.y = pos.y + dist; + else if ("right" == direction) pos.y = pos.y - dist; + else if ("up" == direction) pos.z = pos.z + dist; + else if ("down" == direction) pos.z = pos.z - dist; + else if ("turn left" == direction) rot.z = rot.z + (dist); + else if ("turn right" == direction) rot.z = rot.z - (dist); + p = llListReplaceList(p, [vround(pos, rot)], t / pSTRIDE, t / pSTRIDE); + Poses = llListReplaceList(Poses, [llDumpList2String(p, "|")], i + psPOSROT, i + psPOSROT); + updateSitter(them); + s(id, llKey2Name(id) + " adjusts " + llKey2Name(them) + " by " + (string) dist + " " + direction); + if (id != them) + s(them, llKey2Name(id) + " adjusts " + llKey2Name(them) + " by " + (string)dist + " " + direction); + } + } + } +} + +updateSitter(key id) +{ // Written by Strife Onizuka, size adjustment provided by Talarus Luan + // Using this while the object is moving may give unpredictable results. + integer f = findSitter(id); + if (-1 != f) + { + integer lnk = llList2Integer(Sitters, f + pLINK); + if (-1 == lnk) return; + vector pos = position; + rotation rot = llEuler2Rot(rotat * DEG_TO_RAD); + integer i = findPose(Pose); + if (-1 != i) + { + string prn = llList2String(llParseStringKeepNulls(llList2String(Poses, i + psPOSROT), ["|"], []), f / pSTRIDE); + integer ix = llSubStringIndex(prn, ">"); +// TODO - might be wrong, coz rotations are hard, m'kay. + pos = (vector) llGetSubString(prn, 0, ix); + rot = llEuler2Rot((vector) llGetSubString(prn, ix + 1, -1) * DEG_TO_RAD); + } + vector size = llGetAgentSize(id); + integer prim = llGetLinkNumber(); + // We need to make the position and rotation local to the current prim. + vector localpos = ZERO_VECTOR; + rotation localrot = ZERO_ROTATION; + + if (1 < prim) // Only need the local rot if it's not the root. + { + list local = llGetLinkPrimitiveParams(prim, [PRIM_POS_LOCAL, PRIM_ROT_LOCAL]); + localpos = llList2Vector(local, 0); + localrot = llList2Rot(local, 1); + } + pos += <0.0, 0.0, 0.2>; // Fudge it. Pffft + // <0.008906, -0.049831, 0.088967> are the coefficients for a parabolic curve that + // best fits real avatars. It is not a perfect fit. + float fAdjust = ((((0.008906 * size.z) + -0.049831) * size.z) + 0.088967) * size.z; + vector fa = llRot2Up(rot) * fAdjust; + llSetLinkPrimitiveParamsFast(lnk, + [ + PRIM_POS_LOCAL, ((pos - fa) * localrot) + localpos, + PRIM_ROT_LOCAL, rot * localrot // This does rotate around the avatar's centre, not the prims centre. + ]); + } +} + +showMenu(key id) +{ + integer f = findSitter(id); + if (-1 != f) + { + key them = llList2Key(Sitters, f + pADJ); + string name; + integer distance = llList2Integer(Sitters, f + pDIST); + if ("" == them) + { + them = id; + Sitters = llListReplaceList(Sitters, [them], f + pADJ, f + pADJ); + s(id, "Switched to adjusting mode. Click '▲ Exit' on the adjusting menu to switch back to moving."); + } + if (ScriptKey == them) + name = "all"; + else + name = llKey2Name(them); + sendScript(lMENU, [id, "adjust", "Adjusting position of " + name + + ". \nAdjust by " + llList2String(Distances, distance) + + ", \nusing menu or movement keys."]); + } +} + +integer vMODE; +integer vNONE = 0; +integer vFWD = 1; +integer vLEFT = 2; +integer vUP = 3; +integer vCROUCH = 4; +integer vGROUND = 5; +integer vFALL = 6; +float vTHEN; + +float LastCast; +integer CastOut; +float cast(vector gp, vector pos, float dist) +{ + vector start = gp + <0.0, 0.0, 0.0>; + vector end = pos + <0.0, 0.0, -llFabs(dist * 1.1)>; + gp = pos - gp; + float g = llGround(); + if (0 <= CastOut) + { + list results = llCastRay(start, end, [ + RC_REJECT_TYPES, RC_REJECT_AGENTS | RC_REJECT_PHYSICAL | RC_REJECT_LAND, + RC_DATA_FLAGS, RC_GET_ROOT_KEY, + RC_MAX_HITS, 2] ); + CastOut = llList2Integer(results, -1); + if (0 < CastOut) + { + vector p = llList2Vector(results, 1); + if (llGetKey() == llList2Key(results, 0)) + { + if (1 < CastOut) + { + p = llList2Vector(results, 3); + LastCast = p.z + 0.1; + } + else + LastCast = g + 0.1; + } + else + LastCast = p.z + 0.1; + } + else if (0 == CastOut) + LastCast = g + 0.1; +/* + else + { + if (RCERR_UNKNOWN == CastOut) d("llCastRay() failed for an unspecified reason."); + else if (RCERR_SIM_PERF_LOW == CastOut) d("llCastRay() failed coz sim performance is low."); + else if (RCERR_CAST_TIME_EXCEEDED == CastOut) d("llCastRay() failed coz too many raycasts."); + else d("llCastRay() returned unknown error code " + CastOut); + } +*/ + } + else CastOut += 1; + if (g > LastCast) LastCast = g + 0.1; + return LastCast - 0.1; +} + +updateVehicle(integer keys, integer mode, float move, float rotate) +{ + vector pos = llGetPos(); + vector gp = pos; + vector rotvec = llRot2Euler(llGetRot()); + if (Attached || (0.0 == LeaderOffset)) return; + vAnim = "Standing"; + if (0.0 < vTHEN) + { + if (3.0 > (llGetTimeOfDay() - vTHEN)) + { + pos.z = cast(gp, pos, 4.0) + LeaderOffset; + llSetPrimitiveParams([PRIM_POSITION, pos]); + return; + } + } + if (vUP == vMODE) + { + if (vNONE == mode) vAnim = "Hovering"; + else if (vFWD == mode) + { + vAnim = "Flying"; + pos += (<(llCos(rotvec.z)) * move, (llSin(rotvec.z)) * move, 0.0>); + } + else if (vUP == mode) + { + pos += (<0.0, 0.0, move>); + if (0.0 < move) vAnim = "Hovering Up"; + else vAnim = "Hovering Down"; + } + if (cast(gp, pos, move / Smooth) > (pos.z - LeaderOffset)) + { + vAnim = "Soft Landing"; vMODE = vFWD; + vTHEN = llGetTimeOfDay(); addEvent(3.0, "SYNC Standing"); + } + } + else if (vFALL == vMODE) + { + vAnim = "Falling"; pos.z -= 1.0; + if (cast(gp, pos, move / Smooth) > (pos.z - LeaderOffset)) + { + vAnim = "Standing Up"; vMODE = vFWD; + vTHEN = llGetTimeOfDay(); addEvent(3.0, "SYNC Standing"); + } + } + else if ((vFWD == mode) || (vLEFT == mode) || (vNONE == mode)) + { + if (vLEFT == mode) + { + if (0.0 < move) rotvec.z += 89.0 * DEG_TO_RAD; + else rotvec.z += 91.0 * DEG_TO_RAD; + } + pos += (<(llCos(rotvec.z)) * move, (llSin(rotvec.z)) * move, 0.0>); + if (0.0 != move) + { + float ground = cast(gp, pos, move / Smooth); + if (pos.z > (ground + LeaderOffset * 3.0)) + { + vMODE = vFALL; + pos.z -= 1.0; + } + else pos.z = ground + LeaderOffset; + } + if (vMODE == vFALL) vAnim = "Falling"; + else if (vCROUCH == vMODE) + { + if (0.0 == move) vAnim = "Crouching"; + else vAnim = "CrouchWalking"; + } + else if (vGROUND == vMODE) + vAnim = "Sitting on Ground"; + else + { + if (0.0 != rotate) + { + if (0.0 < rotate) vAnim = "Turning Left"; + else vAnim = "Turning Right"; + } + if (0.0 == move) vAnim = "Standing"; + else vAnim = "Walking"; + } + } + else if (vUP == mode) + { + if (keys & CONTROL_UP) + { + if (vGROUND == vMODE) {vAnim = "Crouching"; vMODE = vCROUCH;} + else if (vCROUCH == vMODE) {vAnim = "Standing"; vMODE = vFWD;} + else {pos += (<0.0, 0.0, move>); vMODE = vUP;} + } + else + { + if (vCROUCH == vMODE) {vAnim = "Sitting on Ground"; vMODE = vGROUND;} + else if (0.0 > move) {vAnim = "Crouching"; vMODE = vCROUCH;} + else {pos += (<0.0, 0.0, move>); vMODE = vUP;} + } + } + // This is useful at least, not tested on a var region yet, but should work, that'll be why it exists. + vector rs = osGetRegionSize(); + vector cr = pos - gp; + integer isBorder; + integer isEdge; + if ((pos.x < 0.0) || (pos.x > rs.x) ||(pos.y < 0.0) || (pos.y > rs.y)) + { + isBorder = TRUE; + if (llFabs(cr.x) > llFabs(cr.y)) + { + cr.y = 0.0; + if (0.0 < cr.x) {if (0.3 < cr.x) cr.x = 1.0; else cr.x = 0.0;} + else if (0.0 > cr.x) {if (-0.3 > cr.x) cr.x = -1.0; else cr.x = 0.0;} + } + else + { + cr.x = 0.0; + if (0.0 < cr.y) {if (0.3 < cr.y) cr.y = 1.0; else cr.y = 0.0;} + else if (0.0 > cr.y) {if (-0.3 > cr.y) cr.y = -1.0; else cr.y = 0.0;} + } + isEdge = llEdgeOfWorld(gp, cr); + integer l = llGetListLength(Sitters); + integer i; + for (i = 0; i < l; i += pSTRIDE) + { + key a = llList2Key(Sitters, i); + key t = llList2Key(Sitters, i + pADJ); + if (isEdge) + { + Sitters = llListReplaceList(Sitters, [a], i + pADJ, i + pADJ); + adjust(a, move, "forward"); + Sitters = llListReplaceList(Sitters, [t], i + pADJ, i + pADJ); + } + else + llSetLinkPrimitiveParamsFast(llList2Integer(Sitters, i + pLINK), + [PRIM_POS_LOCAL, <-1.0, 0.0, -0.0001>]); + } + } + if (isEdge) + d("Can't go there, you'll fall off the edge!"); + else + { + if (isBorder) + { + llWhisper(0, "Can't walk into next sim, teleporting slowly instead."); + cr = llGetRegionCorner() + pos + ((pos - gp) * 2.0); + string data = (string)cr.x + "/" + (string)cr.y + "/" + (string)cr.z; + sendPrim(BossKey, "TPRIM", [data]); + LeaderOffset = 0.0; + addEvent(1.5, "TPRIM " + (string)cr.x + "/" + (string)cr.y + "/" + (string)cr.z); + } + else + { + // This is useful at least for parcel crossing. + integer f = llGetParcelFlags(pos); + if (!f & PARCEL_FLAG_ALLOW_FLY) d("No fly."); + if (!f & PARCEL_FLAG_ALLOW_SCRIPTS) d("No scripts."); + if (!f & PARCEL_FLAG_ALLOW_CREATE_OBJECTS) d("No create object."); + if (!f & PARCEL_FLAG_ALLOW_ALL_OBJECT_ENTRY) d("No object entry."); + if (!f & PARCEL_FLAG_ALLOW_GROUP_SCRIPTS) d("No group scripts."); + if (!f & PARCEL_FLAG_ALLOW_CREATE_GROUP_OBJECTS) d("No group create objects."); + if (!f & PARCEL_FLAG_ALLOW_GROUP_OBJECT_ENTRY) d("No group object entry."); + if ((f & PARCEL_FLAG_ALLOW_SCRIPTS) && (f & PARCEL_FLAG_ALLOW_ALL_OBJECT_ENTRY)) + llSetLinkPrimitiveParamsFast(LINK_THIS, [PRIM_POSITION, pos, PRIM_ROTATION, + llEuler2Rot(<0.0, 0.0, rotate>) * llGetRot()]); + } + } + checkAO(); +} + +newLeader(key id) +{ + Leader = id; + vector bb = llGetAgentSize(Leader); LeaderOffset = (bb.z + 0.3) / 2; + updateVehicle(0, vNONE, 0.0, 0.0); + checkAO(); +d("newLeader " + llKey2Name(Leader) + ", offset " + (string) LeaderOffset); +} + +oldController(key t) +{ + // NOTE - the person just stood up, that automatically revoked the permissions. In theory. + // And sometimes in practice. + if (llGetPermissionsKey() == t) + { + if (llGetPermissions() & PERMISSION_CONTROL_CAMERA) + { + d("releasing camera for " + llKey2Name(t)); + llClearCameraParams(); + } + if (llGetPermissions() & PERMISSION_TAKE_CONTROLS) + { + d("releasing controls for " + llKey2Name(t)); + llReleaseControls(); + } + } + if (t == Controller) + { + addEvent(0.0, "Keys"); + Controller = NULL_KEY; + } +} + +checkSitters(integer del) +{ + list new = []; + integer l = llGetNumberOfPrims(); + integer lnk; + for (lnk = llGetObjectPrimCount(llGetKey()) + 1; lnk <= l; ++lnk) + new += [llGetLinkKey(lnk), lnk]; + l = llGetListLength(Sitters); + for (lnk = 0; lnk < l; lnk += pSTRIDE) + { + key t = llList2Key(Sitters, lnk + pKEY); + if (NULL_KEY != t) + { + integer j = listFindString(new, t, 2); + + if (-1 == j) // old sitter that left + { + d("unsit " + llKey2Name(t)); + stopMe(t, lnk); + oldController(t); + Sitters = llListReplaceList(Sitters, [NULL_KEY], lnk + pKEY, lnk + pKEY); + } + else // old sitter that is still here + new = llListReplaceList(new, [], j, j + 1); + } + } + l = llGetListLength(new) / 2; + for (lnk = 0; lnk < l; lnk +=2) + { + key t = llList2Key(new, lnk); + list n = [t, llList2Integer(new, lnk + 1), "", "", "", "", llGetListLength(Distances) / 3, 0]; + list gndr = llGetObjectDetails(t, [OBJECT_BODY_SHAPE_TYPE]); + stopAnims(t); + integer f = findSitter(NULL_KEY); + if (-1 != f) // new sitter replaces old + Sitters = llListReplaceList(Sitters, n, f, f + pSTRIDE - 1); + else // new sitter added on end + Sitters += n; + if (NULL_KEY != BossKey) sendPrim(BossKey, "SIT_DONE", [t]); + d("sat " + llKey2Name(t) + ", " + llList2String(gndr, 0) + " male @ " + (f / pSTRIDE)); + } + + // Wearer is the default leader, unless anyone is taller. + l = llGetListLength(Sitters); + integer m = l / pSTRIDE; + integer ldr = findSitter(Owner); + float max = 0.0; + if (-1 != ldr) + { + vector s = llGetAgentSize(Owner); + max = s.z; + } + for (lnk = 0; lnk < l; lnk += pSTRIDE) + { + key t = llList2Key(Sitters, lnk + pKEY); + vector s = llGetAgentSize(t); + if (s.z > max) + { + max = s.z; + ldr = lnk; + } + if (NULL_KEY == t) --m; + } + if ((0 != ldr) && (0 != llGetListLength(Sitters))) + { + list s = llList2List(Sitters, ldr, ldr + pSTRIDE - 1); + Sitters = s + llListReplaceList(Sitters, [], ldr, ldr + pSTRIDE - 1); + } + newLeader(llList2Key(Sitters, pKEY)); + llSitTarget(position, llEuler2Rot(rotat * DEG_TO_RAD)); + Controller = NULL_KEY; + for (lnk = 0; lnk < l; lnk += pSTRIDE) + { + updateSitter(llList2Key(Sitters, lnk + pKEY)); + key t = llList2Key(Sitters, lnk + pKEY); + if ((NULL_KEY == Controller) && (Owner != t)) + Controller = t; + } + if (NULL_KEY != Controller) + { + d("checkSitters() requesting camera and controls from " + llKey2Name(Controller)); + llRequestPermissions(Controller, PERMISSION_CONTROL_CAMERA | PERMISSION_TAKE_CONTROLS); + } + llSetSitText(Sit0Text); llSetTouchText("menu"); llSetClickAction(CLICK_ACTION_SIT); + llSetText(HoverText, <1.0, 1.0, 1.0>, 1.0); + if (0 == m) + { + if (del) + { + llSleep(5.0); + d("No one left sitting on me."); + if (NULL_KEY != BossKey) die(); + } + llSetTouchText(""); + llSetLinkAlpha(Link, 0.5, ALL_SIDES); + } + else if (1 == m) llSetLinkAlpha(Link, 0.25, ALL_SIDES); + else if (2 == m) + { + llSetSitText(Sit1Text); llSetClickAction(CLICK_ACTION_TOUCH); + llSetLinkAlpha(Link, 0.1, ALL_SIDES); llSetText("", ZERO_VECTOR, 0.0); + } + return; +} + +stopAnims(key avatar) +{ + if (NULL_KEY != avatar) + { + list anims = llGetAnimationList(avatar); + integer l = llGetListLength(anims); + integer i; + for (i = 0; i < l; i++) + { + string anim = llList2String(anims, i); + if (anim != "") osAvatarStopAnimation(avatar, anim); + } + } +} + +// AO functions. +list States = []; +integer loadCard(string card, integer casperMode) +{ + if (NULL_KEY != llGetInventoryKey(card)) + { + float now = llGetTimeOfDay(); + string section = ""; + integer l = osGetNumberOfNotecardLines(card); + integer i; + States = initialStates; + for (i = 0; i <= l; ++i) + { + string line = osGetNotecardLine(card, i); + string rest = ""; + string name = ""; + integer match = llSubStringIndex(line, "]"); + integer found = -1; + // Ignore anything that does not start with [, and has the matching ]. + // Which is a dirt cheap ZHAO II compatibility. + if (("[" == llGetSubString(line, 0, 0)) && (-1 != match)) + { + name = llStringTrim(llGetSubString(line, 1, match - 1), STRING_TRIM); + if (casperMode) section = name; + else + { + name = alias(name); + rest = llGetSubString(line, match + 1, -1); + // Corner case, no actual anims. + if ((match + 1) == llStringLength(line)) rest = ""; + } + } + // Casper AO has a different format. AOConfig, "[Walking]" on a line by itself, followed by one animation per line. + // I vaguely remember that might be the ZHAO I format. + // Though I doubt if ZHAO I ever made it out of SL, think ZHAO II was too popular at the time. + else if (casperMode) + { + rest = llStringTrim(line, STRING_TRIM); + if ("" != rest) name = section; + } + if ("" != name) + { + found = llListFindList(States, [name]); + if ((0 <= found) && ((found % 2) == 0)) + { + string data = llList2String(States, found + 1); + if ("" != data) data += "|"; + States = llListReplaceList(States, [name, data + rest], found, found + 1); + } + else States += [name, rest]; + } + } + l = llGetListLength(States); + for (i = 0; i < l; i += 2) + { + list nA = llParseStringKeepNulls(llList2String(States, i + 1), ["|"], []); + integer m = llGetListLength(nA); + integer j; + for (j = 0; j < m; ++j) + { + list a = llCSV2List(llList2String(nA, j)); + integer n = llGetListLength(a); + integer k; + for (k = 0; k < n; ++k) + checkAnim(llList2String(a, k)); + } + } + d("Read " + card + " in " + (string) (llGetTimeOfDay() - now) + " seconds."); + return TRUE; + } + return FALSE; +} + +list isPoseAO(integer f) +{ + if ("" != Pose) + { + integer p = findPose(Pose); + if (-1 != p) + { + list ps = llParseStringKeepNulls(llList2String(Poses, p + psANIM), ["|"], []); + if ("~" != llList2String(ps, f / pSTRIDE)) + return ps; + } + } + return []; +} + +checkAO() +{ + string newAnim; + integer l = llGetListLength(Sitters); + integer fast; integer i; integer f; float dpth; + if (NULL_KEY != TheirKey) return; + + if (Attached) + { +// if (llGetAgentInfo(Owner) & AGENT_ALWAYS_RUN) fast = 1; else fast = 0; + newAnim = llGetAnimation(Owner); + vAnim = newAnim; + } + else + { +// TODO - when updateControls() figures out fast mode, use that. + newAnim = vAnim; + } + if (("" == Pose) && ("0" == getSetting("AO"))) + { + stopAnims(Owner); + AOspeed = 1.0; + osSetSpeed(Owner, 1.0); + return; + } + string oldAnim = newAnim; +//d("checkAO() " + newAnim); + AOspeed = 0.0; + integer flying = llListFindList(flyStates, [vAnim]); + if (-1 != flying) + { + float water = llWater(ZERO_VECTOR); + float ground = llGround(ZERO_VECTOR); + integer fly = ("Flying" == vAnim); +// if (fly && !fast) newAnim = "FlyingSlow"; + if (water > ground) // First check if we can even be under water. + { + // In Opensim, pos.z is actually a double, and OpenSim can't do equality for doubles & floats. + vector pos = llGetPos(); float z = pos.z; // Coz OpenSim can't do llGetPos().z + if (z <= water) + { + if (0 > Bobbing) Bobbing++; + Swimming = TRUE; + dpth = water - z; + } + else + { + if (Swimming) + { // That's metres of water depth before it figures you don't have enough to swim in. + if ((z > water) && ((water - ground) > 1.5)) + { // Push you back into the water. + vector velocity = llGetVel(); + dpth = 0.1; + velocity.x = 0.0; velocity.y = 0.0; + if (10 > velocity.z) velocity.z = 10; + velocity.z *= -4; + if ("Swimming Up" == alias("~" +vAnim)) + { + Bobbing++; + if (3 < Bobbing) + { // Switch to flying. + Bobbing = 0; + Swimming = FALSE; + } + } +// TODO - this wont work in vehicle mode. + if (Swimming) + llApplyImpulse(llGetMass() * velocity, FALSE); + } + else // Switch to "walking" mode. + Swimming = FALSE; + } + } + } // No water to swim in here. + else Swimming = FALSE; + } + else // Not in fly mode. + Swimming = FALSE; +// llParticleSystem([]); + if (Swimming) + { + if (Attached) AOspeed = 0.1; + else AOspeed = 0.5; + newAnim = alias("~" + newAnim); +// updateParticles(); + } + else if (-1 != flying) + { + if (Attached) AOspeed = 1.0; + else AOspeed = 4.0; + } + else AOspeed = 1.0; + if (0.0 < AOspeed) osSetSpeed(Owner, AOspeed + ((AOspeed / 2) * (fast + (2 * (integer) getSetting("super"))))); + else AOspeed = 1.0; + for (f = 0; f < l; f += pSTRIDE) + { + string anim = llList2String(Sitters, f + pSTATE); + list anims = llCSV2List(llList2String(Sitters, f + pANIMS)); + integer g; + list states = States; + key id = llList2Key(Sitters, f + pKEY); + list ps = isPoseAO(f); + if (0 != llGetListLength(ps)) + { + states = [Pose, llList2String(ps, f / pSTRIDE)]; + newAnim = Pose; + } +//d("checkAO " + llKey2Name(id) + " " + anim + " -> " + newAnim + " @ " + (string) AOspeed + " " + Pose); + if (newAnim != anim) + { + g = listFindString(states, newAnim, 2); + if (-1 != g) + { + stopMe(id, f); + // Ignore sits, since 99.99% of the time what they are sitting on supplies an animation. + if ((("Sitting" == vAnim) && Attached) || ("" == llList2String(states, g + 1))) + ; + else + { + list newAnims = llParseString2List(llList2String(states, g + 1), ["|"], []); + // If there's more than one, randomly switch between them at random times. + i = llGetListLength(newAnims); + if (1 < i) addEvent(20.0 + llFrand(20.0), "SYNC R"); + i = (integer) llFrand((float) i); + // ZHAO II also allows multiple anims in one set, comma separated. Good idea. + anims = llCSV2List(llList2String(newAnims, i)); +//d("ANIMS " + llKey2Name(id) + " " + newAnim + " -> " + llList2String(newAnims, i)); + for (i = llGetListLength(anims) - 1; i >= 0; --i) + { + string a = llList2String(anims, i); + if (checkAnim(a)) osAvatarPlayAnimation(id, a); + } + } + } + anim = newAnim; + } + Sitters = llListReplaceList(Sitters, [anim, anim, llDumpList2String(anims, ",")], f + pSTATE, f + pANIMS); + newAnim = oldAnim; + } + if (("" == Pose) || ("Swimming Up" == vAnim)) addEvent(Tick * dpth, "checkAO"); +} + +stopMe(key id, integer f) +{ + list anims = llCSV2List(llList2String(Sitters, f + pANIMS)); + integer i; +//d("stopMe " + llKey2Name(id) + " " + llList2String(Sitters, f + pANIMS)); + for (i = llGetListLength(anims) - 1; i >= 0; --i) + osAvatarStopAnimation(id, llList2String(anims, i)); +} + +checkLag() +{ + float dil = llGetRegionTimeDilation(); // Between 0 and 1. + float fps = llGetRegionFPS(); // Frames per second, up to 50. + integer newLag = (integer) (dil * fps); + if (llAbs(Lag - newLag) > 9) + { + string l; + Lag = newLag; + Tick = ((60 - (dil * fps)) / 30) + 0.15; + if (45 <= newLag) l = "No"; + else if (35 <= newLag) l = "A little"; + else if (25 <= newLag) l = "Medium"; + else if (15 <= newLag) l = "Lots of"; + else l = "Way too much"; + if (!osIsNpc(llGetOwner())) s(Owner, l + " lag, tick is " + (string) Tick); + } +} + +init() +{ + ANIMATIONS = readCard("animations"); flyStates = readCard("flystates"); initialStates = readCard("states"); + Attached = (0 != llGetAttached()); + llSetSitText("NO SIT"); llSetTouchText("menu"); + llSetClickAction(CLICK_ACTION_TOUCH); llSetText("", ZERO_VECTOR, 0.0); + Link = llGetLinkNumber(); +// None of this works in OpenSim? +// llSetBuoyancy(-2.0); +// llSetHoverHeight(10.0, TRUE, 2.0); +// llGroundRepel(10.0, TRUE, 2.0); + checkLag(); +} + +laterInit() +{ + if (!loadCard("ZHAO II", FALSE)) // In an object full of random scripts, "Default" is a lousy name. + if (!loadCard("Default", FALSE)) + loadCard("AOConfig", TRUE); // Casper AOs are less common. + if (Attached) + { + d("AO mode."); + Sitters = [Owner, -1, "", "", "", "", llGetListLength(Distances) / 3, 0]; + checkAO(); + } + else + { + if (NULL_KEY == BossKey) + d("Vehicle mode."); + else + d("Vehicle mode, slaved to " + llKey2Name(BossKey) + "."); + integer i = llGetInventoryNumber(INVENTORY_NOTECARD); + list posCards = []; + string item; + while (i-- > 0) + { + item = llGetInventoryName(INVENTORY_NOTECARD, i); + if (llSubStringIndex(item, ".POSITIONS") == 0) posCards += (list) item; + } + readPos(posCards); + d("Loaded " + (string) (llGetListLength(Poses) / psSTRIDE) + " positions in " + + (string) (llGetTimeOfDay() - Start) + " seconds."); + checkSitters(FALSE); + if (NULL_KEY != BossKey) sendPrim(BossKey, "REZ_DONE", []); + } + addEvent(6.0 + llFrand(10.0), "SMILE"); +} + +die() +{ + integer l = llGetListLength(Sitters); + integer f; + for (f = 0; f < l; f += pSTRIDE) + stopMe(llList2Key(Sitters, f + pKEY), f); + d("Deleting myself."); + sendPrim(BossKey, "DIE_DONE", []); + if (PERM_COPY & llGetObjectPermMask(MASK_OWNER)) llDie(); + else s("This no copy object wont delete itself, please delete manually, or take into inventory."); +} + +default +{ + state_entry() + { + llSleep(0.2); + Start = llGetTimeOfDay(); + Owner = llGetOwner(); +d("\n\n1AOor2 sending RESET @ " + (string) Start + "\n"); + ScriptName = llGetScriptName(); ScriptKey = llGetInventoryKey(ScriptName); + LibraryKey = NULL_KEY; + sendScript(lRESET, []); + init(); + if (bREZ == llGetStartParameter()) BossKey = osGetRezzingObject(); + } + + control(key id, integer level, integer edge) {doControl(id, level, edge);} + link_message(integer sender, integer num, string message, key id) {linky(num, message, id);} + + changed(integer change) + { + if (change & CHANGED_LINK) checkSitters(TRUE); + if (change & CHANGED_ANIMATION) checkAO(); + if ((change & CHANGED_TELEPORT) || (change & CHANGED_REGION)) + { + if (99.0 > TPangle) + { + PIN = (integer) llFrand(DEBUG_CHANNEL - 2) + 2; + vector RefPos = llGetPos(); + rotation RefRot = llGetRot(); + llRezObject("1AOor2 prim", ZERO_VECTOR * RefRot + RefPos, ZERO_VECTOR, (ZERO_ROTATION) * RefRot, PIN); + llOwnerSay("@setrot:" + (string) TPangle + "=force"); + s(Owner, "Switching AO to 1ring object."); + oldController(Owner); + TPangle = 999.0; + Chosen = 1; + } + } + } + + run_time_permissions(integer perm) + { + if (PERMISSION_TAKE_CONTROLS & perm) + { + key id = llGetPermissionsKey(); + d("taking controls from " + llKey2Name(id)); + llTakeControls( CONTROL_FWD | CONTROL_BACK | CONTROL_LEFT | CONTROL_RIGHT | + CONTROL_ROT_LEFT | CONTROL_ROT_RIGHT | CONTROL_UP | CONTROL_DOWN, TRUE, FALSE); + ControlTime = llGetTimeOfDay(); + addEvent(Tick * Smooth, "Keys"); + } + if (PERMISSION_CONTROL_CAMERA & perm) + { + lookAtMe(llGetPermissionsKey()); + if ((llGetPermissionsKey() != Controller) && (NULL_KEY != Controller)) + { + d("PERMISSION_CONTROL_CAMERA requesting camera and controls from " + llKey2Name(Controller)); + llRequestPermissions(Controller, PERMISSION_CONTROL_CAMERA | PERMISSION_TAKE_CONTROLS); + } + } + } +} diff --git a/1chatter.lsl b/1chatter.lsl new file mode 100644 index 0000000..fdad263 --- /dev/null +++ b/1chatter.lsl @@ -0,0 +1,1597 @@ + +// Library of generic functions for other scripts to use. + +string Version = "1chatter v0.1 dev version"; + +// BEGIN boilerplate. +integer DEBUG = FALSE; +float Start; +string ScriptName; +key ScriptKey; +key LibraryKey; +key Owner; +string URL; + +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 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);} +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); +} +sendPrim(key them, string cmd, list args) {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 += "…"; + 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) +{ +//d("dynamicMenu " + menu + " -> " + name + "\n" + id + "@" + name); + saveMenu(id + "@" + name, title, aALL, entries, command, TRUE); + 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); +} + +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); + + if (access(id, menu + " menu", fr, llList2Integer(Menus, m + mAUTH), TRUE)) + { + integer l = llGetListLength(entries); + integer n; + + if (llList2Integer(Menus, m + mDYN)) + { + for (; n < l; ++n) + { + string button = llGetSubString(llList2String(entries, n), 0, 23); + 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); + entries = llListReplaceList(entries, [t], n, 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); + 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); + entries = llListReplaceList(entries, [t], n, n); + } + } + string mn = menu; + if (dM) + mn = llGetSubString(mn, 37, -1); + + if (l > 11) + { + if (-1 == page) + page = 1; + integer offset = page * 9; + llDialog(id, version + " - " + mn + "\n\n" + 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, version + " - " + mn + "\n\n" + 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 e = 0; + +//d("handleMenu(" + llKey2Name(id) + "," + button + ") " + menu + " -> " + cmd); + if (llList2Integer(Menus, m + mDYN)) + 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) + lastMenu(id, dM); + return i; + } + + 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 + { + 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); + llResetOtherScript(s); + } + } + } + llListen(DEBUG_CHANNEL, "", NULL_KEY, ""); + VelTime = llGetTimeOfDay(); + VelPos = llGetPos(); + addEvent(300, "Musers", 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; + 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) + { + 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); + readTheme(fr); + 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 + "."); + 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 (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); + if ("" != menu) + menu = fr + "." + menu; + dynamicMenu(llList2Key(input, 2), menu, fr + "." + llList2String(input, 4), + llList2String(input, 5), llList2String(input, 6), llList2String(input, 7)); + } + 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)); + // 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; + } + } + + // Catch OhSillyThreatLevel messages. + if (channel == DEBUG_CHANNEL) + { + key root = llList2Key(llGetObjectDetails(id, [OBJECT_ROOT]), 0); + if ((llGetLinkKey(LINK_ROOT) == root) && ("OSSL Runtime Error: os" == llGetSubString(message, 0, 21))) + { + string function = llGetSubString(message, 20, -1); + integer f = llSubStringIndex(function, " "); + if (-1 != f) + { + if (" permission denied. Script creator is not in the list of users allowed to execute this function and prim owner also has no permission." == llGetSubString(function, f, -1)) + { + llMessageLinked(LINK_SET, DEBUG_CHANNEL, llGetSubString(function, 0, f - 1), id); + llRegionSay(DEBUG_CHANNEL, "OPENSIM SUCKS!"); + 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, "|")); + } + } + + 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); + + 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, " ")); + 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); + } + } + } +} diff --git a/1ring.lsl b/1ring.lsl new file mode 100644 index 0000000..176e706 --- /dev/null +++ b/1ring.lsl @@ -0,0 +1,1424 @@ + +// 1ring written by onefang rejected 2019. +// A simple, generic RLV based "collar". + +string Version = "1ring v0.1 test version"; + +// BEGIN boilerplate. +integer DEBUG = FALSE; +float Start; +string ScriptName; +key ScriptKey; +key LibraryKey; +key Owner; +string URL; + +// Settings. +list Aliases; +list Settings; +integer sNAME = 0; +integer sTYPE = 1; +integer sVALUE = 2; +integer sAUTH = 3; +integer sSTRIDE = 4; + +// 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. + +// 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 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; + +// OhSillyThreat detector +list OhSillyThreats = []; +list OhSillyTreats = // OpenSim threat level. +[ + "osKey2Name", // low +// "osGetAvatarList", // none +// "osGetNotecard", // very high (describes what they where when making this decision) +// "osMakeNotecard", // high (describes what they where when making this decision) +// "osGetRezzingObject", // none + "osMessageObject", // low +// "osAvatarPlayAnimation", // very high +// "osAvatarStopAnimation", // very high +// "osForceOtherSit", // very high + "osSetSpeed" // moderate +]; + +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);} +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); +} +sendPrim(key them, string cmd, list args) +{ + osMessageObject(them, llDumpList2String([ScriptName, cmd] + args, lSEP)); +} +addEvent(float delay, string cmds) +{ + sendScript(lCMD, [fINT, ScriptKey, "TimerEvent", "TIMER", ((string) delay) + " " + cmds]); +} + +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 listFindString(list lst, string name, integer stride) +{ + integer f = llListFindList(lst, [name]); + integer ix = f / stride; + ix = ix * stride; + 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; +} + +// Note these next two are different from the ones in 1chatter. +string alias(string n) +{ + list v = validateName(n, "alias"); + n = llList2String(v, 1); + integer a = listFindString(Aliases, llList2String(v, 0) + llToLower(n), 2); + if (-1 != a) n = llList2String(Aliases, a + 1); + return n; +} + +list validateName(string var, string type) +{ + list v = llParseStringKeepNulls(var, ["."], []); + if (2 != llGetListLength(v)) + v = ScriptName + v; + var = llList2String(v, 1); + if ("setting" == type) + var = llToUpper(var); + return [llList2String(v, 0) + ".", var]; +} + +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; +} + +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) + { + Settings = llListReplaceList(Settings, [val], f + sVALUE, f + sVALUE); + if (llGetSubString(fr, 0, -2) == ScriptName) + { + if (fINT != source) + { + doThing(id, "SET " + var + "=" + val, fr, llList2String(v, 1), val, fINT); + sendScript(lSETTING, [source, id, llList2String(v, 1), val]); + } + } + } +} + +dumpSettings(list settings, string title) +{ + integer l = llGetListLength(settings); + integer i; + + d("v--------------- " + title); + for (i = 0; i < l; i += sSTRIDE) + { + d( + llList2String(settings, i + sNAME) + "~" + + llList2String(settings, i + sTYPE) + "~" + + llList2String(settings, i + sVALUE) + "~" + + llList2String(settings, i + sAUTH) + ); + } + d("^--------------- " + ScriptName); +} + +doSettings(key id, list settings) +{ + integer l = llGetListLength(settings); + integer i; + + for (i = 0; i < l; i += sSTRIDE) + { + string var = llList2String(settings, i + sNAME); + list v = validateName(var, "setting"); + string fr = llList2String(v, 0); + var = llList2String(v, 1); + string val = llList2String(settings, i + sVALUE); + doThing(id, "SET " + var + "=" + val, fr, var, val, fINT); + } +} + +dynamicMenu(key id, string menu, string name, string title, string entries, string command) +{ + sendScript(lDYNAMIC, [id, menu, name, title, entries, command]); +} + +linky(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); + if (lCMD == num) + { +//d("linky lCMD " + llDumpList2String(input, " ~ ")); + if ((fr == (ScriptName + ".")) || (fr == "*.")) + { + key a = llList2Key(input, 3); + string button = llList2String(input, 4); + integer r = doThing(a, button, fr, + llList2String(input, 5), llList2String(input, 6), llList2Integer(input, 2)); + sendScript(lCMD_DONE, [button, a, r]); + } + } + else if (lRESET_DONE == num) + { +d("linky RESET_DONE"); + LibraryKey = them; + Settings = llList2List(input, 2, -1); + + // NOT boilerplate + sendRLV("versionnew", 10.0); + string f = llToLower(llKey2Name(Owner)); + string prefix = llGetSubString(f, 0, 0); + integer i = llSubStringIndex(f, " "); + if (-1 != i) + { + string l = llGetSubString(f, i + 1, -1); + if ("Resident" == l) + prefix += llGetSubString(f, 1, 1); + else + prefix += llGetSubString(f, i + 1, i + 1); + } + setSetting(ScriptKey, ScriptName + ".PREFIX", prefix, fCARD); + // NOT boilerplate + + setSetting(ScriptKey, ScriptName + ".VERSION", Version, fCARD); + sendScript(lSETTINGS, []); + } + else if (lALIAS_DONE == num) + Aliases = llList2List(input, 2, -1); + else if (lSETTINGS_DONE == num) + { + Settings = llList2List(input, 2, -1); + doSettings(id, Settings); + laterInit(); + s("Finished starting up " + getSetting("VERSION") + " in " + (string) (llGetTimeOfDay() - Start)); + } + else if (DEBUG_CHANNEL == num) + { + key root = llList2Key(llGetObjectDetails(id, [OBJECT_ROOT]), 0); + integer f = llListFindList(OhSillyThreats, [message]); + + if (-1 == f) + OhSillyThreats += [message]; + else + d("OhSillyThreats detected the function " + message + "() again!" ); + s("Oh Silly threat system prevented " + message + "()" + + "\n in " + id + " \t" + llKey2Name(id) + + "\n part of " + root + " \t" + llKey2Name(root)); + } +} +// END boilerplate, mostly. + +showAccess(key id) +{ + list bosses = llParseString2List(getSetting("boss"), [","], []); + list trusts = llParseString2List(getSetting("trustee"), [","], []); + list blocked = llParseString2List(getSetting("blocked"), [","], []); + integer l = llGetListLength(bosses); + integer i; + string report = "Access - group "; + + for (i = 0; i < l; ++i) + s(id, osKey2Name(llList2String(bosses, i)) + " is a " + alias(ScriptName + ".boss") + "."); + l = llGetListLength(trusts); + for (i = 0; i < l; ++i) + s(id, osKey2Name(llList2String(trusts, i)) + " is a " + alias(ScriptName + ".trustee") + "."); + l = llGetListLength(blocked); + for (i = 0; i < l; ++i) + s(id, osKey2Name(llList2String(blocked, i)) + " is " + alias(ScriptName + ".blocked") + "."); + if ("0" == getSetting("group")) + report += "off"; + else + report += "on"; + report += ", public "; + if ("0" == getSetting("public")) + report += "off"; + else + report += "on"; + s(id, report + ". The command prefix is /" + getSetting("channel") + getSetting("prefix") + "."); +} + +integer changeAccess(key id, string cmd, key person, integer source) +{ + list tu = llParseString2List(cmd, ["_"], []); + string t = llList2String(tu, 0); + string u = llList2String(tu, 1); + string lW = "boss"; + string eW = "a boss"; + string mW = lW; + string c = "_" + u; + + if ("BLOCKED" == u) + { + lW = "blocked"; + eW = lW; + mW = "blockee"; + } + else if ("TRUSTEE" == u) + { + lW = "trustee"; + eW = "trusted"; + mW = lW; + } + + list lst = llParseString2List(getSetting(lW), [","], []); + list r; + integer l; + integer i; + if (("ADD" == t) || ("NEW" == t)) + { + list b = llGetAgentList(AGENT_LIST_REGION, []); + l = llGetListLength(b); + for (i = 0; i < l; ++i) + { + if (-1 == listFindString(lst, llList2String(b, i), 1)) + r += [llList2String(b, i)]; + } + lst = r; + r = []; + } + l = llGetListLength(lst); + for (i = 0; i < l; ++i) + { + key k = llList2String(lst, i); + if (!osIsNpc(k)) + { + r += osKey2Name(k); + if ("NEW" == t) + r += k; + } + } + + if ("ADD" == t) + { + if (0 == llGetListLength(r)) + { + s(id, "No one here that isn't already " + eW + "."); + return TRUE; + } + else + dynamicMenu(id, "Access", cmd, "Select a new " + mW, llDumpList2String(r, "|"), "NEW" + c); + } + else if ("DEL" == t) + { + if (0 == llGetListLength(r)) + { + s(id, "There is no one on your " + eW + " list."); + return TRUE; + } + else + dynamicMenu(id, "Access", cmd, "Select a " + mW + " to be removed.", llDumpList2String(r, "|"), "OLD" + c); + } + else if ("NEW" == t) + { + integer f = llListFindList(r, [person]); + if (-1 != f) + { + person = llList2Key(r, f + 1); + lst = llParseString2List(getSetting(lW), [","], []) + [person]; + setSetting(id, lW, llDumpList2String(lst, ","), source); + if ("BLOCKED" == u) + s(person, "You are now blocked from using " + llKey2Name(Owner) + + " " + llKey2Name(llGetKey()) + "."); + else if ("BOSS" == u) + s(person, "You are now one of the parental units for " + llKey2Name(Owner) + + ". Remember kids are for life, not just for Christmas."); + else if ("TRUSTEE" == u) + s(person, "You are now trusted by " + llKey2Name(Owner) + "."); + s(id, "You added a " + mW + " - " + osKey2Name(person)); + } + else + s("Can't find " + eW + " " + person); + } + else if ("OLD" == t) + { + integer f = llListFindList(r, [person]); + if (-1 != f) + { + person = llList2Key(lst, f); + lst = llListReplaceList(lst, [], f, f); + setSetting(id, lW, llDumpList2String(lst, ","), source); + if ("BOSS" == u) + s(person, llKey2Name(Owner) + " divorces you, but keeps the cat."); + else + s(person, "You are no longer " + mW + " by " + llKey2Name(Owner) + "."); + s(id, "You removed a " + mW + " - " + osKey2Name(person)); + } + else + s("Can't find " + eW + " " + person); + } + showAccess(id); + return FALSE; +} + +integer doThing(key id, string button, string fr, string cmd, string data, integer source) +{ + if ("SET " == llGetSubString(button, 0, 3)) + { + integer set = listFindString(Settings, fr + cmd, sSTRIDE); + if (-1 != set) + setSetting(id, fr + cmd, data, fINT); + } + if ((fr != (ScriptName + ".") && ("*." != fr))) return TRUE; + + integer f; + string menu; + f = llSubStringIndex(button, "->"); + if (-1 != f) + { + menu = llGetSubString(button, 0, f - 1); + button = llGetSubString(button, f + 2, -1); + } + +//d("doThing " + menu + " ... " + button + " -> " + fr + " . " + cmd + " = " + data); + if ("MENU" == llToUpper(cmd)) + { + sendScript(lMENU, [id, "main", ""]); + return FALSE; + } + else if (("PANIC" == cmd) || ("RUNAWAY" == cmd)) + { +llSay(0, "I'm a teapot, I'm a teapot!"); + setSetting(id, "public", "0", source); + setSetting(id, "hide", "0", source); + setSetting(id, "lock", "0", source); + setSetting(id, "relay", "0", source); + justRLV("clear"); + if ("RUNAWAY" == cmd) + { +llShout(0, "AAAHHHHH!"); + setSetting(id, "boss", "", source); + setSetting(id, "trustee", "", source); + } + llResetScript(); + } + else if ("HIDE" == cmd) + { + if ("0" == data) + llSetLinkAlpha(LINK_SET, 1.0, ALL_SIDES); + else + llSetLinkAlpha(LINK_SET, 0.0, ALL_SIDES); + } + else if ("LOCK" == cmd) + { + if ("0" == data) + justRLV("detach=y"); + else + justRLV("detach=n"); + } + else if ("CHOOSE_ALL" == cmd) + { + list s; + if ("Follow…" == button) + s = [AGENT, "Select someone to follow", "FOLLOW"]; + else if ("Go to…" == button) + s = [ACTIVE | AGENT | PASSIVE, "Select something or someone to go to", "GOTO"]; + else if ("Leash…" == button) + s = [ACTIVE | AGENT | PASSIVE, "Select something or someone to leash to", "LEASH"]; + else if ("Sit…" == button) // This will only be for objects, can't sit on an avatar. + s = [ACTIVE | PASSIVE, "Select something to sit on", "SIT"]; + else + { + D("Unknown CHOOSE_ALL option - " + button); + return FALSE; + } + sendScript(lSCAN, [id] + s); + return FALSE; + } + else if ("FOLLOW" == cmd) + { + s("Following " + llKey2Name(data)); + mode = MODE_FOLLOW; + goto(data); + unleash(); + } + else if ("GOTO" == cmd) + { + s("Going to " + llKey2Name(data)); + mode = MODE_SINGLE; + goto(data); + unleash(); + } + else if ("GOTO_TICK" == cmd) + { + if ((MODE_FOLLOW == mode) || (MODE_LEASH == mode) || (MODE_SINGLE == mode) || (MODE_SCAN == mode)) + goto(Stalkee); + } + else if ("FREE" == cmd) + { +s("YAY! Freeeeeee at last!!!!!"); + stopGoto(TRUE); + llReleaseControls(); + } + else if ("STAY" == cmd) + llRequestPermissions(llGetOwner(), PERMISSION_TAKE_CONTROLS); + else if ("STOP" == cmd) + stopGoto(TRUE); + else if ("GRAB" == cmd) + return doThing(id, llKey2Name(id), fr, "LEASH", id, source); + else if ("LEASH" == cmd) + { + if (("SET " != llGetSubString(button, 0, 3)) && ("" != data)) + { + s("Leashing you to " + llKey2Name(data)); + mode = MODE_LEASH; + goto(data); + unleash(); + leashTo(Stalkee); + } + } + else if ("RELEASE" == cmd) + { + someBoss = id; + stopGoto(TRUE); + } + else if ("SIT" == cmd) + { + s("Sitting on " + llKey2Name(data)); + justRLV("sit:" + data + "=force"); + } + else if ("STAND" == cmd) + justRLV("unsit=force"); + else if (("ADD_BLOCKED" == cmd) || ("ADD_BOSS" == cmd) || ("ADD_TRUSTEE" == cmd) + || ("DEL_BLOCKED" == cmd) || ("DEL_BOSS" == cmd) || ("DEL_TRUSTEE" == cmd) + || ("NEW_BLOCKED" == cmd) || ("NEW_BOSS" == cmd) || ("NEW_TRUSTEE" == cmd) + || ("OLD_BLOCKED" == cmd) || ("OLD_BOSS" == cmd) || ("OLD_TRUSTEE" == cmd)) + return changeAccess(id, cmd, button, source); + else if ("LIST_BOSS" == cmd) + showAccess(id); + else if ("CHOOSE_CLOTHES" == cmd) + { + someBoss = id; + sendRLV("getoutfit"); + return FALSE; + } + else if ("TAKEOFF" == cmd) + justRLV("remoutfit:" + button + "=force"); + else if ("CHOOSE_OUTFIT" == cmd) + { + someBoss = id; + sendRLV("getinv"); + return FALSE; + } + else if ("PUTON" == cmd) + { + string type = llGetSubString(button, 0, 1); + + if ("+ " == type) + { + s("Adding contents of #RLV/" + llGetSubString(button, 2, -1)); + justRLV("attachallover:" + llGetSubString(button, 2, -1) + "=force"); + } + else if ("- " == type) + { + s("Removing contents of #RLV/ " + llGetSubString(button, 2, -1)); + justRLV("detachall:" + llGetSubString(button, 2, -1) + "=force"); + } + else + { +// TODO - Doesn't seem to be a way of detaching everything else, +// this just replaces anything the new outfit covers. +// Might be better to show sub folders instead. + s("Replacing outfit with contents of #RLV/ " + button); + justRLV("attachall:" + button + "=force"); + } + } + else if ("CHOOSE_ATTACH" == cmd) + { + someBoss = id; + sendRLV("getattach"); + return FALSE; + } + else if ("DETACH" == cmd) + justRLV("detach:" + button + "=force"); + else if ("RLV_OUT" == cmd) + { + gotRLV(llList2Integer(llParseString2List(data, [" "], []), 0), FALSE, ""); + } + else if (("REGION" == cmd) || ("TELEPORT" == cmd)) + { + list bdy = llParseStringKeepNulls(llUnescapeURL(data), ["|"], []); + TPtDestination = bdy; +// TODO - should validate these parameters. +// TODO - once the stuck detector is in place, we'll know where they used to be, +// try to TP to a similar distance away, in a similar direction. +// Or at least to half the follow distance away. + s("Teleporting you to " + osKey2Name(Stalkee) + + " at " + llList2String(TPtDestination, 1) + " " + llList2String(TPtDestination, 2)); + osTeleportAgent(Owner, llList2String(TPtDestination, 1), llList2Vector(TPtDestination, 2), <1.0,1.0,1.0>); + } + else if ("TESTS" == cmd) + s("selected test " + data); + else if ("URL" == cmd) + { + URL = data; + if (NULL_KEY != LeashKey) + osMessageObject(LeashKey, "URL|" + data); +d("New URL " + URL); + } + else if ("▲" == cmd) + ; + else if (-1 == listFindString(Settings, fr + cmd, sSTRIDE)) + { + if (fMENU == source) + d("Unknown menu command '" + cmd + "' from -\n\t" + button); + else + d("Unknown command '" + cmd + "' from -\n\t" + button); + } + return (source == fMENU); +} + + +// General variables +integer LMchannel = -8888; +integer LMhandle0 = 0; +integer LMhandle1 = 0; +integer LGchannel = -9119; +key LeashKey = NULL_KEY; +key someBoss = NULL_KEY; + +// TP tracker. +list TPtDestination = []; + +// Movement +integer mode = 0; +integer MODE_NONE = 0; // Do nothing. +integer MODE_SIT = 1; // Original sit tester - menu for adjusting the sit. +integer MODE_DRIVE = 2; // Drivable box using controls and set pos / rot. +integer MODE_SINGLE = 3; // llMoveToTarget(), single scan, stop when we get there. +integer MODE_SCAN = 4; // llMoveToTarget(), multiple scans, stop when we get there. +integer MODE_FOLLOW = 5; // llMoveToTarget(), multiple scans, keep following after getting there. +integer MODE_LEASH = 5; // llMoveToTarget(), multiple scans, keep following after getting there, with leash. + +list ATTACHMENTS = +[ + "none", + "chest", "skull", "left shoulder", "right shoulder", "left hand", "right hand", "left foot", "right foot", + "spine", "pelvis", + "mouth", "chin", "left ear", "right ear", "left eyeball", "right eyeball", "nose", + "r upper arm", "r forearm", "l upper arm", "l forearm", + "right hip", "r upper leg", "r lower leg", "left hip", "l upper leg", "l lower leg", + "stomach", "left pec", "right pec", + "center 2", "top right", "top", "top left", "center", "bottom left", "bottom", "bottom right", + "neck", "root" +]; + +list CLOTHES = +[ + "gloves", + "jacket", "pants", "shirt", + "shoes", "skirt", "socks", + "underpants", "undershirt", + "skin", "eyes", "hair", "shape", "alpha", "tattoo", "physics" +]; + +// simple emoter +string OurName; +integer meListening; +integer bareListening; +integer meChannel = 12; +integer bareChannel = 123; + +list nextWord(string separator, string message, string last) +{ + list result = []; + integer index; + + index = llSubStringIndex(message, separator); + if (0 <= index) + { + if (0 != index) // Check for a corner case. + last += llGetSubString(message, 0, index - 1); + if ((index + 1) < llStringLength(message)) + message = llGetSubString(message, index + 1, -1); + else + message = ""; + result += message + last; + } + else + result += last + message; + + return(result); +} + +integer findLink(string name) +{ + integer n = llGetNumberOfPrims(); + integer link; + for (link = 1; link <= n; ++link) + { + if (llGetLinkName(link) == name) + return link; + } + link = -1; + return link; +} + +leashTo(key id) +{ + integer link = findLink("leashpoint"); + float age = 4.0; + integer count = 1; + float rate = 0.05; + list p = + [ + PSYS_PART_FLAGS, PSYS_PART_FOLLOW_VELOCITY_MASK | PSYS_PART_TARGET_POS_MASK | PSYS_PART_EMISSIVE_MASK | PSYS_PART_FOLLOW_SRC_MASK, + PSYS_SRC_PATTERN, PSYS_SRC_PATTERN_DROP, + PSYS_PART_START_SCALE, <0.3, 0.3, 0.1>, + PSYS_SRC_ACCEL, <0,0,-0.4>, + PSYS_PART_MAX_AGE, age, + PSYS_SRC_BURST_RADIUS, 0.1, + PSYS_SRC_BURST_RATE, rate, + PSYS_SRC_BURST_PART_COUNT, count, + PSYS_SRC_BURST_SPEED_MIN, 3.0, + PSYS_SRC_BURST_SPEED_MAX, 3.0, + PSYS_SRC_TEXTURE, getSetting("leash"), + PSYS_SRC_TARGET_KEY, id, + PSYS_SRC_ANGLE_BEGIN, 0.0, + PSYS_SRC_ANGLE_END, PI, + PSYS_SRC_OMEGA, <0,0,1>, + PSYS_SRC_MAX_AGE, 0.0 + ]; + if ( ((age / rate) * count) < 4096) + { + if (-1 != link) + llLinkParticleSystem(link, p); + else + llParticleSystem(p); + if (0 == LMhandle0) + { + LMhandle0 = llListen(LMchannel, "", NULL_KEY, (string) llGetOwnerKey(Stalkee) + "handle ok"); + LMhandle1 = llListen(LMchannel, "", NULL_KEY, (string) llGetOwnerKey(Stalkee) + "handle detached"); + llShout(LMchannel, (string) llGetOwnerKey(Stalkee) + "collar"); + llShout(LMchannel, (string) llGetOwnerKey(Stalkee) + "handle"); + llShout(LGchannel, "lockguard " + Owner + " handle link " + llGetLinkKey(link)); + } + } + else + D("Your particle system creates too many concurrent particles. Reduce count or age, or increase rate."); +} + +unleash() +{ + integer link = findLink("leashpoint"); + if (LMhandle0) llListenRemove(LMhandle0); + if (LMhandle1) llListenRemove(LMhandle1); + LMhandle0 = 0; + LMhandle1 = 0; + if (-1 != link) + llLinkParticleSystem(link, []); + else + llParticleSystem([]); +} + + +// Following +key Stalker; +key Stalkee; +integer tid = 0; +float RANGE = 4.0; // Meters away that we stop walking towards. +float TAU = 0.1; // Make smaller for more rushed following. + +float bb = 0.0; +float tweak = 0.0; +float sr = 10; +integer Lag = 100; + +vector getRange(key this) +{ + list det = llGetObjectDetails(this, [OBJECT_POS, OBJECT_ROT]); + + if (0 == llGetListLength(det)) + { + // Note - we can't use llGetObjectDetails() here, coz they might not be in adjacent sims either. + s("Can't find " + osKey2Name(this) + " in the sim."); + stopGoto(TRUE); + return ZERO_VECTOR; + } + + vector pos = llList2Vector(det, 0); +// rotation rot = (rotation)llList2String(det, 1); + + if (this != Stalkee) + { + Stalkee = this; + + vector tSize = llGetAgentSize(this); + vector mSize = llGetAgentSize(llGetOwner()); + tweak = (mSize.z - tSize.z) * 2; + if (0.0 > tweak) + tweak = 0.0 - tweak; +//d("tweak " + (string) mSize.z + " " + (string) tSize.z + " " + (string) tweak); + if (1.6 > tweak) + tweak = 1.6; + list box = llGetBoundingBox(this); + bb = llVecDist(llList2Vector(box, 1), llList2Vector(box, 0)) / 2.0; + } + pos.z = pos.z - tweak; +// pos += offset * rot; + return pos; +} + +goto(key this) +{ + if (NULL_KEY == this) + return; + + float dist = 0.0; + vector pos = getRange(this); + + if (ZERO_VECTOR == pos) + return; + dist = llVecDist(pos, llGetPos()); + vector p = pos - llGetPos(); + float r = llAtan2(p.x, p.y); + if (llFabs(sr - r) > 0.25) + { + justRLV("setrot:" + (string) r + "=force"); + sr = r; + } + if (dist > (RANGE + bb)) + { + if (dist > (2.0 * (RANGE + bb))) + osSetSpeed(Owner, 2.0); + else + osSetSpeed(Owner, 1.0); + + llStopMoveToTarget(); + if (0 != tid) + llTargetRemove(tid); + llMoveToTarget(pos, TAU); + } + else + { + if (MODE_SINGLE == mode) + stopGoto(TRUE); + else + stopGoto(FALSE); + } + if ((MODE_FOLLOW == mode) || (MODE_LEASH == mode) || (MODE_SINGLE == mode) || (MODE_SCAN == mode)) + { + float tick = 0.35; + float dil = llGetRegionTimeDilation(); // Between 0 and 1. + float fps = llGetRegionFPS(); // Frames per second, up to 50. + integer newLag = (integer) (dil * fps); + + if (llAbs(Lag - newLag) > 9) + { + Lag = newLag; + tick = ((60 - (dil * fps)) / 30) + 0.15; + } + addEvent(tick, "GOTO_TICK"); + } + else + { + Lag = 100; + addEvent(0.0, "GOTO_TICK"); + } +} + +stopGoto(integer all) +{ + addEvent(0.0, "GOTO_TICK"); + llStopMoveToTarget(); + if (0 != tid) + { + llTargetRemove(tid); + tid = 0; + } + osSetSpeed(Owner, 1.0); + if (all) + { + Stalkee = NULL_KEY; + mode = MODE_NONE; + Lag = 100; + bb = 0.0; + tweak = 0.0; + sr = 10; + unleash(); + } +} + + +// We have to jump through hoops coz RLV wont tell us if a command failed, or was delayed. +// And viewer support for RLV is all kinds of broken. +integer RLVchannel; +//integer RLVCHAN = -1812221819; // RLV relay channel. +string RLVversion; +list RLVq; // Queue of RLV commands. +integer qCHAN = 0; +integer qCMD = 1; +integer qNUM = 2; +integer qRSP = 3; +integer qTIME = 4; +integer qTRIES = 5; +integer qLSTN = 6; +integer qSTRIDE = 7; +integer RLVl; // Current RLV channel. +integer RLVd; // RLV startup is done. +list RLVa; // all RLV commands. +list RLVb; // RLV comands in blacklist. +list RLVs; // RLV commands currently restricted. +list RLVu = // RLV commands we actually use. +[ + "attachallover", "attachall", "clear", "detach", "detachall", "getattach", + "getcommand", "getstatusall", "notify", + "getinv", "getoutfit", "remoutfit", "setrot", "sit", "unsit", + "versionnew", "versionnumbl" +]; + +justRLV(string cmd) +{ + sendRLV(cmd, -1.0); +} +sendRLV(string cmd) +{ + sendRLV(cmd, 10.0); +} +sendRLV(string cmd, float time) +{ + integer c = llGetListLength(llParseString2List(cmd, [","], [])); + RLVq += [(string)(RLVchannel + RLVl), cmd, (string) c, 0, time, 0, 0]; + ++RLVl; + doRLV(); +} +doRLV() +{ + integer i; + // Normally you don't get the length in the middle of the loop, but it may change on us. + for (i = 0; i < llGetListLength(RLVq); i += qSTRIDE) + doRLV(i); +} +doRLV(integer f) +{ + integer chan = llList2Integer(RLVq, f + qCHAN); + string cmds = llList2String(RLVq, f + qCMD); + integer t = llList2Integer(RLVq, f + qTRIES); + float time = llList2Float(RLVq, f + qTIME); + integer h = llList2Integer(RLVq, f + qLSTN); + string cmd = ""; + if (0 != h) + return; +// TODO - check if this command is available and not blacklisted. +// Though since getcommands seems to miss "clear" and "setrot", even though they work, seems pointless. + if (0.0 < time) + { + list c = llParseString2List(cmds, [","], []); + integer l = llGetListLength(c); + integer i; + for (i = 0; i < l; ++i) + cmd += "," + llList2String(c, i) + "=" + chan; + cmd = llGetSubString(cmd, 1, -1); + if (0 == h) + { + h = llListen(chan, "", Owner, ""); + RLVq = llListReplaceList(RLVq, [h], f + qLSTN, f + qLSTN); + } + llOwnerSay("@" + cmd); + ++t; + RLVq = llListReplaceList(RLVq, [t], f + qTRIES, f + qTRIES); + addEvent(10.0, "RLV_OUT " + chan + " " + cmds); + } + else if (RLVd) + { + llOwnerSay("@" + cmds); + RLVq = llListReplaceList(RLVq, [], f, f + qSTRIDE - 1); + } +} + +getRLV(string name) +{ + string r; + integer l = llGetListLength(RLVu); + integer i; + for (i = 0; i < l; ++i) + r += "," + name + ":" + llList2String(RLVu, i); + sendRLV(llGetSubString(r, 1, -1)); +} + +gotStatus(string message) +{ + if ("" != message) + { + list st = llParseString2List(message, ["/"], []); + integer l = llGetListLength(st); + integer i; + for (i = 0; i < l; ++i) + { + string r = llList2String(st, i); + if ("notify:" == llGetSubString(r, 0, 6)) + { // Not documented anywhere I can find, but this is telling us we already have one. + // Though I have seen it telling us this is the one we just created. Play it safe. + integer t = (integer) llGetSubString(r, 7, -1); + if((RLVchannel - 1) != t) + { +d("gotStatus removing " + r); + justRLV("notify:" + t + "=rem"); + } + } + else + { + integer a = TRUE; + integer j = llSubStringIndex(r, "="); + if (-1 != j) + a = (llGetSubString(r, j + 1, j + 1) == "n"); + if (a) + { +d("gotStatus adding " + r); + RLVs += [r]; + } + else + { + j = llListFindList(RLVs, [r]); + if (-1 != j) + { +d("gotStatus subtracting " + r); + RLVs = llListReplaceList(RLVs, [], j, j); + } + else +d("gotStatus already got " + r); + } + } + } + RLVs = deDup(RLVs); + s(llGetListLength(RLVs) + " RLV restrictions - " + llDumpList2String(RLVs, " / ")); + } +} + +list deDup(list lst) +{ + integer l = llGetListLength(lst); + integer i; + lst = llListSort(lst, 1, TRUE); + if (1 < l) + { +//d("deDup " + llDumpList2String(lst, " ~ ")); + for (i = 1; i < l; ++i) + { + if (llList2String(lst, i - 1) == llList2String(lst, i)) + { + lst = llListReplaceList(lst, [], i, i); + --i; --l; + } + } +//d("deDup " + llDumpList2String(lst, " ~ ") + "\n"); + } + return lst; +} + +string gotRLV(integer chan, integer g, string message) +{ + string r; + integer f = listFindString(RLVq, (string) chan, qSTRIDE); + if (-1 != f) + { + r = llList2String(RLVq, f + qCMD); + integer n = llList2Integer(RLVq, f + qNUM); + integer rsp = llList2Integer(RLVq, f + qRSP); + integer v = ("versionnew" == r); + integer t = llList2Integer(RLVq, f + qTRIES); + integer h = llList2Integer(RLVq, f + qLSTN); + + if (g) + { +//string rp = "FULL "; + addEvent(0.0, "RLV_OUT " + chan + " " + llList2String(RLVq, f + qCMD)); + ++rsp; + RLVq = llListReplaceList(RLVq, [rsp], f + qRSP, f + qRSP); + list c = llParseString2List(r, [","], []); + r = llList2String(llParseString2List(llList2String(c, 0), [":", "="], []), 0); + if (rsp >= n) + { + --t; + if (0 < t) + { + RLVq = llListReplaceList(RLVq, [0], f + qRSP, f + qRSP); + RLVq = llListReplaceList(RLVq, [t], f + qTRIES, f + qTRIES); +//d("gotRLV " + rp + " " + chan + " " + n + "==" + rsp + " " + t + " " + r + " -> " + message + "\n" + llDumpList2String(llList2List(RLVq, f, f + qSTRIDE - 1), " ~ ")); + } + else + { + if (0 != h) + llListenRemove(h); +//d("gotRLV " + rp + " " + chan + " " + n + "==" + rsp + " " + " " + t + " " + r + " -> " + message); + RLVq = llListReplaceList(RLVq, [], f, f + qSTRIDE - 1); + } + } + else + { +//rp = "PARTIAL"; +//d("gotRLV " + rp + " " + chan + " " + n + "==" + rsp + " " + " " + t + " " + r + " -> " + message + "\n" + llDumpList2String(llList2List(RLVq, f, f + qSTRIDE - 1), " ~ ")); + } + + if (v) + { + RLVversion = message; + s(RLVversion); + getRLV("getcommand"); + sendRLV("versionnumbl"); + } + else + { + if ("getcommand" == r) + RLVa += llParseString2List(message, ["/", ";"], []); + else if ("versionnumbl" == r) + { + list tm = llParseString2List(message, ["/", ";"], []); + if (1 < llGetListLength(tm)) + RLVb += llList2List(tm, 1, -1); + } + else if ("getstatusall" == r) + gotStatus(message); + else if ("" == r) + d("RLV report for unknown command - " + message); + } + } + else + { + if (2 >= t) + { // We are either waiting for the viewer to get it's shit together on login, + // or slowly finding out these commands are not supported anyway. + // 30 seconds seems about right for the former, and has been documented. + d("RLV command " + r + " not responding! Trying again. " + t); + if (1 == t) s("RLV could take up to 60 seconds to start up, you may have to wait for that."); + if (0 != h) + { + llListenRemove(h); + RLVq = llListReplaceList(RLVq, [0], f + qLSTN, f + qLSTN); + } + doRLV(f); + } + else + { + if (v) // Various docs say RLV might take upto 30 seconds to start in the viewer. + s(Owner, "Your viewer is not RLV-enabled, so some things wont work."); + else + { + d("RLV command " + r + " not responding! Giving up."); + s("Your viewer is taking too long to start up RLV."); + } + if (0 != h) llListenRemove(h); + RLVq = llListReplaceList(RLVq, [], f, f + qSTRIDE - 1); + } + } + } + else if ((RLVchannel - 1) == chan) + { +d("gotRLV @notify response - " + message); + gotStatus(message); + } + else + D("gotRLV() didn't find - " + g + " " + chan + " -> " + message + " " + "\n" + llDumpList2String(RLVq, " ~ ")); + + if ((!RLVd) && ("" != RLVversion)) + { + integer l = llGetListLength(RLVq); + integer i; + RLVd = TRUE; + for (i = 0; i < l; i += qSTRIDE) + { + if (0 != llList2Integer(RLVq, i + qLSTN)) + RLVd = FALSE; + } + if (RLVd) + { + if (0 == llGetListLength(RLVa)) RLVa = RLVu; + getRLV("getstatusall"); // Doing this now coz Cool VL is odd at login. +// TODO - Also deal with "%f" == "=force". + RLVa = deDup(RLVa); + RLVb = deDup(RLVb); + l = llGetListLength(RLVb); + for (i = 0; i < l; ++i) + { + f = llListFindList(RLVa, [llList2String(RLVb, i)]); + if (-1 != f) + RLVa = llListReplaceList(RLVa, [], f, f); + } + list bl; + l = llGetListLength(RLVu); + for (i = 0; i < l; ++i) + { + f = llListFindList(RLVa, [llList2String(RLVu, i)]); + if (-1 == f) + bl += [llList2String(RLVu, i)]; + } + s("RLV started up in " + (string) (llGetTimeOfDay() - Start) + " seconds.\n" + + llGetListLength(RLVa) + " RLV commands, " + + llGetListLength(RLVb)+ " RLV black listed commands - " + + llDumpList2String(RLVb, " / ")); + if (0 != llGetListLength(bl)) + s("The following RLV commands are unsupported by your viewer, or blacklisted - " + + llDumpList2String(bl, ", ") + + "\nSome things may or may not work. That list may not be accurate."); + justRLV("notify:" + (RLVchannel - 1) + "=add"); + doRLV(); + } + } + return r; +} + +init() +{ + stopGoto(TRUE); + Stalker = llGetOwner(); + OurName = llGetObjectName(); + RLVchannel = llAbs((integer) ("0x" + llGetSubString((string) llGetKey(), -4, -1))) + (integer) llFrand(9999.0); + llListen(RLVchannel, "", llGetOwner(), ""); + meListening = llListen(meChannel, "", llGetOwner(), ""); + bareListening = llListen(bareChannel, "", llGetOwner(), ""); +} + +laterInit() +{ + showAccess(Owner); +} + +default +{ + state_entry() + { + llSleep(0.2); + Start = llGetTimeOfDay(); + Owner = llGetOwner(); +d("\n\n1ring sending RESET @ " + (string) Start + "\n"); + ScriptName = llGetScriptName(); ScriptKey = llGetInventoryKey(ScriptName); + LibraryKey = NULL_KEY; + sendScript(lRESET, []); + init(); + } + + link_message(integer sender, integer num, string message, key id) + { + linky(num, message, id); + } + + on_rez(integer param) + { +// showAccess(Owner); + d("on_rez reset"); + llResetScript(); + } + + attach(key id) + { + if ((NULL_KEY != id) && ((llGetTimeOfDay() - Start) > 60.0)) + { + s("Attach detected, and RLV didn't respond yet, resetting."); +// llResetScript(); + } + } + + at_target(integer tnum, vector targetpos, vector ourpos) + { + if (tnum == tid) + { + vector p = targetpos - ourpos; + justRLV("setrot:" + (string) llAtan2(p.x, p.y) + "=force"); + if ((MODE_SINGLE == mode) || (MODE_SCAN == mode)) + stopGoto(TRUE); + } + } + + run_time_permissions(integer perm) + { + if (perm & PERMISSION_TAKE_CONTROLS) + { + llTakeControls(0, FALSE, FALSE); + s(llKey2Name(someBoss) + " has made you stay."); + } + } + + listen(integer channel, string name, key id, string message) + { + if (channel == LMchannel) + { + list dt = llGetObjectDetails(id, [OBJECT_ROOT]); + key t = llGetSubString(message, 0, 35); + message = llGetSubString(message, 36, -1); + if ((llList2Key(dt, 0) == Stalkee) || (llGetOwnerKey(id) == Stalkee)) + { + //leash holder announced it got detached... send particles to avi + if (message == "handle detached") + leashTo(Stalkee); + else + { + // We heard from a leash holder. re-direct particles + LeashKey = id; + if (message == "collar ok") + leashTo(id); + if (message == "handle ok") + leashTo(id); + osMessageObject(LeashKey, "URL|" + URL); +d("HEY leashy! " + URL); + } + } + } + + if ((RLVchannel <= channel) && (channel < (RLVchannel + RLVl))) + { + string rlv = gotRLV(channel, TRUE, message); + list items = []; + channel = 0; + if ("getattach" == rlv) + { + do + { + if (llGetSubString(message, channel, channel) == "1") + items += llList2String(ATTACHMENTS, channel); + } while(++channel < 42); + + dynamicMenu(someBoss, "clothes", "DETACH", "Please select the attachment to detach", + llDumpList2String(items, "|"), "DETACH"); + } + else if ("getoutfit" == rlv) + { + do + { + if (llGetSubString(message, channel, channel) == "1") + items += llList2String(CLOTHES, channel); + } while(++channel < 20); + + dynamicMenu(someBoss, "clothes", "TAKEOFF", "Please select the clothes to take off", + llDumpList2String(items, "|"), "TAKEOFF"); + } + else if ("getinv" == rlv) + { + list folders = llListSort(llCSV2List(message), 1, TRUE); + integer l = llGetListLength(folders); + integer i; + + for (i = 0; i < l; ++i) + { + string folder = llList2String(folders, i); + items += [folder, "+ " + folder, "- " + folder]; + } + dynamicMenu(someBoss, "clothes", "PUTON", "Please select the outfit to put on", + llDumpList2String(items, "|"), "PUTON"); + } + } + + list result = nextWord(" ", message, ""); + if (channel == meChannel) + { + list names = nextWord(" ", llKey2Name(llGetOwner()), ""); + + if (llGetSubString(message, 0, 0) == "'") + { + list suffix = nextWord(" ", message, ""); + names = ["", llList2String(names, 1) + llList2String(suffix, 1)]; + message = llList2String(suffix, 0); + } + // Rename ourselves to the first word of owners name. + llSetObjectName(llList2String(names, 1)); + llSay(0, "/me " + message); + } + else if (channel == bareChannel) + { + // Rename ourselves to the first word. + llSetObjectName(llList2String(result, 1)); + llSay(0, "/me " + llList2String(result, 0)); + } + // Change our name back + llSetObjectName(OurName); + } + + changed(integer change) + { + if (change & CHANGED_INVENTORY) + { +// readSettings(); +// showAccess(Owner); + } + if (change & CHANGED_TELEPORT) + { +// TODO - check if this was a requested TP and we are being carried. If so, restart the carry process. + if (0 != llGetListLength(TPtDestination)) + { + // Check if we ended up in a landing spot instead of where we thought we where going. + vector pos = getRange(Stalkee); + float dist = llVecDist(pos, llGetPos()); + if (dist > (2 *(RANGE + bb))) + { + s("Double TP to get closer."); + osTeleportAgent(Owner, llList2String(TPtDestination, 1), llList2Vector(TPtDestination, 2), <1.0,1.0,1.0>); + } + TPtDestination = []; + } + } + if (change & CHANGED_OWNER) + llResetScript(); + } +} diff --git a/OhSillyThreatDetector.lsl b/OhSillyThreatDetector.lsl new file mode 100644 index 0000000..4f17ed6 --- /dev/null +++ b/OhSillyThreatDetector.lsl @@ -0,0 +1,59 @@ + +// Detect when OpenSim reports a function was used that triggered a threat level warning. +// Coz their official method doesn't work, and often their threat levels are set way too high. + +// This script will send a link message with number DEBUG_CHANNEL, message being the name of the naughty function, +// and key of the prim containing the naughty script. It's up to the naughty script to catch this link message +// and do something less naughty. + +// Since it sniffs on DEBUG_CHANNEL messages, and a script can't hear any message sent by scripts in the same prim, +// you'll need to put this script in a prim without the naughty scripts you want to check. + +default +{ + state_entry() + { +// llSay(DEBUG_CHANNEL, "G'day, I'm " + llGetKey() + " part of " + llGetLinkKey(LINK_ROOT)); + llListen(DEBUG_CHANNEL, "", NULL_KEY, ""); + } + + listen(integer channel, string name, key id, string message) + { + key root = llList2Key(llGetObjectDetails(id, [OBJECT_ROOT]), 0); + // name is the name of the prim, id is the UUID of the prim. + // Reports the name and UUID of the prim itself, not the object it's part of. + // Naturally can't detect debugs from our own prim, even if it's a different script. + // Works from another prim in the same object. + + /* Threat level errors look like this (at least in OpenSim 0.8.2) - +OSSL Runtime Error: osSetStateEvents permission denied. Script creator is not in the list of users allowed to execute this function and prim owner also has no permission. + +There is also (OpenSim 0.9 at least) - +Max Zephyr script +OSSL Runtime Error: permission denied. All OS functions are disabled.(script: oc_settings event: changed primID:487baa88-d6e5-420c-b18c-f47f5bbd7de4 at <127.9408, 128.0009, 25.75689>) + +My script +OSSL Runtime Error: osSetSpeed permission denied. All OS functions are disabled.(script: 1AOor2 event: changed primID:c7db8d3c-ae33-44ce-a685-cf63a37f0cf5 at <123.933, 203.0375, 24.29473>) + +From the source (NOTE: first version has no function name) - +("{0} permission denied. All OS functions are disabled.") +("{0} permission denied. All OS functions are disabled.", function) +("{0} permission denied. Allowed threat level is {1} but function threat level is {2}.", function, +("{0} permission denied. Script creator is not in the list of users allowed to execute this function and prim owner also has no permission.", function +("{0} permission denied. Script permissions error.", function + +OpenSim 0.9.0.1 doesn't include the "OSSL Runtime Error: " bit at the beginning anymore. +OpenSim 0.9.2 seems to not even send it to the DEBUG_CHANNEL for scripts anymore. + */ + if ((llGetLinkKey(LINK_ROOT) == root) && ("OSSL Runtime Error: " == llGetSubString(message, 0, 19))) + { + list e = llParseStringKeepNulls(llGetSubString(message, 20, -1), [" "], []); +//llOwnerSay(llList2String(e, 0)); + if (("permission" == llList2String(e, 1)) && ("denied" == llList2String(e, 2))) + { + string function = llList2String(e, 0); + llMessageLinked(LINK_SET, DEBUG_CHANNEL, function, id); + } + } + } +} diff --git a/onefang's leash holder.lsl b/onefang's leash holder.lsl new file mode 100644 index 0000000..6d7b360 --- /dev/null +++ b/onefang's leash holder.lsl @@ -0,0 +1,44 @@ +integer mychannel = -8888; +integer particle_chan = -270510; +string listenfor; +string response; +string detach; + +key masterid; +key handle; + +default +{ + state_entry() + { + masterid = llGetOwner(); + handle = llGetKey(); + listenfor = (string)masterid + "handle"; + response = (string)masterid + "handle ok"; + detach = (string)masterid + "handle detached"; + llListen(mychannel, "", NULL_KEY, listenfor); + llSay(mychannel, response); + } + + attach(key id) + { + if (NULL_KEY == id) + llSay(mychannel, detach); + } + + listen(integer channel, string name, key id, string message) + { + llSay(mychannel, response); + llSay(particle_chan, handle + "#" + masterid); + } + + on_rez(integer param) + { + llResetScript(); + } + + touch_start(integer total_number) + { + llSay(particle_chan, handle + "#" + masterid); + } +} -- cgit v1.1