// 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.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; 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 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 doAnim = TRUE; integer doSit = TRUE; integer doSpeed = TRUE; 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; } animBegin(key u, string a){if (doAnim) osAvatarPlayAnimation(u, a); else llStartAnimation(a); /*s("START " + llKey2Name(u) + " " + a);*/} animEnd (key u, string a){if (doAnim) osAvatarStopAnimation(u, a); else llStopAnimation (a); /*s("STOP " + llKey2Name(u) + " " + a);*/} speed(key u, float s){if (doSpeed) osSetSpeed(u, s);} animSwitch(key u, string anim) { integer f = findAvatar(u); if (-1 != f) { string a = llList2String(Avs, f + aANIM); //s("animSwitch " + a + " -> " + anim); if ("" != a) animEnd(u, a); if (("" != anim) && (checkAnim(anim, FALSE))) animBegin(u, anim); Avs = llListReplaceList(Avs, [anim], f + aANIM, f + aANIM); } } 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) { 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]); } integer Chosen; linky(integer num, string message, key id) { if (DEBUG_CHANNEL == num) { if ("osAvatarPlayAnimation" == message) {doAnim = (integer) id; setSetting(Owner, "OSANIM", id, fLINK);} if ("osAvatarStopAnimation" == message) {doAnim = (integer) id; setSetting(Owner, "OSANIM", id, fLINK);} if ("osForceOtherSit" == message) {doSit = (integer) id; setSetting(Owner, "OSSIT", id, fLINK);} if ("osSetSpeed" == message) {doSpeed = (integer) id; setSetting(Owner, "OSSPEEH", id, fLINK);} } 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)); sendScript(num - 1, [llList2String(input, 6), a, r]); } 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); 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. integer checkThing(key id, string button, string fr, string cmd, string data, integer source) { if ((fr != (ScriptName + ".") && ("*." != fr))) return TRUE; if (("POSE" == cmd) || ("SINGLE_POSE" == cmd)) { //d("CHECKING " + data); // POSE piggyback,~,piggyback-sit list p = llParseStringKeepNulls(llGetSubString(data, 5, -1), [",", "|"], []); string name = llList2String(p, 0); integer l = llGetListLength(p); integer i; p = llListReplaceList(p, [], 0, 0); l--; for (i = 0; i < l; i++) { string a = llList2String(p, i); integer e = FALSE; integer f = FALSE; list ae = splitEmote(a); string an = llList2String(ae, 0); // Coz LSL doesn't short circuit, and we don't want to checkAnim("~") if ("~" == an) e = TRUE; else if (!checkAnim(an, TRUE)) e = TRUE; if (e) { p = llListReplaceList(p, [], i, i); i--; l--; } } savePose(name, llDumpList2String(p, "|"), "", ""); } 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 av = findAvatar(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. animEnd(llGetOwner(), Smile); Smile = llList2String(Smiles, (integer) llFrand((float) llGetListLength(Smiles))); addEvent(3.0 + llFrand(2.0), cmd); animBegin(llGetOwner(), Smile); } else if ("SLOWER_-" == cmd) { integer sp = llListFindList(AOspeeds, [getSetting("SPEED")]); if (sp > 0) sp--; else sp = 0; setSetting(id, "SPEED", llList2String(AOspeeds, sp), fMENU); s("Now moving at " + llList2String(AOspeedNames, sp) + " speed."); } else if ("NORMAL_SPEED" == cmd) { setSetting(id, "SPEED", "1.0", fMENU); s("Now moving at normal speed."); } else if ("FASTER_+" == cmd) { integer sp = llListFindList(AOspeeds, [getSetting("SPEED")]); integer l = llGetListLength(AOspeeds) - 1; if (sp < l) sp++; else sp = l; setSetting(id, "SPEED", llList2String(AOspeeds, sp), fMENU); s("Now moving at " + llList2String(AOspeedNames, sp) + " speed."); } else if ("ADJUST_ALL" == cmd) { 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."); list result = []; integer l = llGetListLength(Poses); vector adj = (vector) getSetting("ADJUSTBY"); 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++) { string p; list tmp = llParseString2List(llList2String(prs, j), ["><"], []); p = (string) ( ((vector) ( llList2String(tmp, 0) + ">" )) + adj); r += p + "<" + llList2String(tmp, 1); } result += [r]; } osMakeNotecard(".ADJUSTED_POSITIONS", result); s("Adjusted positions saved to the .ADJUSTED_POSITIONS notecard."); return FALSE; } else if ("LESS_-" == cmd) { integer distance = llList2Integer(Avs, av + aDIST); --distance; if (0 > distance) distance = 0; Avs = llListReplaceList(Avs, [distance], av + aDIST, av + aDIST); // This is done so we can do a bit more than the usual menu showing stuff. showMenu(id); return FALSE; } else if ("MORE_+" == cmd) { integer distance = llList2Integer(Avs, av + aDIST); ++distance; if (llGetListLength(Distances) <= distance) distance = llGetListLength(Distances) - 1; Avs = llListReplaceList(Avs, [distance], av + aDIST, av + aDIST); showMenu(id); return FALSE; } else if ("NEXT_AVATAR" == cmd) { key them = llList2Key(Avs, av + aADJ); integer t = findAvatar(them); if (-1 != t) { t += aSTRIDE; if (llGetListLength(Avs) <= t) t = -1; } else t = 0; if (-1 != t) Avs = llListReplaceList(Avs, [llList2Key(Avs, t + aKEY)], av + aADJ, av + aADJ); else Avs = llListReplaceList(Avs, [ScriptKey], av + aADJ, av + aADJ); 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(Avs, av + aDIST)), llToLower(cmd)); } else if ("AO" == cmd) { if ("0" != data) { integer l = llGetListLength(Avs); nextState = ""; for (f = 0; f < l; f += aSTRIDE) updateAvatar(llList2Key(Avs, f + aKEY)); } checkAO(); } else if ("SYNC" == cmd) { if ("R" != data) nextPose = data; else { Pose = ""; State = ""; } if ("" == Posed) // Coz we don't want the checkAO regular SYNC R to break us out of the pose. checkSitters(TRUE); } else if ("EMOTE" == cmd) { f = findAvatar(data); if (-1 != f) doEmote(Pose, f); } 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, menu, 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(llKey2Name(data) + " is an NPC, forcing them to '" + What + "' with you."); } else { s("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(300.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); addEvent(0.5, "PING"); } else if ("REZ_DONE" == cmd) { if (1 == Chosen) { sendPrim(TheirKey, "SIT", [Stalkee, What, llDumpList2String(Whats, "|")]); sendPrim(TheirKey, "COUPLES", [Owner]); } 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, Owner, Stalkee, "STOP", ""]); S(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, Owner, Stalkee, "STOP", ""]); s(llKey2Name(id) + " said yes to your offer of '" + What + "'."); } else if ("COUPLE_TIMEOUT" == cmd) { if (NULL_KEY != Stalkee) { Chosen = -1; S(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 { if (doSit) osForceOtherSit(Stalkee/*, llGetKey()*/); else llInstantMessage(Stalkee, "Please sit on the big gold heart."); } newPose(id, llList2Key(p, 1), llList2List(p, 2, -1)); } } else if ("SIT_DONE" == cmd) { d("SIT_DONE for " + llKey2Name(data)); if (0 == Chosen) { Chosen = 1; addEvent(0.0, "COUPLE_TIMEOUT"); sendScript(lCMD, "1ring", [fINT, Owner, Stalkee, "STOP", ""]); S(llKey2Name(data) + " just grabbed your offer of '" + What + "'."); } if(NULL_KEY == Controller) { if ("" != Posed) { s("Stopping pose " + Posed); // animEnd(Owner, Posed); Posed = ""; animSwitch(Owner, Posed); } llOwnerSay("@sit:" + TheirKey + "=force"); s("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 ("PING" == cmd) { key k = TheirKey; if (NULL_KEY != BossKey) k = BossKey; if (pingPrim(k)) addEvent(0.5, "PING"); else { if (NULL_KEY != BossKey) { S("Boss went away, slacking off."); die(); } else { S("Vehicle went away."); TheirKey = NULL_KEY; if (99.0 <= TPangle) { s("Switching AO to 1ring object."); nextPose = ""; oldController(Owner); checkAO(); } } } } else if ("DIE" == cmd) die(); else if ("DIE_DONE" == cmd) { TheirKey = NULL_KEY; if (99.0 <= TPangle) { s("Switching AO to 1ring object."); nextPose = ""; oldController(Owner); } } else if ("ADJUST" == cmd) { if (Attached) s(id, "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(id, "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("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("Current positions saved to the .POSITIONS notecard."); } else if ("SWAP" == cmd) { Swapped = !Swapped; checkSitters(TRUE); } 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, ZERO_VECTOR); } } 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 ("BUILTINS" == cmd) { integer l = llGetListLength(ANIMATIONS); integer i; string opts = ""; string cmds = ""; for (i = 0; i < l; i++) { string anim = llList2String(ANIMATIONS, i); opts += anim + "|"; cmds += "SINGLE_POSE " + anim + "," + anim + "|"; } dynamicMenu(id, menu, menu, "Choose a built in animation: ", llGetSubString(opts, 0, -2), llGetSubString(cmds, 0, -2), 2); return FALSE; } else if ("OTHERS" == cmd) { integer l = llGetListLength(leftOvers); integer i; string anim; if (0 == l) { i = llGetInventoryNumber(INVENTORY_ANIMATION); while (i-- > 0) { integer j; integer left = TRUE; anim = llGetInventoryName(INVENTORY_ANIMATION, i); if (-1 == llListFindList(usedAnims, [anim])) { leftOvers += (list) anim; l++; } } } leftOvers = llListSort(leftOvers, 1, TRUE); if (0 != l) { string opts = ""; string cmds = ""; for (i = 0; i < l; i++) { anim = llList2String(leftOvers, i); opts += anim + "|"; cmds += "SINGLE_POSE " + anim + "," + anim + "|"; } dynamicMenu(id, menu, "", "Choose a pose: ", llGetSubString(opts, 0, -2), llGetSubString(cmds, 0, -2), 2); return FALSE; } else s("No left over poses."); } else if ("SINGLE_POSE" == cmd) { list p = llParseStringKeepNulls(data, [","], []); if ("" != Posed) animEnd(Owner, Posed); Posed = llList2String(p, 1); animSwitch(Owner, Posed); } else if ("STOP_ALL" == cmd) { nextPose = ""; Posed = ""; Pose = ""; State = ""; stopAnims(Owner); } else if ("▲" == cmd) { if (Attached) Chosen = -1; else { if (-1 != av) { if ("" != llList2Key(Avs, av + aADJ)) { Avs = llListReplaceList(Avs, [""], av + aADJ, av + aADJ); s(id, "Switched out of adjusting mode."); addEvent(Tick * Smooth, "Keys"); } } } } 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); } float TPangle = 999.0; killPrim() { if (NULL_KEY != TheirKey) sendPrim(TheirKey, "DIE", []); Stalkee = NULL_KEY; What = ""; Whats = []; 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 and AO list Avs = []; integer aKEY = 0; // Key of the avatar. integer aNUM = 1; // Number of the sitter, index into Poses[].psANIM and Poses[].psPOSROT integer aLINK = 2; // The "prim" link the avatar is. integer aSTATE = 3; // AO / Pose state for this person. integer aANIM = 4; // Current animation for this avatar. integer aADJ = 5; // Key of who they are adjusting, "" means not adjusting, NULL_KEY will mean adjust all. integer aDIST = 6; // For the adjusting this avatar is doing, not for who they are adjusting. Index into Distances[]. integer aKEYS = 7; // KeysLevel for this avatar. Mostly needed for held down keys I think? integer aSTRIDE = 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; string Posed; // Single pose, overrides AO. string Pose; // Couples pose name. string State; // AO state name. string nextPose; // Couples pose name. string nextState; // AO state name. // Smiler integer SmileCounter = 0; string Smile = "express_toothsmile"; list Smiles = [ "express_toothsmile", "express_embarrassed_emote", "express_smile", "express_wink_emote" ]; // Couples list Whats; string What; key Stalkee; integer PIN; key Leader = NULL_KEY; float LeaderOffset = 0.0; key Controller = NULL_KEY; integer Swapped = FALSE; // AO integer Lag = 100; integer Swimming; integer Bobbing; float Tick = 0.2; float AOspeed = 1.0; list AOspeeds = ["0.1", "0.5", "1.0", "2.0", "5.0", "10.0", "20.0", "50.0"]; // Strings coz LSL can't find floats. list AOspeedNames = [ "snail", "slow", "normal", "fast", "light", "ridiculous", "ludicrous", "plaid" ]; list flyStates; list initialStates; // "Taking Off", "hover_up", list ANIMATIONS; list usedAnims; list leftOvers; integer checkAnim(string a, integer add) { if (("" != a) && (INVENTORY_ANIMATION != llGetInventoryType(a)) && (-1 == llListFindList(ANIMATIONS, [a]))) { S("Missing animation - " + a + "!"); return FALSE; } if (add) { if (-1 == llListFindList(usedAnims, [a])) usedAnims += [a]; } return TRUE; } newPose(key id, string p, list ps) { integer f = findPose(p); integer l = llGetListLength(Avs); if (-1 != f) { if (NULL_KEY != id) setSetting(id, "AO", "0", fINT); nextPose = p; string anims = llDumpList2String(ps, "|"); Poses = llListReplaceList(Poses, collectEmotes(anims), f + psANIM, f + psEMOTE); if (NULL_KEY != id) { for (f = 0; f < l; f += aSTRIDE) { updateAvatar(llList2Key(Avs, f + aKEY)); } checkAO(); } } } integer findAvatar(key id) {return listFindString(Avs, id, aSTRIDE);} 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);} list splitEmote(string anim) { integer p = llSubStringIndex(anim, "::"); if (-1 != p) return [llGetSubString(anim, 0, p - 1), llGetSubString(anim, p + 2, -1)]; else return [anim, ""]; } list collectEmotes(string anim) { list anims = []; list emotes = []; list nA = llParseStringKeepNulls(anim, ["|"], []); integer m = llGetListLength(nA); integer j; for (j = 0; j < m; ++j) { list an = llCSV2List(llList2String(nA, j)); integer n = llGetListLength(an); integer k; string anms = ""; string emts = ""; for (k = 0; k < n; ++k) { list ae = splitEmote(llList2String(an, k)); string a = llList2String(ae, 0); string e = llList2String(ae, 1); integer er = FALSE; // Coz LSL doesn't short circuit, and we don't want to checkAnim("~") if ("~" == a) er = TRUE; else if (checkAnim(a, TRUE)) er = TRUE; if (er) { anms += "," + a; if ("" != e) emts += "," + e; } } anims += [llGetSubString(anms, 1, -1)]; emotes += [llGetSubString(emts, 1, -1)]; } return [llDumpList2String(anims, "|"), llDumpList2String(emotes, "|")]; } doEmote(string pose, integer f) { integer i = findPose(pose); if (-1 != i) { list e = llParseStringKeepNulls(llList2String(Poses, i + psEMOTE), ["|"], []); if ([] != e) { string em = llList2String(e, llList2Integer(Avs, f + aNUM)); if ("" != em) { // NOTE - this one is splitting emote::time list s = splitEmote(em); string t = llList2String(s, 1); key k = llList2Key(Avs, f + aKEY); animEnd(k , llList2Key(s, 0)); animBegin(k , llList2Key(s, 0)); if ("" == t) t = "1.0"; addEvent((float) t, "EMOTE " + k); } } } } savePose(string name, string anim, string exp, string posRot) { integer f = findPose(name); if (-1 != f) { if ("" == anim) { anim = llList2String(Poses, f + psANIM); exp = llList2String(Poses, f + psEMOTE); } else if ("" == posRot) posRot = llList2String(Poses, f + psPOSROT); } else { if ("" == posRot) posRot = "<0.0,0.0,0.0><0.0,0.0,0.0><0.3,0.0,0.0><0.0,0.0,0.0>"; } if (NULL_KEY == exp) exp = ""; if (NULL_KEY == posRot) posRot = ""; if (-1 != f) Poses = llListReplaceList(Poses, [name, anim, exp, posRot], f, f + psSTRIDE - 1); else Poses += [name, anim, exp, posRot]; Poses = llListReplaceList(Poses, collectEmotes(anim), f + psANIM, f + psEMOTE); } 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; if (m) { for (j = 0; j < m; j++) r += llList2String(prs, j); result += [r]; } //s(r); } return result; } lookAtMe(key id) { if ((llGetPermissionsKey() == id) && (llGetPermissions() & PERMISSION_CONTROL_CAMERA)) { d("setting camera for " + llKey2Name(id)); llClearCameraParams(); llSetCameraParams( // [ CAMERA_ACTIVE, TRUE, CAMERA_FOCUS_OFFSET, <0.0, 0.0, 0.0>, CAMERA_PITCH, 12.5, // CAMERA_BEHINDNESS_ANGLE, 10.0, CAMERA_BEHINDNESS_LAG, 0.0, CAMERA_DISTANCE, 3.0, // CAMERA_FOCUS_LAG, 0.1 , CAMERA_FOCUS_THRESHOLD, 1.0, // CAMERA_POSITION_LAG, 0.1, CAMERA_POSITION_THRESHOLD, 1.0 // ]); [ CAMERA_ACTIVE, TRUE, 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]); // TODO - check this. if ("Sitting" != llGetAnimation(id)) oldController(id); return; } // integer start = level & edge; // integer end = ~level & edge; integer held = level & ~edge; // integer untouched = ~(level | edge); // d(0, "doControl " + llKey2Name(id) + " " + llList2CSV([level, edge, start, end, held, untouched])); integer f = findAvatar(id); if (-1 != f) { integer a = ("" != llList2Key(Avs, f + aADJ)); 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) { Avs = llListReplaceList(Avs, [""], f + aADJ, f + aADJ); 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; } Avs = llListReplaceList(Avs, [level], f + aKEYS, f + aKEYS); 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. updateControls() { if (Attached) return; integer mode = vNONE; integer reverse = 1; float speed = 0.0; float rot = 0.0; integer l = llGetListLength(Avs); integer i; integer keys; for (i = l - aSTRIDE; i >= 0; i -= aSTRIDE) { key k = llList2Key(Avs, i + aKEY); keys = llList2Integer(Avs, i + aKEYS); if ("" == llList2Key(Avs, i + aADJ)) { 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(Avs, i + aDIST)); 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 = findAvatar(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(Avs, f + aADJ); integer l; integer t; if (ScriptKey == them) { t = 0; l = llGetListLength(Avs); } else { t = findAvatar(them); l = t + aSTRIDE; } if (-1 != t) { if (("turn left" == direction) || ("turn right" == direction)) dist = dist * 90.0; for (; t < l; t += aSTRIDE) { them = llList2Key(Avs, t + aKEY); integer num = llList2Integer(Avs, t + aNUM); integer lnk = llList2Integer(Avs, t + aLINK); if (-1 == lnk) return; string prn = llList2String(p, num); 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)], num, num); Poses = llListReplaceList(Poses, [llDumpList2String(p, "|")], i + psPOSROT, i + psPOSROT); updateAvatar(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); } } } } updateAvatar(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 = findAvatar(id); if (-1 != f) { integer lnk = llList2Integer(Avs, f + aLINK); if (-1 == lnk) return; integer num = llList2Integer(Avs, f + aNUM); vector pos = position; rotation rot = llEuler2Rot(rotat * DEG_TO_RAD); integer i; if ("" != nextPose) i = findPose(nextPose); else i = findPose(Pose); if (-1 != i) { string prn = llList2String(llParseStringKeepNulls(llList2String(Poses, i + psPOSROT), ["|"], []), num); 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 rotates around the avatar's centre, not the prims centre. ]); } } showMenu(key id) { integer f = findAvatar(id); if (-1 != f) { key them = llList2Key(Avs, f + aADJ); string name; integer distance = llList2Integer(Avs, f + aDIST); if ("" == them) { them = id; Avs = llListReplaceList(Avs, [them], f + aADJ, f + aADJ); 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; nextState = "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) nextState = "Hovering"; else if (vFWD == mode) { nextState = "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) nextState = "Hovering Up"; else nextState = "Hovering Down"; } if (cast(gp, pos, move / Smooth) > (pos.z - LeaderOffset)) { nextState = "Soft Landing"; vMODE = vFWD; vTHEN = llGetTimeOfDay(); addEvent(3.0, "SYNC Standing"); } } else if (vFALL == vMODE) { nextState = "Falling"; pos.z -= 1.0; if (cast(gp, pos, move / Smooth) > (pos.z - LeaderOffset)) { nextState = "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) nextState = "Falling"; else if (vCROUCH == vMODE) { if (0.0 == move) nextState = "Crouching"; else nextState = "CrouchWalking"; } else if (vGROUND == vMODE) nextState = "Sitting on Ground"; else { if (0.0 != rotate) { if (0.0 < rotate) nextState = "Turning Left"; else nextState = "Turning Right"; } if (0.0 == move) nextState = "Standing"; else nextState = "Walking"; } } else if (vUP == mode) { if (keys & CONTROL_UP) { if (vGROUND == vMODE) {nextState = "Crouching"; vMODE = vCROUCH;} else if (vCROUCH == vMODE) {nextState = "Standing"; vMODE = vFWD;} else {pos += (<0.0, 0.0, move>); vMODE = vUP;} } else { if (vCROUCH == vMODE) {nextState = "Sitting on Ground"; vMODE = vGROUND;} else if (0.0 > move) {nextState = "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(Avs); integer i; for (i = 0; i < l; i += aSTRIDE) { key a = llList2Key(Avs, i + aKEY); key t = llList2Key(Avs, i + aADJ); if (isEdge) { Avs = llListReplaceList(Avs, [a], i + aADJ, i + aADJ); adjust(a, move, "forward"); Avs = llListReplaceList(Avs, [t], i + aADJ, i + aADJ); } else llSetLinkPrimitiveParamsFast(llList2Integer(Avs, i + aLINK), [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; if (NULL_KEY != id) { vector bb = llGetAgentSize(Leader); LeaderOffset = (bb.z + 0.3) / 2; integer l = llGetListLength(Avs); integer i; integer oldLdr = -1; integer newLdr = -1; integer ldr = 0; if (Swapped) ldr = 1; for (i = 0; i < l; i += aSTRIDE) { if (llList2Integer(Avs, i + aNUM) == ldr) oldLdr = i; if (llList2Key(Avs, i + aKEY) == id) newLdr = i; } if (oldLdr != newLdr) { Avs = llListReplaceList(Avs, [ldr], newLdr + aNUM, newLdr + aNUM); Avs = llListReplaceList(Avs, [newLdr / aSTRIDE], oldLdr + aNUM, oldLdr + aNUM); } d("newLeader " + llKey2Name(Leader) + ", offset " + (string) LeaderOffset + "\n" + llDumpList2String(Avs, "|")); } else { Leader = id; LeaderOffset = 0.0; d("newLeader NONE, offset " + (string) LeaderOffset); } Pose = ""; State = ""; updateVehicle(0, vNONE, 0.0, 0.0); } 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) { if (Attached) { checkAO(); return; } list new = []; integer l = llGetNumberOfPrims(); integer lnk; for (lnk = llGetObjectPrimCount(llGetKey()) + 1; lnk <= l; ++lnk) new += [llGetLinkKey(lnk), lnk]; l = llGetListLength(Avs); if (0 != l) { for (lnk = 0; lnk < l; lnk += aSTRIDE) { key t = llList2Key(Avs, lnk + aKEY); if (NULL_KEY != t) { integer j = listFindString(new, t, 2); if (-1 == j) // old sitter that left { d("unsit " + llKey2Name(t)); stopAnims(t); oldController(t); Avs = llListReplaceList(Avs, [NULL_KEY, -1, -1], lnk + aKEY, lnk + aLINK); } else // old sitter that is still here { Avs = llListReplaceList(Avs, [llList2Integer(new, j + 1)], lnk + aLINK, lnk + aLINK); new = llListReplaceList(new, [], j, j + 1); } } } } l = llGetListLength(new); for (lnk = 0; lnk < l; lnk +=2) { key t = llList2Key(new, lnk); list n = [t, -1, llList2Integer(new, lnk + 1), "", "", "", llGetListLength(Distances) / 3, 0]; list gndr = llGetObjectDetails(t, [OBJECT_BODY_SHAPE_TYPE]); stopAnims(t); integer f = findAvatar(NULL_KEY); if (-1 != f) // new sitter replaces old Avs = llListReplaceList(Avs, n, f, f + aSTRIDE - 1); else // new sitter added on end Avs += n; if (NULL_KEY != BossKey) sendPrim(BossKey, "SIT_DONE", [t]); d("sat " + llKey2Name(t) + ", " + llList2String(gndr, 0) + " male @ " + (f / aSTRIDE)); } // Wearer is the default leader, unless anyone is taller. l = llGetListLength(Avs); integer m = l / aSTRIDE; integer ldr = findAvatar(Owner); float max = 0.0; if (-1 != ldr) { vector s = llGetAgentSize(Owner); max = s.z; } Controller = NULL_KEY; for (lnk = 0; lnk < l; lnk += aSTRIDE) { key t = llList2Key(Avs, lnk + aKEY); vector s = llGetAgentSize(t); Avs = llListReplaceList(Avs, [lnk / aSTRIDE], lnk + aNUM, lnk + aNUM); if ((NULL_KEY == Controller) && (Owner != t)) Controller = t; if (s.z > max) { max = s.z; ldr = lnk; } if (NULL_KEY == t) --m; } if (NULL_KEY != Controller) { d("checkSitters() requesting camera and controls from " + llKey2Name(Controller)); llRequestPermissions(Controller, PERMISSION_CONTROL_CAMERA | PERMISSION_TAKE_CONTROLS); } if (-1 != ldr) newLeader(llList2Key(Avs, ldr + aKEY)); else checkAO(); //for (lnk = 0; lnk < l; lnk += aSTRIDE) // S(llKey2Name(llList2Key(Avs, lnk + aKEY)) + " is number " + llList2String(Avs, lnk + aNUM)); for (lnk = 0; lnk < l; lnk += aSTRIDE) updateAvatar(llList2Key(Avs, lnk + aKEY)); if ("1AOor2 prim" == llGetObjectName()) { 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(""); llSetLinkPrimitiveParamsFast(Link, [PRIM_SIZE, <1.0, 1.0, 1.0>, PRIM_COLOR, <255, 199, 17>, 0.5]); } else if (1 == m) llSetLinkPrimitiveParamsFast(Link, [PRIM_SIZE, <1.0, 1.0, 1.0>, PRIM_COLOR, <255, 199, 17>, 0.25]); else if (2 == m) { llSetSitText(Sit1Text); llSetClickAction(CLICK_ACTION_TOUCH); llSetLinkPrimitiveParamsFast(Link, [PRIM_SIZE, <0.01, 0.01, 0.01>, PRIM_COLOR, <255, 199, 17>, 0.0]); } } return; } // Particle engine, for bubbles while swimming. integer noParticles = TRUE; updateParticles() { if (noParticles) { noParticles = FALSE; llParticleSystem( [ PSYS_PART_MAX_AGE, 16.0, PSYS_PART_FLAGS, PSYS_PART_EMISSIVE_MASK | PSYS_PART_INTERP_SCALE_MASK | PSYS_PART_WIND_MASK, PSYS_PART_START_COLOR, <1.0, 1.0, 1.0>, PSYS_PART_END_COLOR, <1.0, 1.0, 1.0>, PSYS_PART_START_SCALE, <0.05, 0.05, 0.05>, PSYS_PART_END_SCALE, <0.1, 0.1, 0.1>, PSYS_SRC_PATTERN, PSYS_SRC_PATTERN_EXPLODE, PSYS_SRC_BURST_RATE, 4.0, PSYS_SRC_ACCEL, <0.0, 0.0, 0.3>, PSYS_SRC_BURST_PART_COUNT, 1, PSYS_SRC_BURST_RADIUS, 0.2, PSYS_SRC_BURST_SPEED_MIN, 0.7, PSYS_SRC_BURST_SPEED_MAX, 0.001, PSYS_SRC_ANGLE_BEGIN, PI, PSYS_SRC_ANGLE_END, PI, PSYS_SRC_OMEGA, <0.0, -0.2, 0.0>, PSYS_SRC_MAX_AGE, 0.0, PSYS_PART_START_ALPHA, 1.0, PSYS_PART_END_ALPHA, 1.0, PSYS_SRC_TEXTURE, "soap-bubble" ]); } } // AO functions. integer loadCard(string card, integer casperMode) { if (NULL_KEY != llGetInventoryKey(card)) { float now = llGetTimeOfDay(); string section = ""; integer l = osGetNumberOfNotecardLines(card); integer i; 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 = findPose(name); if (-1 != found) { string data = llList2String(Poses, found + psANIM); if ("" != data) data += "|" + rest; rest = data; } savePose("AO_" + name, rest, NULL_KEY, NULL_KEY); } } d("Read " + card + " in " + (string) (llGetTimeOfDay() - now) + " seconds."); return TRUE; } return FALSE; } checkAO() { string newAnim; string oldAnim = State; integer l = llGetListLength(Avs); integer fast = 0; integer i; integer f; float dpth; if (NULL_KEY != TheirKey) return; if (Attached) { // if (llGetAgentInfo(Owner) & AGENT_ALWAYS_RUN) fast = 1; else fast = 0; nextState = llGetAnimation(Owner); } // TODO - when updateControls() figures out fast mode, use that. newAnim = nextState; if ("" != Posed) { s("Stopping pose " + Posed); Posed = ""; animSwitch(Owner, Posed); // Posed = ""; } if (("0" == getSetting("AO"))) { if ("" == nextPose) { // stopAnims(Owner); AOspeed = 1.0; speed(Owner, 1.0); return; } } if ("" != nextPose) { oldAnim = Pose; newAnim = nextPose; } //S("checkAO() " + oldAnim + " -> " + newAnim); AOspeed = 0.0; integer flying = llListFindList(flyStates, [nextState]); if (-1 != flying) { float water = llWater(ZERO_VECTOR); float ground = llGround(ZERO_VECTOR); integer fly = ("Flying" == nextState); // 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) > 0.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 *= -7; if ("Swimming Up" == alias("~" +nextState)) { 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; if ((!Swimming && (!noParticles))) { llParticleSystem([]); noParticles = TRUE; } 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; AOspeed = AOspeed * (float) getSetting("speed"); if (0.0 < AOspeed) speed(Owner, AOspeed + ((AOspeed / 2) * (fast + (2 * (integer) getSetting("super"))))); else AOspeed = 1.0; integer p; if ("" != nextPose) p = findPose(nextPose); else p = findPose("AO_" + nextState); Pose = nextPose; for (f = 0; f < l; f += aSTRIDE) { string old = oldAnim; string new = newAnim; list anims = llParseString2List(llList2String(Poses, p + psANIM), ["|"], []); key id = llList2Key(Avs, f + aKEY); integer num = llList2Integer(Avs, f + aNUM); //s("checkAO " + llKey2Name(id) + " " + old + " -> " + new + " @ " + nextPose + " \n" + //llDumpList2String(anims, "|")); if ("" != nextPose) { if ("~" == llList2String(anims, num)) { //s("POSE " + llKey2Name(id) + " nextState = " + nextState); integer q = findPose("AO_" + nextState); anims = llParseString2List(llList2String(Poses, q + psANIM), ["|"], []); old = llList2String(Avs, f + aSTATE); new = nextState; } else anims = [llList2String(anims, num)]; } //s("checkA1 " + llKey2Name(id) + " " + old + " -> " + new + " @ " + nextPose + " \n" + //llDumpList2String(anims, "|")); if (new != old) { // Ignore sits, since 99.99% of the time what they are sitting on supplies an animation. if ((("Sitting" == nextState) && Attached)) ; else { // If there's more than one, randomly switch between them at random times. i = llGetListLength(anims); 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)); //S("ANIMS " + llKey2Name(id) + " " + new + " -> " + llList2String(newAnims, i)); // for (i = llGetListLength(anims) - 1; i >= 0; --i) // { string a = llList2String(anims, i); //S("ANIMS " + llKey2Name(id) + " " + new + " -> " + a); animSwitch(id, a); doEmote(Pose, f); // } } Avs = llListReplaceList(Avs, [new], f + aSTATE, f + aSTATE); } } if ("" != Pose) State = nextPose; else State = nextState; if (("" == Pose) || ("Swimming Up" == State)) addEvent(Tick * dpth, "checkAO"); } 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 = llList2Key(anims, i); if (anim != "") animEnd(avatar, anim); } animSwitch(avatar, ""); } } 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(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() { 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."); 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) { s("AO mode."); if ("Sitting" != llGetAnimation(Owner)) stopAnims(Owner); Avs = [Owner, 0, -1, "", "", "", llGetListLength(Distances) / 3, 0]; nextPose = ""; nextState = ""; checkAO(); } else { llSitTarget(position, llEuler2Rot(rotat * DEG_TO_RAD)); if (NULL_KEY == BossKey) s("Vehicle mode."); else s("Vehicle mode, slaved to " + llKey2Name(BossKey) + "."); if (NULL_KEY != BossKey) { checkSitters(FALSE); sendPrim(BossKey, "REZ_DONE", []); addEvent(0.5, "PING"); // llSetLinkCamera(LINK_THIS, <-3.0, 0.0, 1.0>, <0.0, 0.0, 0.0>); } } addEvent(6.0 + llFrand(10.0), "SMILE"); } die() { integer l = llGetListLength(Avs); integer f; for (f = 0; f < l; f += aSTRIDE) stopAnims(llList2Key(Avs, f + aKEY)); 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("Switching AO to 1ring object."); nextPose = ""; 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); } } } }