// SF Poser: an animation controller - Documentation and the latest version are here: https://opensimworld.com/sfposer // This is the only script you need - No addons required. // (c) 2020 Satyr Aeon. Licensed under the GNU General Public License v3 // SPDX-License-Identifier: GPL-3.0-or-later integer autoTimer = 0; // timer in seconds integer allowRemote = 0; // 0 = only seated, 1 = everyone, 2=only sitting+dataserver 3=everyone+dataserver, 4=dataserver only float adjusterStep = 0.02; // step for adjustment menu (X+ , X- ...) in meters string lockMenus ="A"; // All string lockSit ="A"; list configOptions; list ncList; list groups; //names integer offset; string curGroup; integer groupSize; list poses; //names list poseData; string poseShortcodes; string curPose; list animAvis; //keys list animAnims;// inv names list animPos; list animRot; string mode; integer channel; key user = NULL_KEY; list handleIds; integer curHandle; string newMenu; list invAnims; string invAnimFilter; integer listener=-1; integer listenTs; integer poseTs; integer init; list addonList; // [button,commands] list npcList; // [name, notecard, uuid] list propList; // [name, object-id, user-id] list exprList; list lgList; // [position, leftwrist, lefthook] list rlvList; // [userid] list foundList; list hookedEvents; // [eventfilter, shortcode] list presets; // [name, shortcodes] string lastPreset; vector offsetPos = ZERO_VECTOR; rotation offsetRot= ZERO_ROTATION; runShortcodes(string m, integer level) // level =0 (pose line), level=1 (.SFconfig), level=2 (Button), level=3 (Remote api) { integer l; integer idx; string s; list codes = llParseStringKeepNulls(m, ["{", "}"], []); for (l = 0 ; l < llGetListLength(codes); l+=2) { string c = l2trim(codes, l); list tk = llParseStringKeepNulls(l2trim(codes, l+1), [";"], []); if (c == "") { } else if (c == "TIMER") autoTimer = llList2Integer(tk, 0); // TIMER{10} / TIMER{0} else if (c == "GIVE" && user != NULL_KEY) llGiveInventory(user, llList2String(tk, 0)); //GIVE{cup of tea} else if (c == "PROP" || c == "PROPATT" || c == "DELPROP" || c == "TOGGLEPROP" ||c == "MSGPROP") //PROP{fire pit;;} { idx = llListFindList(propList, l2trim(tk, 0)); if (idx>=0) { if (c == "TOGGLEPROP" || c == "DELPROP") { osMessageObject(llList2Key(propList, idx+1), "DIE"); // kill propList = [] + llDeleteSubList(propList, idx, idx+2); } else if (c == "MSGPROP") osMessageObject(llList2Key(propList, idx+1), l2trim(tk, 1) ); } if (c == "PROP" || ( c == "TOGGLEPROP" && idx <0 ) ) // PROP{MyProp;;} { propList += [llList2String(tk, 0), NULL_KEY, -1]; llRezAtRoot(llList2String(tk, 0), myPos()+llList2Vector(tk, 1)*myRot(), ZERO_VECTOR, rotFromStr( llList2String(tk, 2) ) *myRot(), 1); } else if (c == "PROPATT") // PROPATT{MyAttachProp;0} -- attach prop to user at pos 0 { propList += [llList2String(tk, 0), NULL_KEY, llList2Integer(tk,1) ]; llRezAtRoot(llList2String(tk, 0), myPos(), ZERO_VECTOR, ZERO_ROTATION, 1); } } else if (c == "MSGATT") // MSGATT{0;Hello attachment at points 19 and 4 of user 0;19,4} { key u = llList2Key(animAvis, llList2Integer(tk, 0)); if (llKey2Name(u) != "") osMessageAttachments(u, llList2String(tk, 1), llParseString2List(llList2String(tk, 2),[","],[]) , 0); } else if (c == "MSGOBJ") { // MSGOBJ{; SWITCHTOPOSE[myPose] } -- WARNING: replaces [ with '{' ! string ss = l2trim(tk, 1); ss = osReplaceString(ss, "\\[", "{", -1, 0); ss = osReplaceString(ss, "\\]", "}", -1, 0); osMessageObject( llList2Key(tk, 0), ss); } else if (c == "MSGLINK") osMessageObject( llGetLinkKey(llList2Integer(tk, 0)), llList2String(tk, 1) ); // MSGLINK(4;Hello link number 4} else if (c == "SAYCH" ) llSay(llList2Integer(tk, 0), replaceVars(llList2String(tk, 1), 0)); else if (c == "SAY") llSay(0, replaceVars(llList2String(tk, 0), 0)); else if (c == "USAY") say( replaceVars(llList2String(tk, 0), 0) ); else if (c == "REGIONSAY" ) llRegionSay(llList2Integer(tk, 0), replaceVars(llList2String(tk, 1), 0)); else if (c == "LINKMSG") llMessageLinked(llList2Integer(tk, 0), llList2Integer(tk, 1), llList2String(tk, 2), llDumpList2String(animAvis,"|") ); else if (c == "EXPR") exprList = tk; // [expr1;repeat-time1;expr2;repeat2 ....] else if (c == "ANIM" || c == "STOPANIM") // ANIM{clap;clap;clap;....} { for (idx=0; idx < llGetListLength(animAvis);idx++) if (llList2Key(animAvis,idx) != NULL_KEY) { if (c == "STOPANIM") osAvatarStopAnimation(llList2Key(animAvis,idx), l2trim(tk, idx ) ) ; else restartAn(llList2Key(animAvis,idx), l2trim(tk, idx) ); } } else if (c == "LG") lgList += [llList2Integer(tk, 0), llList2String(tk,1), llList2String(tk, 2)]; //LG{0;leftwrist gravity 1 ;lefthook} -- do NOT add the "link" or "unlink" part else if (c == "RLVCAPTURE") llSensor("", "", AGENT, llList2Float(tk, 0), PI) ; // RLVCAPTURE{10.0} -- radius 10m else if (c == "RLVRELEASE") { mode = "RlvRelease"; showDlg(); } else if (c == "RLV") // send rlv commands { for (idx=1; idx < llGetListLength(tk); idx++) relayRlv( llList2Key(animAvis, llList2Integer(tk, 0) ), l2trim( tk, idx) ); } else if (c == "SWITCHTOMENU" && level>1) trySwitchToGroup(l2trim(tk,0), TRUE); // SWITCHTOMENU{My Group} else if (c == "SWITCHTOPOSE" && level>1) switchToPose(l2trim(tk,0)); // SWITCHTOPOSE{My Pose} else if (c == "UNSIT" && level>1) llUnSit( llList2Key(animAvis, llList2Integer(tk,0) ) ); // UNSIT{0} else if (c == "SWAP" && level>1) swapPos( llList2Integer(tk,0), llList2Integer(tk,1) ); // SWAP{0,2} else if (c == "ADDNPC" || c == "DELNPC") { idx =llListFindList(npcList, l2trim(tk,0)); if (idx<0) { say("NPC Not found "+ l2trim(tk, 0)); return; } if (c == "ADDNPC" && llList2Key(npcList, idx+2) == NULL_KEY) { list nm = llParseStringKeepNulls(l2trim(tk,0),[" "], []); key nu = osNpcCreate( llList2String(nm,0), llList2String(nm,1), myPos()+<0,0,2>, llList2String(npcList, idx+1), 8|OS_NPC_CREATOR_OWNED ); osNpcSit(nu, llGetKey() , OS_NPC_SIT_NOW); npcList = [] + llListReplaceList(npcList, nu, idx+2, idx+2); } else if (c == "DELNPC") { osNpcRemove(llList2Key(npcList, idx+2)); npcList = [] + llListReplaceList(npcList, [NULL_KEY], idx+2, idx+2); } } else if (c == "TRIGGERSND") llTriggerSound(llList2String(tk, 0), 1.); else if (c == "LOOPSND") { llStopSound(); llLoopSound(llList2String(tk, 0), 1.); } else if (c == "PLAYSND") { llStopSound(); llPlaySound(llList2String(tk, 0), 1.); } else if (c == "STOPSND") llStopSound(); else if (c == "SLEEP") llSleep(llList2Float(tk, 0)); else if (c == "SETPRIM" || c == "SETPARTICLES" || c == "SHOW") // use the script "SFposer encoder for SETPRIM" to generate this shortcode { for (idx=1; idx <= llGetNumberOfPrims(); idx++) if (llGetLinkName(idx) == llList2String(tk, 0)) { if (c == "SHOW") llSetLinkAlpha(idx, 1.0*llList2Float(tk, 1) , ALL_SIDES); else if (c == "SETPRIM") llSetLinkPrimitiveParamsFast(idx, decodeList( llList2List(tk, 1, -1) ) ); else llLinkParticleSystem(idx, decodeList( llList2List(tk, 1, -1) ) ); } } else if (c == "SETNPRIM") llSetLinkPrimitiveParamsFast(llList2Integer(tk, 0), decodeList( llList2List(tk, 1, -1) ) ); else if (c == "PRESET" && level != -2 ) // do not allow recursively running preset { if ( lastPreset != l2trim(tk,0)) { lastPreset = l2trim(tk, 0); idx = llListFindList( presets, l2trim(tk, 0)); if (idx >=0) runShortcodes( llList2String(presets, idx+1) , -2 ); } } else if (c == "DELBUTTON" ) { idx = llListFindList(addonList, l2trim(tk, 1)); if (idx>=0) addonList = [] + llDeleteSubList(addonList, idx, idx+1); } else if (c != "" && llGetListLength(codes) >1) trigger( llDumpList2String( ["SFPOSER", c]+tk, "|"), 1); // this could be an unknown shortcode -- Send it to addon } } vector myPos() { return llGetPos() + offsetPos*llGetRot(); } rotation myRot() { return llGetRot() * offsetRot; } string getStatusString() { return "SFSTATUS|"+(string)user+"|"+curGroup+"|"+curPose+"|"+(string)autoTimer+"|"+mode+"|"+poseShortcodes+"|"+llDumpList2String(animAvis, ","); } string configOpt(string name) { integer idx = llListFindList(configOptions, [name]); if (idx>=0) return llList2String(configOptions, idx+1); return ""; } rotation rotFromStr(string s) { if (llGetListLength(osMatchString(s, ",", 0)) ==4) return llEuler2Rot( (vector) s ); else return (rotation)s; } integer hasAccess(key u, string a) { return ( (a=="A" || a=="0") || u==llGetOwner() || osIsNpc(u) || (a=="G" && llSameGroup(u)) || (a=="S" && (llListFindList(animAvis, llGetOwner()) >=0)) ) == TRUE; } runCommand(string line, integer lev) { list tk = llParseStringKeepNulls( line , ["="], []); string c = l2trim(tk,0); if ( c== "allowRemote" && lev == 1) allowRemote = llList2Integer(tk, 1); else if ( c == "adjusterStep" && lev == 1) adjusterStep = llList2Float(tk, 1); else if ( c== "lockMenus" && lev == 1) lockMenus = l2trim(tk, 1); else if ( c== "lockSit" && lev == 1) lockSit = l2trim(tk, 1); else if ( c== "autoTimer" && lev == 1) autoTimer = llList2Integer(tk, 1); else if ( c== "offsetPos" && lev == 1) offsetPos = llList2Vector(tk, 1); else if ( c== "offsetRot" && lev == 1) offsetRot = llEuler2Rot(DEG_TO_RAD*llList2Vector(tk, 1)); else if ( c == "Preset" && lev == 1) presets += [l2trim(tk,1), l2trim(tk, 2)]; else if ( c == "OnEvent" && lev == 1) hookedEvents += [l2trim(tk,1), l2trim(tk, 2)]; else if ( c == "Button" ) { integer idx = llListFindList(addonList, l2trim(tk, 1)); if (idx<0 && l2trim(tk, 1) != "" ) { addonList += [l2trim(tk,1), l2trim(tk, 2)]; } else if (idx>=0) { addonList = [] + llListReplaceList(addonList, [l2trim(tk,2)], idx+1, idx+1); } } else if (llSubStringIndex(line, "{") >1) runShortcodes(line, lev); else if (lev == 1 && llGetListLength(tk)>1) configOptions += [ llToLower(c), l2trim(tk, 1)]; // All other options } loadConfig() // .SFconfig lines can contain either A=B strings or plain shortcodes{} or # comments { integer l; if (llGetInventoryType(".SFconfig") == INVENTORY_NOTECARD) { list lines = llParseString2List(osGetNotecard(".SFconfig"), ["\n"], []); for (l=0; l < llGetListLength(lines) ; l++) if ( l2trim(lines, l) != "" && llGetSubString(l2trim(lines, l), 0, 0) != "#" ) runCommand( l2trim(lines, l), 1 ); } } string replaceVars(string str, integer i) // %USER00 -> "User One", %USER01 -> "User two" ... { list tk = llParseStringKeepNulls(" "+str+" " , ["%USER"], [] ); str = llList2String(tk,0); for (i=1; i < llGetListLength(tk); i++) str = str + llKey2Name( llList2Key(animAvis, (integer)(llGetSubString( llList2String(tk, i) , 0, 1))) ) + llGetSubString(llList2String(tk, i), 2, -1); return llStringTrim(str, STRING_TRIM); } list decodeList(list tk) { integer i; list out =[]; for (i=0; i < llGetListLength(tk); i+=2) { string c = l2trim(tk, i); if (c =="I") out += llList2Integer(tk, i+1); else if (c =="V") out += llList2Vector(tk, i+1); else if (c =="R") out += llList2Rot(tk, i+1); else if (c =="K") out += llList2Key(tk, i+1); else if (c =="F") out += llList2Float(tk, i+1); else if (c =="S") out += llList2String(tk, i+1); } return out; } restartAn(key id, string an) { osAvatarStopAnimation(id, an); osAvatarPlayAnimation(id, an); } startListen() { if (listener>=0) return; listener = llListen(channel, "", "", ""); listenTs = llGetUnixTime(); } integer fixOffset(integer off, integer maxx) { if (off >= maxx || off <0) return 0; return off; } showDlg() { string title = "."; list opts; integer i; integer maxOffset; list cmds = ["BACK","OPTIONS", "QUIT"]; if (mode == "SWAP") { for (i=0; i < llGetListLength(animPos); i++) { key u = llList2Key(animAvis, i); if (u !=user) { if (u == NULL_KEY) opts += (string)(i+1)+" (empty)"; else opts += (string)(i+1)+" "+llGetSubString(llKey2Name(u), 0, 7); } } title = "Swap with whom?"; } else if (mode == "RENAME" || mode == "NEW POSE") { startListen(); llTextBox(llGetOwner(), "Enter a new name:", channel); return; } else if (llGetListLength(handleIds) || mode == "Editing") { opts = [ "", "RENAME", "NEW POSE", "FILTER ANIMS", "DEL POSE"]; title = "Editing "+curGroup+ " > "+curPose + ".\nClick handles to select animation.\nMove handles to change positions.\nClick SAVE POSE after each pose.\nClick Edit Off to abort.\n\nChanges WILL NOT be saved until you press SAVE MENU"; cmds = ["EDIT OFF", "SAVE MENU", "SWAP"]; } else if (mode == "POSES" || mode == "BACK") { mode = "POSES"; opts = poses; title = "Poses Menu: "+curGroup+ " ("+(string)llGetListLength(poses)+" poses / "+(string)groupSize+" avis)\nPose: "+curPose ; if (autoTimer>0) title += " [Timer: "+(string)autoTimer+" sec]"; title += "\nChange Pose:"; if (groupSize >1) cmds = ["MENUS", "OPTIONS", "SWAP"]; else cmds = ["MENUS", "OPTIONS", "QUIT"]; } else if (mode == "MENUS") { opts = groups; if (configOpt("mainmenutitle") != "" ) title = configOpt("mainmenutitle"); else { title = "Menu: "+curGroup+"\nSelect Poses Menu: "; if (llGetListLength(addonList)>0) title += "\n(Addon Options are available)"; } } else if (mode == "EXPRESSION") { title = "Select Expression:"; opts = [ "open_mouth", "surprise_emote", "tongue_out","smile", "toothsmile", "wink_emote", "cry_emote","kiss", "laugh_emote","disdain", "repulsed_emote", "anger_emote", "bored_emote","sad_emote", "embarrassed_emote", "frown","shrug_emote", "afraid_emote", "worry_emote" ]; } else if (mode == "RlvCapture" || mode == "RlvRelease") { title = "Capture whom?"; list ids = foundList; if (mode == "RlvRelease") { title = "Release Whom?"; ids = rlvList; } if (!llGetListLength(ids)) { say("Nobody found"); mode = ""; return; } for (i=0; i < llGetListLength(ids) && i < 9; i++) opts += (string)(i+1)+" "+llGetSubString( llKey2Name( llList2Key(ids, i) ) , 0, 10); } else if (mode == "ADJUST") { opts = ["X+", "Y+", "Z+" , "X-", "Y-", "Z-", "SYNC", "SWAP"]; title = "Adjusting pose: "+curPose; cmds = ["BACK", "OPTIONS","MENUS"]; } else if (mode == "OPTIONS") { cmds = ["BACK", "ADJUST", "QUIT"] ; opts += llList2ListStrided(addonList, 0, -1, 2); opts += ["TIMER", "SYNC", "EXPRESSION", "SWAP"]; if (llGetListLength(npcList)) opts += "NPCs"; if (user == llGetOwner()) { opts += ["EDIT POSE"]; if (lockMenus!="A") opts += "UNLOCK MENU"; else opts += "LOCK MENU"; opts += ["NEW MENU", "UPGRADE"]; } title = "Options"; } else if (mode == "ADD NPC" || mode == "DEL NPC" ) { for (i=0; i llGetListLength(opts) || offset <0) offset=0; if (llGetListLength(opts) >9) opts = llList2List(opts, offset, offset+7) + ">>"; while (llGetListLength(opts) < 9) opts += ["."]; startListen(); listenTs = llGetUnixTime(); llDialog(user, title, cmds + llList2List(opts, 6,8) + llList2List(opts , 3,5) + llList2List(opts , 0,2) , channel); } rezHandles() { if (llGetListLength(handleIds) , <0,0.5,1>, <0,1,0>, <1,1,0>, <0,0,1>, <1,0,0>, <0,1,1>, <1,1,1>, <0,0,0>, <.5,.5,.5>]; for (i =0; i < groupSize; i++) osSetPrimitiveParams( llList2Key(handleIds, i), [PRIM_SIZE, <.1, .1, 3>, PRIM_TEXT, (string)(i+1), llList2Vector(handleColors, i%10) , 1.0, PRIM_COLOR, ALL_SIDES, llList2Vector(handleColors, i%10), 0.5, PRIM_POSITION, myPos() + llList2Vector(animPos, i)*myRot(), PRIM_ROTATION, llList2Rot(animRot,i)*myRot(), PRIM_DESC, (string)llGetKey() ]); } loadInvAnims() { if (llGetListLength(invAnims) ==0) { offset=0; integer i; string sn; llSay(0,"Loading animations list ..."); for (i =0; i < llGetInventoryNumber(INVENTORY_ANIMATION); i++) { sn = llGetInventoryName(INVENTORY_ANIMATION, i); if ( sn != "~baseAnim" && (invAnimFilter == "" || (llSubStringIndex(sn, invAnimFilter) >=0))) invAnims += llGetInventoryName(INVENTORY_ANIMATION, i); } llSay(0, "done."); } } loadNCs(integer doNpcs) { integer i; ncList = []; if (doNpcs) npcList = []; string gn; for (i=0; i < llGetInventoryNumber(INVENTORY_NOTECARD); i++) { string nc = llGetInventoryName(INVENTORY_NOTECARD, i); if (llGetSubString( nc, 0, 4) == ".menu") { integer gs; string prm; if (llGetSubString(nc, 9,9) == " ") { // "old" format with single-digit number of avis gs = (integer)llGetSubString(nc, 7,7); prm = llGetSubString(nc, 8,8); } else { gs = (integer)llGetSubString(nc, 7,8); prm = llGetSubString(nc, 9,9); } gn = llStringTrim(llGetSubString(nc, 10, -1), STRING_TRIM); if (gn != "") ncList += [gn, prm, gs, nc]; } else if (doNpcs && llGetSubString( nc, 0, 3) == ".NPC") npcList += [llGetSubString(nc, 8,-1), nc, NULL_KEY]; } } killNpcs() { integer i; for (i=0; i< llGetListLength(npcList); i+=3) { if (llList2Key(npcList,i+2) != NULL_KEY) osNpcRemove(llList2Key(npcList,i+2)); npcList = llListReplaceList(npcList, [NULL_KEY], i+2,i+2); } } killRezzed(integer force) { integer i; for (i= llGetListLength(propList)-3; i>=0; i-=3) { key id = llList2Key(propList,i+1); if (force==1 || llList2Integer( llGetObjectDetails(id, [OBJECT_ATTACHED_POINT]), 0 ) <= 0 ) // Do not delete if prop is attached unless force=1 (quitting) { propList = [] + llDeleteSubList(propList, i, i+2); if (llKey2Name(id) != "") osMessageObject(id, "DIE"); } } } string l2trim(list l, integer i) { return llStringTrim( llList2String(l, i), STRING_TRIM); } relayRlv(key id, string m) { if (llKey2Name(id) != "" && m != "") llSay(-1812221819, llGetObjectName()+","+ (string)id + "," +m ); } rlvRelease(key u) { relayRlv( u , "@unsit=y"); relayRlv( u , "!release"); } loadPoses(string nc) { integer i; poses = []; poseData = []; list lines = llParseStringKeepNulls(osGetNotecard(nc), ["\n"], []); for (i=0; i < llGetListLength(lines); i++) { string ln = llList2String(lines, i); integer idx = llSubStringIndex(ln, "|"); if (idx>0) { string sn = llStringTrim( llGetSubString(ln, 0, idx-1), STRING_TRIM); if (sn !="") { poses += sn; poseData += llGetSubString(ln, idx+1, -1); } } } poses = [] + poses; poseData = [] + poseData; } string trySwitchToGroup(string gName, integer printErr) { integer i = llListFindList(ncList, gName); string err; if ( i<0 || (llList2Integer(ncList, i+2) < (llGetNumberOfPrims()- llGetObjectPrimCount(llGetKey())) ) || (llList2Integer(ncList, i+2) <1)) err = ("ERROR: Menu "+gName+" is for "+(string)llList2Integer(ncList, i+2)+" users"); else if ( hasAccess(user, llList2String(ncList, i+1)) == FALSE) err = ("Access to menu is not allowed"); else { curGroup = gName; groupSize = llList2Integer(ncList, i+2); list avOld = animAvis; animAvis = []; integer n; for (n =0; n < llGetListLength(avOld); n++) if (llList2Key(avOld,n) != NULL_KEY) animAvis += llList2Key(avOld, n); while (llGetListLength(animAvis) < groupSize ) { animAvis += NULL_KEY; n++; } loadPoses(llList2String(ncList, i+3)); poseTs = llGetUnixTime(); return ""; } if (err != "" && printErr>0) say(err); return err; } integer doAutoSwitch(integer totSeated) { integer n; if (totSeated == groupSize) return TRUE; for (n =2; n < llGetListLength(ncList); n+=4) { if (llList2Integer(ncList, n) >= totSeated && trySwitchToGroup( llList2String(ncList, n-2) , FALSE ) == "") { controlAnims(0); curPose = llList2String(poses, 0); if (curPose != "") setPose(curPose); return TRUE; } } return FALSE; } killAnims(key u) { list ans =llGetAnimationList(u); integer i; for (i = llGetListLength(ans); i>=0 ; i--) osAvatarStopAnimation(u, llList2Key(ans,i)); } controlAnims(integer doStart) //0 = stop, 1= setpositions only, 2 = start { integer total=llGetNumberOfPrims(); integer i; for (i = llGetObjectPrimCount(llGetKey()); i < total; i++) { key u = llGetLinkKey(i+1); integer idx = llListFindList( animAvis, u); if (idx>=0) { if (doStart>0) { vector pos = llList2Vector(animPos, idx); rotation rot = llList2Rot(animRot, idx); llSetLinkPrimitiveParamsFast(i+1, [PRIM_ROT_LOCAL, rot*offsetRot, PRIM_POS_LOCAL, ( pos + <0.0, 0.0, 0.4> )*offsetRot + offsetPos ]); if (doStart == 2) osAvatarPlayAnimation(u, llList2String(animAnims, idx) ); } else osAvatarStopAnimation(u, llList2String(animAnims, idx) ); } } } setPose( string pose) { integer i; curPose = pose; exprList =[]; if (curPose != "") { integer idx = llListFindList(poses, pose); if ( idx >=0) { list tk = llParseStringKeepNulls (llList2String(poseData, idx), ["|"], []); animAnims = []; animPos = []; animRot = []; poseShortcodes = llList2String(tk, 0); // shortcode line for (i=1; i < groupSize*3 ; i+=3) // fill with empty if needed { animAnims += llList2String(tk, i); animPos += (vector)llList2String(tk, i+1); animRot += (rotation)llList2String(tk, i+2); } } } } setLG(integer isOn, key specificUser) { integer i; integer idx; for (i=0; i < llGetListLength(lgList); i+=3) { for (idx=2; idx <= llGetNumberOfPrims(); idx++) if (llGetLinkName(idx) == llList2String(lgList, i+2) && llList2Key(animAvis, llList2Integer(lgList,i)) != NULL_KEY && (specificUser == NULL_KEY || (specificUser == llList2Key(animAvis, llList2Integer(lgList,i)))) ) { if (isOn>0) llWhisper(-9119, "lockguard "+(string)llList2Key(animAvis, llList2Integer(lgList,i) ) +" " + llList2String(lgList, i+1) + " link " + (string)llGetLinkKey(idx)); else llWhisper(-9119, "lockguard "+(string)llList2Key(animAvis, llList2Integer(lgList,i) ) +" " + llList2String(lgList, i+1) + " unlink " + (string)llGetLinkKey(idx)); } } if (isOn<=0 && specificUser == NULL_KEY) lgList = []; } switchToPose(string m) { if (llGetListLength(propList)>0) killRezzed(0); if (llGetListLength(lgList)>0) setLG(0, NULL_KEY); controlAnims(0); setPose(m); if (curPose != "") { controlAnims(2); // start trigger("GLOBAL_NEXT_AN|"+poseShortcodes, 1); if (llSubStringIndex(poseShortcodes, "{")>1) runShortcodes(poseShortcodes, 0); poseTs = llGetUnixTime(); setLG(1, NULL_KEY); llSetTimerEvent(1); } } switchUser(key u) { if (osIsNpc(u)) u = llGetOwner(); // override for npcs if (user != u) { integer i; groups = []; offset =0; user = u; for (i =0; i < llGetListLength(ncList); i+= 4) if ( hasAccess(user, llList2String(ncList, i+1) ) ) groups += l2trim(ncList, i); trigger("GLOBAL_NEW_USER_ASSUMED_CONTROL|"+(string)u, 1); } } say(string str) { llRegionSayTo(user, 0, str); } editOff() { integer i; for (i=0; i < llGetListLength(handleIds); i++) osMessageObject(llList2Key(handleIds, i), "DIE"); handleIds = []; } trigger(string s, integer addpos) // API { if (llGetListLength(hookedEvents)) { integer i; for (i =0; i < llGetListLength(hookedEvents); i+=2) if ( llSubStringIndex( s, llList2String(hookedEvents, i)) ==0) runShortcodes(llList2String(hookedEvents, i+1), 2); } if (addpos) llMessageLinked(LINK_THIS, 0, s, llDumpList2String(animAvis,"|")); else llMessageLinked(LINK_THIS, 0, s, NULL_KEY); } findInitialPose() { if (configOpt("defaultgroup") != "" && llListFindList(groups, configOpt("defaultgroup") ) >=0) curGroup = configOpt("defaultgroup"); else curGroup = llList2String(groups,0); trySwitchToGroup(curGroup, TRUE); curPose = llList2String(poses, 0); if (curPose != "") setPose(curPose); } swapPos(integer a, integer b) { if (a >=0 && a< groupSize && b >=0 && b < groupSize && a != b) { key o = llList2Key(animAvis, b); controlAnims(0); animAvis = llListReplaceList(animAvis, llList2Key(animAvis,a) , b, b); animAvis = [] + llListReplaceList(animAvis, o, a, a); controlAnims(2); } } invAnimsDlg() { integer i; string str; list opts; if (offset < 0 || offset > llGetListLength(invAnims)) offset =0; for (i= 0; i < 9 && (i +offset< llGetListLength(invAnims)) ; i++) { str += (offset+i)+":"+llList2String(invAnims, i+offset)+"\n"; opts += (string)(offset+i)+" "+llGetSubString(llList2String(invAnims, i+offset) , 0, 8); } startListen(); llDialog(user, "Select anim:\n"+str, ["<<<", "DONE", ">>>"] + opts, channel); } string getPropsShortcode() { string str =""; integer i; for (i=0; i< llGetListLength(propList); i+=3) { if (llList2Integer(propList, i+2) < 0) { list ll = llGetObjectDetails(llList2Key(propList,i+1), [OBJECT_POS, OBJECT_ROT]); str += " PROP{"+llList2String(propList, i)+";" + (string) ( (llList2Vector(ll,0)-myPos())/myRot() ) + ";"+(string)( llList2Rot(ll, 1) / myRot() ) + "}"; } } return str; } standAll() { while (llGetObjectPrimCount(llGetKey()) < llGetNumberOfPrims() ) llUnSit( llGetLinkKey( llGetObjectPrimCount(llGetKey())+1 )); } doCleanup() { editOff(); setLG(0, NULL_KEY); killNpcs(); killRezzed(1); integer l; for (l=0; l < llGetListLength(rlvList); l++) rlvRelease( llList2Key(rlvList, l) ); } default { state_entry() { init =0; if (llGetStartParameter() == 99) { llSetRemoteScriptAccessPin(0); llOwnerSay("Updated!"); } if (llGetLinkNumber() > 1) { llSay(0, llGetScriptName()+" must be placed in the root prim!"); return; } if (llGetInventoryType("~positioner") != INVENTORY_OBJECT || llGetInventoryType("~baseAnim") != INVENTORY_ANIMATION) llSay(0, "You must add the animation '~baseAnim' and the object '~positioner' (full perm) to this object"); llSitTarget( <0,0, 0.001>, ZERO_ROTATION); channel = -1 - (integer)("0x" + llGetSubString( (string) llGetKey(), -6, -1) )-393; loadNCs(1); loadConfig(); if (llGetListLength(ncList) ==0) { llSay(0, "No menus found. Right click to touch me, and select Options->New Menu to create menus"); allowRemote =1; } trigger("GLOBAL_SYSTEM_RESET", 0); } changed(integer c) { if (c & CHANGED_LINK) { integer total=llGetNumberOfPrims(); integer prims =llGetObjectPrimCount(llGetKey()); integer nonNpcs=0; integer l; list seated = []; llSleep(.25); controlAnims(0); for ( l = prims; l < total; l++) { key u = llGetLinkKey(l+1); if (!osIsNpc( u )) nonNpcs++; seated += u; integer idx = llListFindList(animAvis, [u]); if (idx==-1) // Sit { if (!hasAccess(u, lockSit)) { llUnSit(u); llSay(0, "Access not allowed"); return; } if (init == 0) { init = 2; trigger("GLOBAL_START_USING", 1); switchUser(u); findInitialPose(); } if ( ((integer)configOpt("autoswitchgroup") ) > 0 && total-prims != groupSize ){ doAutoSwitch( total-prims ); init = 3; } idx = llListFindList(animAvis, [NULL_KEY]); if (idx==-1) { llUnSit(u); llSay(0, "The current menu is for "+(string)groupSize+" users only!"); return; } else { animAvis = [] + llListReplaceList(animAvis, [u], idx, idx); // seated killAnims(u); osAvatarPlayAnimation(u, "~baseAnim"); trigger("GLOBAL_USER_SAT|"+(string)idx+"|"+(string)u, 1); } } } for (l=0; l < llGetListLength(animAvis); l++) { key u = llList2Key(animAvis, l); if (u != NULL_KEY && llListFindList(seated, [u]) < 0) { // u Unsat setLG(0, u); osAvatarStopAnimation(u, llList2String(animAnims, l)); osAvatarStopAnimation(u, "~baseAnim"); animAvis = [] + llListReplaceList(animAvis, [NULL_KEY], l, l); trigger("GLOBAL_USER_STOOD|"+(string)l+"|"+(string)u, 1); if ( ( ((integer)configOpt("autoswitchgroup") ) == 1) && total-prims != groupSize ){ doAutoSwitch( total-prims ); init = 3; } } } if (init == 2 || init == 3) { if (llGetListLength(seated)) switchToPose(curPose); init = 4; if ( (integer)configOpt("showmenusonstart")>0 ) mode = "MENUS"; else mode = "POSES"; if ( (integer)configOpt("showdialogonsit") >0 ) { showDlg(); } } else { controlAnims(2); //restart } if (llGetListLength(seated) >0 && nonNpcs ==0 && (configOpt("allowsolonpc")=="0") ) { standAll(); } else if (llGetListLength(seated)==0) { doCleanup(); trigger("GLOBAL_SYSTEM_GOING_DORMANT", 0); llResetScript(); } } else if (c & CHANGED_INVENTORY) invAnims =[]; else if (c & (CHANGED_REGION_START|CHANGED_OWNER) ) llResetScript(); } touch_start(integer d) { key u = llDetectedKey(0); if (! hasAccess(u, lockMenus) || (llListFindList(rlvList, u)>=0) ) { llRegionSayTo( llDetectedKey(0), 0, "Menu is locked"); return; } if ( ((allowRemote == 1 || allowRemote == 3) || ((allowRemote ==0 || allowRemote ==2) && (llListFindList(animAvis, u) >= 0) ) ) == FALSE ) return; if (user == NULL_KEY || llKey2Name(user) == "") { switchUser(llDetectedKey(0)); if (init == 0) { findInitialPose(); mode = ""; init=2; } } else if (u != user) { startListen(); llDialog(u, "Take control from "+llKey2Name(user)+"?", ["TAKE CONTROL", "CLOSE"], channel); return; } if (mode == "") { if ( (integer)configOpt("showmenusonstart")>0 ) mode = "MENUS"; else mode = "POSES"; } showDlg(); } listen(integer c, string n, key id, string m) { if (m == "CLOSE" || m == ".") return; else if (m == "<<" ) offset -=8; else if (m == ">>" ) offset +=8; else if (m == "BACK" || m == "POSES" || m == "MENUS" || m == "OPTIONS" || m == "ADJUST" || m == "EXPRESSION" || m == "TIMER" || m == "DONE" || m == "NPCs" || m == "RENAME" || m == "NEW POSE") { offset=0; mode = m; } else if (m == "SYNC") { integer i; for (i=0; i < llGetListLength(animAvis); i++) if (llList2Key(animAvis, i) != NULL_KEY) restartAn( llList2Key(animAvis, i), llList2Key(animAnims, i)); trigger("GLOBAL_ANIMATION_SYNCH_CALLED", 1); } else if (m == "QUIT") { killRezzed(1); standAll(); return; } else if (m == "SWAP") { if (groupSize < 3) { if (groupSize == 2) swapPos(0, 1); mode = "POSES"; } else mode = "SWAP"; } else if (m == "TAKE CONTROL") { switchUser(id); mode = "POSES"; } else if (m == "EDIT POSE") { if (curGroup != "" && curPose != "") { autoTimer=0; mode = "Editing"; say("Editing pose. Click the handles to change animations. Changes won't be saved until you press Save Menu"); rezHandles(); trigger("GLOBAL_NOTICE_ENTERING_EDIT_MODE", 1); } } else if (m == "LOCK MENU" ) { lockMenus="O"; say("Menu Locked to Owner"); } else if (m == "UNLOCK MENU") { lockMenus="A"; say("Menu Unlocked!"); } else if (m == "EDIT OFF") { listenTs = llGetUnixTime(); editOff(); mode = "POSES"; trigger("GLOBAL_NOTICE_LEAVING_EDIT_MODE",1); } else if (m == "SAVE POSE") { integer i; integer idx = llListFindList(poses, curPose); if (idx >=0) { string an; string props = getPropsShortcode(); if (props != "") { poseShortcodes = props + osReplaceString(poseShortcodes, "PROP{.*?}", "", -1, 0); say("The following PROP shortcodes will be saved in the notecard when you press Save Menu:\n"+props); } string str = poseShortcodes; if (str == "") str = "NOCODE"; for (i=0; i < llGetListLength(animAnims); i++) { an = l2trim(animAnims,i); if (an =="") an ="NoAnim"; str += "|"+an+"|"+(string)llList2Vector(animPos,i)+"|"+(string)llList2Rot(animRot,i); } poseData = [] + llListReplaceList(poseData, [str], idx, idx); trigger("GLOBAL_STORE_ADDON_NOTICE",1); } } else if (m == "" || m == "DEL POSE") { if (!llGetListLength(handleIds)) return; integer idx = llListFindList(poses, curPose); if (m == "") idx++; else if (m == "DEL POSE") { say("Deleting pose '"+llList2String(poses, idx)+"'. Changes will be saved when you Save Menu"); poses = [] + llDeleteSubList(poses, idx, idx); poseData = [] + llDeleteSubList(poseData, idx, idx); } idx = fixOffset(idx, llGetListLength(poses) ); switchToPose(llList2String(poses, idx)); setHandles(); } else if (m == "FILTER ANIMS") { llTextBox(llGetOwner(), "Enter a filter below. Only the animations containing that filter will appear in the list. Leave empty for no filter", channel); mode = "WaitAnimFilter"; return; } else if (m == "SAVE MENU") { string str; integer i; for (i=0; i < llGetListLength(poses); i++) { str += osReplaceString( llList2String(poses, i)+"|"+llList2String(poseData,i)+"\n" , "(\\.[0-9]+?)0+([>,])", "$1$2", -1, 0); // Remove trailing zeros } integer idx = llListFindList(ncList, curGroup); if (idx>=0) { string nc = llList2String(ncList, idx+3); llRemoveInventory(nc); llSleep(.3); osMakeNotecard(nc, str); say(nc+" saved"); trigger("GLOBAL_EDIT_STORE_TO_CARD|"+nc,1); } } else if (m == "NEW MENU") { newMenu = ""; llTextBox(llGetOwner(), "Enter the name for new menu:", channel); mode = "NewMenuName"; return; } else if (m == "TIMER OFF") { autoTimer =0; mode = "POSES"; } else if (m == "Custom..." ) { llTextBox(user, "Enter Auto Timer seconds. (Enter 0 to disable)", channel); return; } else if (m == "ADD NPC" || m=="DEL NPC" || m == "DEL ALL") { if (m == "DEL ALL") { killNpcs(); return; } else { mode = m; offset=0; } } else if (m == "UPGRADE") { llOwnerSay("Attempting upgrade. Make sure there is an SFposer upgrader owned by you somewhere in this region"); llRegionSay(-1293191, "SFUPGRADEME"); return; } else if (mode == "MENUS") { if (curGroup != m) trySwitchToGroup(m, TRUE); mode="POSES"; offset=0; } else if (mode == "POSES") { if (llListFindList(poses, m) >=0) switchToPose(m); } else if (mode == "ADD NPC" || mode == "DEL NPC") { if (mode == "ADD NPC") runShortcodes("ADDNPC{"+m+"}", 1); else runShortcodes("DELNPC{"+m+"}", 1); } else if (mode == "EXPRESSION") { restartAn(user, "express_"+m); } else if (mode == "TIMER") { autoTimer = fixOffset( (integer)m, 99999); say("Auto timer set to "+m+" seconds"); poseTs = llGetUnixTime(); llSetTimerEvent(1); mode = "OPTIONS"; } else if (mode == "RENAME" || mode == "NEW POSE") { m = llStringTrim(llGetSubString(m,0,16), STRING_TRIM); integer idx = llListFindList(poses, m); if (m == "" || llListFindList(poses, m) >=0 || curPose == "" ) say("ERROR! Pose name is empty or already exists. Try again"); else { idx = llListFindList(poses, curPose); if (idx>=0) { if (mode == "NEW POSE") { poses += m; poseData += llList2String(poseData, idx); // Copy from current curPose = m; say("Created new pose "+curPose+". Please press SAVE POSE now"); } else { poses = [] + llListReplaceList(poses, m, idx, idx); say("Pose "+curPose+" renamed to "+m); curPose = m; } } mode = "OPTIONS"; } } else if (mode == "RlvCapture") { key u = llList2Key( foundList, ((integer)llGetSubString(m, 0, 0)) -1 ); if (llKey2Name(u) != "" && llListFindList(rlvList, [u]) < 0) { say( "Capturing " + llKey2Name( u) ) ; relayRlv(u , "@sit:"+ (string)llGetKey() + "=force"); relayRlv(u , "@unsit=n"); rlvList += u; mode = "OPTIONS"; } } else if (mode == "RlvRelease") { integer idx = ((integer)llGetSubString(m, 0, 0)) -1; if (idx >=0 && llKey2Name( llList2Key(rlvList, idx) ) != "" ) { say("Releasing " +llKey2Name( llList2Key(rlvList, idx) ) ) ; rlvRelease(llList2Key(rlvList, idx) ); rlvList = [] + llDeleteSubList(rlvList, idx, idx); } mode = "OPTIONS"; } else if (mode == "WaitAnimFilter") { invAnimFilter = llStringTrim(m, STRING_TRIM); invAnims = []; mode = ""; } else if (mode == "NewMenuName") { newMenu= llStringTrim( llGetSubString(m, 0, 16), STRING_TRIM); if (newMenu !="") { mode = "NewMenuSize"; llTextBox(user, "How many avatars for menu '"+newMenu+"'?\nPlease enter a number from 1 to 99", channel); } return; } else if (mode == "NewMenuSize") { integer nn = (integer)m; if (nn <1 || nn > 99 || newMenu == "") { say("ERROR! Number must be from 1 to 99"); mode = ""; return; } string nc; if (nn < 10) nc = ".menu000"+(string)nn+"A "+newMenu; else nc = ".menu00"+(string)nn+"A "+newMenu; if (llGetInventoryType(nc) == INVENTORY_NOTECARD) { say("ERROR! Menu Notecard "+nc + " already exists, aborting!"); mode = ""; return; } osMakeNotecard(nc, "Pose1|NOCODE|"); llSleep(1.); say("Created menu notecard "+nc+" with an empty pose 'Pose1'. You can now sit and select OPTIONS->EDIT POSE to edit/rename poses"); controlAnims(0); curPose = "Pose1"; loadNCs(0); trySwitchToGroup(newMenu, TRUE); switchToPose(curPose); mode = ""; return; } else if (mode == "InvAnims") { // Special dialog if (m == ">>>") offset += 9; else if (m == "<<<") offset-= 9; else { m = llList2String(invAnims, (integer)(llGetSubString(m, 0, 2))); if (llGetInventoryType(m) != INVENTORY_ANIMATION) return; controlAnims(0); animAnims = llListReplaceList(animAnims, [m], curHandle, curHandle); controlAnims(2); } invAnimsDlg(); return; } else if (mode =="SWAP") { swapPos( llListFindList(animAvis, [user]), (integer)llGetSubString(m, 0,1) -1); mode = "POSES"; } else if (mode == "ADJUST") { vector v ; if (m == "X+") v.x+=adjusterStep; else if (m == "X-") v.x-=adjusterStep; else if (m == "Y+") v.y+=adjusterStep; else if (m == "Y-") v.y-=adjusterStep; else if (m == "Z+") v.z+=adjusterStep; else if (m == "Z-") v.z-=adjusterStep; integer idx = llListFindList(animAvis, user); if (idx>=0) { v = llList2Vector(animPos, idx) + v; animPos = llListReplaceList(animPos, v, idx, idx); controlAnims(1); // positions only } } else if (mode == "OPTIONS") { integer idx = llListFindList(addonList, m); if (idx<0) return; string d = llList2String(addonList, idx+1); if (llSubStringIndex(d, "{") >1) runShortcodes(d, 2); else trigger( d +"|"+user, 1); // PMAC compatible button if (!(integer)configOpt("showoptionsagain")) return; } showDlg(); } timer() { integer idx; if (llGetListLength(handleIds)) { llSetTimerEvent(0); for (idx =0;idx < llGetListLength(animPos); idx++) { list p = llGetObjectDetails(llList2Key(handleIds, idx), [OBJECT_POS, OBJECT_ROT]); vector v = (llList2Vector(p,0) - myPos()) / myRot(); rotation r = llList2Rot(p, 1)/myRot(); animPos = [] + llListReplaceList(animPos, v, idx,idx); animRot = [] + llListReplaceList(animRot, r, idx,idx); } controlAnims(1); llSetTimerEvent(0.3); return; } if (llGetListLength(exprList)>0) { llSetTimerEvent(0); integer ts = llGetUnixTime(); for (idx=0; idx 0) llSetTimerEvent(autoTimer); else llSetTimerEvent(300); if (autoTimer>0) { if ( (llGetUnixTime() - poseTs) > autoTimer) { idx = llListFindList(poses, curPose) +1; if (idx >= llGetListLength(poses)) idx=0; string np = llList2String(poses, idx); if (np != "") switchToPose(np); } } if ( listener>0 && (llGetUnixTime() - listenTs) > 200) { llListenRemove(listener); listener = -1; } if (listener <0 && autoTimer <=0 && llGetListLength(exprList)<=0) llSetTimerEvent(0); } on_rez(integer n) { llResetScript(); } object_rez(key id) { llSleep(.2); integer idx = llListFindList(propList, [llKey2Name(id), NULL_KEY]); if (idx >=0) { propList = [] + llListReplaceList(propList, [id], idx+1, idx+1); key u = user; if (llList2Integer(propList, idx+2) >=0) u = llList2Key(animAvis, llList2Integer(propList, idx+2) ); if ( llKey2Name(u) != "") osMessageObject(id , "SFUSER|"+(string)u); // tell prop to attach to user } else if (mode == "Editing") { handleIds += [id]; rezHandles(); return; } } sensor( integer t) { foundList =[]; integer i; for (i=0; i < t && llGetListLength(foundList) < 10; i++) if ( llListFindList(rlvList, llDetectedKey(i)) <0 && osIsNpc(llDetectedKey(i)) == FALSE) foundList += llDetectedKey(i); mode = "RlvCapture"; showDlg(); } no_sensor() { say("Nobody found."); } dataserver(key id, string m) { if (llGetOwnerKey(id) == llGetOwner() && llSubStringIndex(m, "HANDLE_TOUCH") ==0 ) {// positioning handler list tk = llParseStringKeepNulls(m, ["|"], []); integer idx = llListFindList(handleIds, llList2String(tk, 1)); if (idx <0) return; curHandle = idx; loadInvAnims(); mode = "InvAnims"; offset=0; invAnimsDlg(); } else if (llGetOwnerKey(id) == llGetOwner() && llSubStringIndex(m, "DO_UPDATE") ==0 ) {// autoupdate integer nn = (integer) ( 1 + llFrand(100000.)); llSetRemoteScriptAccessPin(nn); osMessageObject(id, "UPDATEPIN|"+(string)llGetKey()+"|"+(string)nn); llOwnerSay("Removing myself for update..."); doCleanup(); llRemoveInventory(llGetScriptName()); } else if (allowRemote >=2 && allowRemote <= 4) { if (user != llGetOwnerKey(id)) switchUser(llGetOwnerKey(id)); if (m == "GETSTATUS") osMessageObject(id, getStatusString()); else if (m != "") runCommand(m, 3); } } link_message(integer l, integer f, string m, key k) { if (f != -1) return; if ( llSubStringIndex(m, "MAIN_")==0) // compatibilty with PMAC. { list tk = llParseStringKeepNulls(m, ["|"], []); if (llList2String(tk, 0) == "MAIN_REGISTER_MENU_BUTTON") m = "Button="+llList2String(tk, 1)+"="+llList2String(tk, 2); if (llList2String(tk, 0) == "MAIN_UNREGISTER_MENU_BUTTON") m = "DELBUTTON{"+llList2String(tk, 1)+"}"; } if (m == "GETSTATUS") llMessageLinked(l, 0, getStatusString(), ""); else if (m != "") runCommand(m, 2); } }