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. --- 1ring.lsl | 1424 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1424 insertions(+) create mode 100644 1ring.lsl (limited to '1ring.lsl') 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(); + } +} -- cgit v1.1