// 1ring written by onefang rejected 2019. // A simple, generic RLV based "collar". string Version = "1ring v0.14 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 lCHECK = -11; integer lCHECK_DONE = -12; integer lCONTROL = -13; integer lCONTROL_DONE = -14; integer lCMD = -15; integer lCMD_DONE = -16; integer lSETTINGS = -17; integer lSETTINGS_DONE = -18; integer lSCAN = -19; integer lDYNAMIC = -20; integer lMENU = -21; // OhSillyThreat detector integer doSpeed = FALSE; d(string m) {if (DEBUG) llInstantMessage(Owner, llGetScriptName() + ": " + m);} D(string m) {llRegionSay(DEBUG_CHANNEL, llGetScriptName() + ": " + m);} s(string m) {s(Owner, m);} s(key id, string m) {if (id == Owner) llOwnerSay(m); else llInstantMessage(id, m);} S(string m) {llInstantMessage(Owner, llGetScriptName() + ": " + m);} sendScript(integer cmd, list args) {sendScript(LibraryKey, cmd, ScriptName, args);} sendScript(key them, integer cmd, list args) {sendScript(them, cmd, inKey2Name(them), args);} sendScript(integer cmd, string name, list args) {sendScript(LibraryKey, cmd, name, args);} sendScript(key them, integer cmd, string name, list args) { llMessageLinked(LINK_SET, cmd, llDumpList2String([ScriptKey, name] + args, lSEP), them); } integer pingPrim(key k) { if (NULL_KEY != k) return (0 != llGetListLength(llGetObjectDetails(k, [OBJECT_DESC]))); return FALSE; } sendPrim(key them, string cmd, list args) { if (pingPrim(them)) osMessageObject(them, llDumpList2String([ScriptName, 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; } speed(key u, float s){if (doSpeed) osSetSpeed(u, s);} 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) { dynamicMenu(id, menu, name, title, entries, command, 1); } dynamicMenu(key id, string menu, string name, string title, string entries, string command, integer ret) { sendScript(lDYNAMIC, [id, menu, name, title, entries, command, ret]); } linky(integer num, string message, key id) { if (DEBUG_CHANNEL == num) { if ("osSetSpeed" == message) doSpeed = (integer) 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 ((lCHECK == num) || (lCMD == num)) { if ((fr == (ScriptName + ".")) || (fr == "*.")) { key a = llList2Key(input, 3); string button = llList2String(input, 4); integer r; if (lCHECK == num) { //d("link CHECK " + llDumpList2String(input, " ~ ")); r = checkThing(a, button, fr, llList2String(input, 5), llList2String(input, 6), llList2Integer(input, 2)); } else { //d("link CHAT " + llDumpList2String(input, " ~ ")); r = doThing(a, button, fr, llList2String(input, 5), llList2String(input, 6), llList2Integer(input, 2)); } sendScript(num - 1, [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); // 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)); } } // 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 checkThing(key id, string button, string fr, string cmd, string data, integer source) { if ((fr != (ScriptName + ".") && ("*." != fr))) return FALSE; return TRUE; } integer doThing(key id, string button, string fr, string cmd, string data, integer source) { integer set = -1; if ("SET " == llGetSubString(button, 0, 3)) { set = listFindString(Settings, fr + cmd, sSTRIDE); if (-1 != set) setSetting(id, fr + cmd, data, fINT); else S("Unknown setting '" + cmd); source = 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" == data) s = [AGENT, "Select someone to follow", "FOLLOW"]; else if ("goto" == data) s = [ACTIVE | AGENT | PASSIVE, "Select something or someone to go to", "GOTO"]; else if ("leash" == data) s = [ACTIVE | AGENT | PASSIVE, "Select something or someone to leash to", "LEASH"]; else if ("sit" == data) // This will only be for objects, can't sit on an avatar. s = [ACTIVE | PASSIVE, "Select something to sit on", "SIT"]; else if ("texture" == data) { integer i = llGetInventoryNumber(INVENTORY_TEXTURE); integer j = 0; string opts = ""; string cmds = ""; while (i-- > 0) { string t = llGetInventoryName(INVENTORY_TEXTURE, i); if ("leash_" == llGetSubString(t, 0, 5)) { string n = llGetSubString(t, 6, -1); opts += n + "|"; cmds += "LEASH_TEXTURE " + t + "|"; j++; } } if (j) { dynamicMenu(id, "", "", "Choose a leash texture: ", llGetSubString(opts, 0, -2), llGetSubString(cmds, 0, -2), 2); return FALSE; } return (source == fMENU); } else { D("Unknown CHOOSE_ALL option - " + data); 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 ("LEASH_BIGGER" == cmd) { vector sz = (vector) getSetting("leash_size"); sz.x += 0.1; sz.y += 0.1; setSetting(id, "leash_size", (string) sz, source); if (MODE_LEASH == mode) { unleash(); leashTo(Stalkee); } } else if ("LEASH_SMALLER" == cmd) { vector sz = (vector) getSetting("leash_size"); if (0.1 < sz.x) sz.x -= 0.1; if (0.1 < sz.y) sz.y -= 0.1; setSetting(id, "leash_size", (string) sz, source); if (MODE_LEASH == mode) { unleash(); leashTo(Stalkee); } } else if ("LEASH_LONGER" == cmd) { RANGE += 2.0; setSetting(id, "leash_range", (string) RANGE, source); if (MODE_LEASH == mode) { unleash(); leashTo(Stalkee); } s("The leash range is now " + (string) RANGE + " meters."); } else if ("LEASH_SHORTER" == cmd) { if (2.0 < RANGE) RANGE -= 2.0; setSetting(id, "leash_range", (string) RANGE, source); if (MODE_LEASH == mode) { unleash(); leashTo(Stalkee); } s("The leash range is now " + (string) RANGE + " meters."); } else if ("LEASH_TEXTURE" == cmd) { setSetting(id, "leash", data, source); if (MODE_LEASH == mode) { 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)); osTeleportOwner(llList2String(TPtDestination, 1), llList2Vector(TPtDestination, 2), ZERO_VECTOR); } 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)) && (-1 != set)) { if (fMENU == source) S("Unknown menu command '" + cmd + "' from - " + button); else S("Unknown command '" + cmd + "' from - " + 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, (vector) getSetting("leash_size"), 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 = 1.5; // 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; RANGE = (float) getSetting("leash_range"); 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))) speed(Owner, 2.0); else speed(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; } speed(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) { if ((RLVd) && ("" == RLVversion)) { llSay(0, "RLV not enabled for " + llKey2Name(Owner) + "."); return; } 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; if ((RLVd) && ("" == RLVversion)) { llSay(0, "RLV not enabled for " + llKey2Name(Owner) + "."); 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."); RLVd = TRUE; } else { d("RLV command " + r + " not responding! Giving up."); S("Your viewer is taking too long to start up RLV."); RLVd = TRUE; } 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. " + llGetListLength(RLVa) + " RLV commands, " + llGetListLength(RLVb)+ " RLV black listed commands - " + llDumpList2String(RLVb, " / ")); if (0 != llGetListLength(bl)) d("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() { if ("" == getSetting("PREFIX")) { 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); } 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", 2); } 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", 2); } 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); if ("" != folder) items += [folder, "+ " + folder, "- " + folder]; } if (0 != llGetListLength(items)) dynamicMenu(someBoss, "clothes", "PUTON", "Please select the outfit to put on", llDumpList2String(items, "|"), "PUTON", 2); else s("You have no outfits in your #RLV folder."); } } if (channel == meChannel) { string n = llKey2Name(Owner); integer i = llSubStringIndex(n, " "); // Rename ourselves to the first word of owners name. llSetObjectName(llGetSubString(n, 0, i - 1)); llSay(0, "/me " + message); } else if (channel == bareChannel) { integer i = llSubStringIndex(message, " "); // Rename ourselves to the first word. llSetObjectName(llGetSubString(message, 0, i - 1)); llSay(0, llGetSubString(message, i + 1, -1)); } // 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."); osTeleportOwner(llList2String(TPtDestination, 1), llList2Vector(TPtDestination, 2), ZERO_VECTOR); } TPtDestination = []; } } if (change & CHANGED_OWNER) llResetScript(); } }