From 2979af1f56d650ab456a4947d6ec718b2682efa4 Mon Sep 17 00:00:00 2001 From: NikkiLacrima <111503035+NikkiLacrima@users.noreply.github.com> Date: Sat, 5 Jul 2025 23:46:15 +0200 Subject: [PATCH 1/4] Testing for improved memory handling in oc_cuff NL 250725 --- oc_cuff.lsl | 42 ++++++++++-------------------------------- 1 file changed, 10 insertions(+), 32 deletions(-) diff --git a/oc_cuff.lsl b/oc_cuff.lsl index bde52f3..870678b 100644 --- a/oc_cuff.lsl +++ b/oc_cuff.lsl @@ -24,18 +24,6 @@ https://github.com/OpenCollarTeam/OpenCollar Visual locking system fix by Safra Nitely (based on togglelock by Aria) Cuff locking levels system fix by Safra Nitely (using OC standard levles of locking) */ -list StrideOfList(list src, integer stride, integer start, integer end) { - list l = []; - integer ll = llGetListLength(src); - if(start < 0)start += ll; - if(end < 0)end += ll; - if(end < start) return llList2List(src, start, start); - while(start <= end) { - l += llList2List(src, start, start); - start += stride; - } - return l; -} integer API_CHANNEL = 0x60b97b5e; string NEW_VERSION; @@ -132,20 +120,11 @@ integer g_iFirstInit=TRUE; list g_lOptedLM = []; list g_lPoses = []; - -list g_lMenuIDs; -integer g_iMenuStride; - string UPMENU = "BACK"; Dialog(key kID, string sPrompt, list lChoices, list lUtilityButtons, integer iPage, integer iAuth, string sName) { - key kMenuID = llGenerateKey(); - llRegionSayTo(g_kCollar, API_CHANNEL, llList2Json(JSON_OBJECT, [ "pkt_type", "from_addon", "addon_name", g_sAddon, "iNum", DIALOG, "sMsg", (string)kID + "|" + sPrompt + "|" + (string)iPage + "|" + llDumpList2String(lChoices, "`") + "|" + llDumpList2String(lUtilityButtons, "`") + "|" + (string)iAuth, "kID", kMenuID ])); - - integer iIndex = llListFindList(g_lMenuIDs, [kID]); - if (~iIndex) g_lMenuIDs = llListReplaceList(g_lMenuIDs, [ kID, kMenuID, sName ], iIndex, iIndex + g_iMenuStride - 1); - else g_lMenuIDs += [kID, kMenuID, sName]; + llRegionSayTo(g_kCollar, API_CHANNEL, llList2Json(JSON_OBJECT, [ "pkt_type", "from_addon", "addon_name", g_sAddon, "iNum", DIALOG, "sMsg", (string)kID + "|" + sPrompt + "|" + (string)iPage + "|" + llDumpList2String(lChoices, "`") + "|" + llDumpList2String(lUtilityButtons, "`") + "|" + (string)iAuth, "kID", sName+"~"+llGetScriptName() ])); } string lvl_msg =" "; @@ -164,7 +143,11 @@ Menu(key kID, integer iAuth) { if (CMD_LEVEL == 503)cmd_msg = "WEARER"; if (CMD_LEVEL == 504)cmd_msg = "EVERYONE"; - string sPrompt = "\n[OpenCollar Cuffs]\nMemory: "+(string)llGetFreeMemory()+"b\nVersion: "+g_sVersion+"\n" +"Current command level =" +cmd_msg +"\nYour command level =" +lvl_msg; + llScriptProfiler( PROFILE_SCRIPT_MEMORY ); + string sPrompt = "\n[OpenCollar Cuffs]\n"+"\nVersion: "+g_sVersion+"\n" +"Current command level =" +cmd_msg +"\nYour command level =" +lvl_msg; + llScriptProfiler( PROFILE_NONE ); + sPrompt += "\nMemory: "+(string)llGetSPMaxMemory(); + sPrompt += "\nCuff Name: "+g_sAddon+"\n"; if(UPDATE_AVAILABLE)sPrompt+="* An update is available!\n"; @@ -714,15 +697,10 @@ default UserCommand(iNum, sStr, kID); } - else if (iNum == DIALOG_TIMEOUT) { - integer iMenuIndex = llListFindList(g_lMenuIDs, [kID]); - g_lMenuIDs = llDeleteSubList(g_lMenuIDs, iMenuIndex - 1, iMenuIndex + 3); //remove stride from g_lMenuIDs - } else if (iNum == DIALOG_RESPONSE) { - integer iMenuIndex = llListFindList(g_lMenuIDs, [kID]); - if (iMenuIndex != -1) { - string sMenu = llList2String(g_lMenuIDs, iMenuIndex + 1); - g_lMenuIDs = llDeleteSubList(g_lMenuIDs, iMenuIndex - 1, iMenuIndex - 2 + g_iMenuStride); + integer iPos = llSubStringIndex(kID, "~"+llGetScriptName()); + if(iPos>0){ + string sMenu = llGetSubString(kID, 0, iPos-1); list lMenuParams = llParseString2List(sStr, ["|"], []); key kAv = llList2Key(lMenuParams, 0); string sMsg = llList2String(lMenuParams, 1); @@ -768,7 +746,7 @@ default else if (sMsg == "DISCONNECT") { Link("offline", 0, "", llGetOwnerKey(g_kCollar)); iRespring=FALSE; - g_lMenuIDs = []; +// g_lMenuIDs = []; g_kCollar = NULL_KEY; } From 41bc2c5e211e2562f8aa5cd5294db987c5ca1d25 Mon Sep 17 00:00:00 2001 From: NikkiLacrima <111503035+NikkiLacrima@users.noreply.github.com> Date: Sun, 6 Jul 2025 12:58:21 +0200 Subject: [PATCH 2/4] Update oc_cuff_pose.lsl --- oc_cuff_pose.lsl | 1097 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 837 insertions(+), 260 deletions(-) diff --git a/oc_cuff_pose.lsl b/oc_cuff_pose.lsl index fbf5a0c..bd1dee1 100644 --- a/oc_cuff_pose.lsl +++ b/oc_cuff_pose.lsl @@ -1,16 +1,227 @@ - /* This file is a part of OpenCollar. Copyright ©2021 : Contributors : Aria (Tashia Redrose) - * February 2021 - Created oc_cuff_pose -Paragon (ParagonFanfare) - * August 2023 - Fixed a bug where chains were stopped when standing from furniture + * February 2021 - Created oc_cuff +Safra (Safra Nitely) + * June 2021 - add priority for animations, fix visual lock/unlock +Lilith (Lilith Xue) + * August 2021 - Add ping for Lockmiester furniture compatibility +Ping (Pingout Duffield) + * February 2022 - Add code to allow RLV Restrictions to clear between poses + * April 2022 - Remove SyncLock, Clean Up Script Style +Kristen Mynx + * May 2022 - Added resizer support +Kristen Mynx + * July 2022 - Fix "BACK" buttons on resizer +Ping (Pingout Duffield) + * Nov 2022 - Add in link message for cuff [New Theme] button using iNum == 32 + * May 2023 - Minor change: new chain texture UUID, adjustment to particle scale +Nikki Lacrima + * July 2025 - Reduce memory use and duplicate pose changes et al. Licensed under the GPLv2. See LICENSE for full details. https://github.com/OpenCollarTeam/OpenCollar +Visual locking system fix by Safra Nitely (based on togglelock by Aria) +Cuff locking levels system fix by Safra Nitely (using OC standard levles of locking) */ + +integer API_CHANNEL = 0x60b97b5e; +string NEW_VERSION; +integer UPDATE_AVAILABLE; +integer g_iAmNewer; + +Compare(string V1, string V2){ + NEW_VERSION=V2; + + if(V1==V2){ + UPDATE_AVAILABLE=FALSE; + return; + } + V1 = llDumpList2String(llParseString2List(V1, ["."],[]),""); + V2 = llDumpList2String(llParseString2List(V2, ["."],[]), ""); + integer iV1 = (integer)V1; + integer iV2 = (integer)V2; + + if(iV1 < iV2){ + UPDATE_AVAILABLE=TRUE; + g_iAmNewer=FALSE; + } else if(iV1 == iV2) return; + else if(iV1 > iV2){ + UPDATE_AVAILABLE=FALSE; + g_iAmNewer=TRUE; + + llSetText("", <1,0,0>,1); + } +} + + +integer bool(integer a){ + if(a)return TRUE; + else return FALSE; +} +list g_lCheckboxes=["□","▣"]; +string Checkbox(integer iValue, string sLabel) { + return llList2String(g_lCheckboxes, bool(iValue))+" "+sLabel; +} +//list g_lCollars; +string g_sAddon = "OpenCollar Cuffs"; + +string g_sVersion = "2.2.2"; + +//integer CMD_ZERO = 0; +integer CMD_OWNER = 500; +integer CMD_TRUSTED = 501; +integer CMD_GROUP = 502; +integer CMD_WEARER = 503; +integer CMD_EVERYONE = 504; +integer CMD_LEVEL = 504; +//integer CMD_BLOCKED = 598; // <--- Used in auth_request, will not return on a CMD_ZERO +//integer CMD_RLV_RELAY = 507; +//integer CMD_SAFEWORD = 510; +//integer CMD_RELAY_SAFEWORD = 511; +//integer CMD_NOACCESS = 599; + +integer LM_SETTING_SAVE = 2000; //scripts send messages on this channel to have settings saved, must be in form of "token=value" +integer LM_SETTING_REQUEST = 2001; //when startup, scripts send requests for settings on this channel +integer LM_SETTING_RESPONSE = 2002; //the settings script sends responses on this channel +integer LM_SETTING_DELETE = 2003; //delete token from settings +//integer LM_SETTING_EMPTY = 2004; //sent when a token has no value + +integer DIALOG = -9000; +integer DIALOG_RESPONSE = -9001; +integer DIALOG_TIMEOUT = -9002; + +integer NOTIFY=-1002; + +integer LINK_NEWTHEME=32; + +integer SUMMON_PARTICLES = -58931; // Used only for cuffs to summon particles from one NAMED leash point to another NAMED anchor point +// SUMMON_PARTICLES should follow this message format: ||| +integer QUERY_POINT_KEY = -58932; +// This query is automatically triggered and the REPLY signal immediately spawns in particles via the SetParticles function +// Replies to this query are posted on the REPLY_POINT_KEY +// Message format for QUERY is: | kID identifier +integer REPLY_POINT_KEY = -58933; +// Reply format: |kID +integer CLEAR_ALL_CHAINS = -58934; +integer STOP_CUFF_POSE = -58935; // <-- stops all active animations originating from this cuff +integer DESUMMON_PARTICLES = -58936; // Message only includes the From point name +integer CLEAR_POSE_RESTRICTION = -58937; // Clear Restrictions between poses + +integer g_iFirstInit=TRUE; + +/* + * Since Release Candidate 1, Addons will not receive all link messages without prior opt-in. + * To opt in, add the needed link messages to g_lOptedLM = [], they'll be transmitted on + * the initial registration and can be updated at any time by sending a packet of type `update` + * Following LMs require opt-in: + * [ALIVE, READY, STARTUP, CMD_ZERO, MENUNAME_REQUEST, MENUNAME_RESPONSE, MENUNAME_REMOVE, SAY, NOTIFY, DIALOG, SENSORDIALOG] + */ +list g_lOptedLM = []; +list g_lPoses = []; + +string UPMENU = "BACK"; + +Dialog(key kID, string sPrompt, list lChoices, list lUtilityButtons, integer iPage, integer iAuth, string sName) { + + llRegionSayTo(g_kCollar, API_CHANNEL, llList2Json(JSON_OBJECT, [ "pkt_type", "from_addon", "addon_name", g_sAddon, "iNum", DIALOG, "sMsg", (string)kID + "|" + sPrompt + "|" + (string)iPage + "|" + llDumpList2String(lChoices, "`") + "|" + llDumpList2String(lUtilityButtons, "`") + "|" + (string)iAuth, "kID", sName+"~"+llGetScriptName() ])); +} + +string lvl_msg =" "; +string cmd_msg =" "; + +Menu(key kID, integer iAuth) { + + if (iAuth == 500)lvl_msg = "OWNER ONLY"; + if (iAuth == 501)lvl_msg = "TRUSTED & OWNER"; + if (iAuth == 502)lvl_msg = "GROUP ACCESS"; + if (iAuth == 503)lvl_msg = "WEARER, GROUP & OWNERS"; + if (iAuth == 504)lvl_msg = "EVERYONE"; + if (CMD_LEVEL == 500)cmd_msg = "OWNER"; + if (CMD_LEVEL == 501)cmd_msg = "TRUSTED"; + if (CMD_LEVEL == 502)cmd_msg = "GROUP ACCESS"; + if (CMD_LEVEL == 503)cmd_msg = "WEARER"; + if (CMD_LEVEL == 504)cmd_msg = "EVERYONE"; + + llScriptProfiler( PROFILE_SCRIPT_MEMORY ); + string sPrompt = "\n[OpenCollar Cuffs]\n"+"\nVersion: "+g_sVersion+"\n" +"Current command level =" +cmd_msg +"\nYour command level =" +lvl_msg; + llScriptProfiler( PROFILE_NONE ); + sPrompt += "\nMemory: "+(string)llGetSPMaxMemory(); + + sPrompt += "\nCuff Name: "+g_sAddon+"\n"; + + if(UPDATE_AVAILABLE)sPrompt+="* An update is available!\n"; + // if(g_iAmNewer)sPrompt+="** You are using a pre-release version. Some bugs may be encountered!"; + list lButtons = [];//"TEST CHAINS"]; + + if(g_iHasPoses && llGetInventoryType("oc_cuff_pose")==INVENTORY_SCRIPT){ + if(!g_iHidden) + lButtons+=["Pose"]; + else sPrompt +="\nPoses not available while the Collar is hidden"; + if(llGetInventoryType("oc_cuff_themes")==INVENTORY_SCRIPT + && (llGetObjectPermMask(MASK_OWNER) & (PERM_COPY | PERM_MODIFY | PERM_TRANSFER)) == (PERM_COPY | PERM_MODIFY | PERM_TRANSFER)){ + if(!g_iHidden) + lButtons+=["New Theme"]; + } + } + if(llGetInventoryType("oc_cuff_resizer")==INVENTORY_SCRIPT){ + if(!g_iHidden) + lButtons+=["Cuff Resize"]; + } + if(iAuth == CMD_OWNER) { + lButtons+=["ClearChains"]; + } + + lButtons += [Checkbox(g_iCuffLocked, "Lock")]; + + + //llSay(0, "opening menu"); + Dialog(kID, sPrompt, lButtons, ["DISCONNECT", UPMENU], 0, iAuth, "Menu~Main"); +} + + +UserCommand(integer iNum, string sStr, key kID) { + if (iNumCMD_WEARER) return; + if (llSubStringIndex(llToLower(sStr), llToLower(g_sAddon)) && llToLower(sStr) != "menu " + llToLower(g_sAddon)) return; + if (iNum == CMD_OWNER && llToLower(sStr) == "runaway") { + return; + } + + if (llToLower(sStr) == llToLower(g_sAddon) || llToLower(sStr) == "menu "+llToLower(g_sAddon)) { + Menu(kID, iNum); + } //else if (iNum!=CMD_OWNER && iNum!=CMD_TRUSTED && kID!=g_kWearer) RelayNotify(kID,"Access denied!",0); + else { + //integer iWSuccess = 0; + //string sChangetype = llList2String(llParseString2List(sStr, [" "], []),0); + //string sChangevalue = llList2String(llParseString2List(sStr, [" "], []),1); + //string sText; + } +} + +Link(string packet, integer iNum, string sStr, key kID){ + list packet_data = [ "pkt_type", packet, "iNum", iNum, "addon_name", g_sAddon, "bridge", FALSE, "sMsg", sStr, "kID", kID ]; + + if(!g_iHasPoses)packet_data += ["noMenu", 1]; + if (packet == "online" || packet == "update") { // only add optin if packet type is online or update + packet_data+= [ "optin", llDumpList2String(g_lOptedLM, "~") ]; + } + + string pkt = llList2Json(JSON_OBJECT, packet_data); + if (g_kCollar != "" && g_kCollar != NULL_KEY) { + llRegionSayTo(g_kCollar, API_CHANNEL, pkt); + } + else { + llRegionSay(API_CHANNEL, pkt); + } +} + +key g_kCollar=NULL_KEY; +integer g_iLMLastRecv; +integer g_iLMLastSent; + + list g_lDSRequests; key NULL=NULL_KEY; UpdateDSRequest(key orig, key new, string meta){ @@ -34,12 +245,6 @@ string GetDSMeta(key id){ } } -/// -/// FROM SL WIKI http://wiki.secondlife.com/wiki/Combined_Library#Replace -/// -string str_replace(string str, string search, string replace) { - return llDumpList2String(llParseStringKeepNulls((str = "") + str, [search], []), replace); -} integer HasDSRequest(key ID){ return llListFindList(g_lDSRequests, [ID]); } @@ -50,85 +255,88 @@ DeleteDSReq(key ID){ else return; } -list StrideOfList(list src, integer stride, integer start, integer end) -{ - list l = []; - integer ll = llGetListLength(src); - if(start < 0)start += ll; - if(end < 0)end += ll; - if(end < start) return llList2List(src, start, start); - while(start <= end) - { - l += llList2List(src, start, start); - start += stride; - } - return l; +/// +/// FROM SL WIKI http://wiki.secondlife.com/wiki/Combined_Library#Replace +/// +string str_replace(string str, string search, string replace) { + return llDumpList2String(llParseStringKeepNulls((str = "") + str, [search], []), replace); } -list g_lPoseMap = []; - -string g_sPendingPose; -string g_sPendingAnim; -string g_sPendingChains; -string g_sPendingRLV; -string g_sPendingAge; -string g_sPendingGravity; +integer g_iHasPoses=TRUE; -integer TIMEOUT_REGISTER = 30498; +ClearAllParticles(){ + integer i=0; + integer end = llGetNumberOfPrims(); + for(i=LINK_ROOT;i<=end;i++){ + llLinkParticleSystem(i,[]); + } +} -string g_sPendingAnims; -string g_sPendingCollarChains; -string g_sPoseName= ""; -string g_sActivePose=""; -string currentAnim=""; +SetParticles(integer link, key kID,key kTexture, float fMaxAge, float fGravity){ + + if(kTexture=="" || kTexture=="def")kTexture="1f5df35f-7859-897a-9c40-c787ba944393"; + if(fMaxAge==0)fMaxAge=7.3; + if(llRound(fGravity) == -1) fGravity = -0.01; + llLinkParticleSystem(link, [ +PSYS_SRC_PATTERN,PSYS_SRC_PATTERN_DROP, +PSYS_PART_START_ALPHA,1, +PSYS_PART_START_SCALE,<0.07, 0.07, 0>, +PSYS_PART_END_SCALE,<0.07,0.07,0>, +PSYS_PART_MAX_AGE,fMaxAge, +PSYS_SRC_BURST_PART_COUNT,1, +PSYS_SRC_ACCEL,<0, 0, -0.01>, +PSYS_SRC_TEXTURE,kTexture, +PSYS_SRC_TARGET_KEY,kID, +PSYS_PART_FLAGS,PSYS_PART_FOLLOW_SRC_MASK| +PSYS_PART_FOLLOW_VELOCITY_MASK| +PSYS_PART_INTERP_SCALE_MASK| +PSYS_PART_TARGET_POS_MASK + ]); +} -list g_lCollarMap = []; -integer SUMMON_PARTICLES = -58931; // Used only for cuffs to summon particles from one NAMED leash point to another NAMED anchor point -// SUMMON_PARTICLES should follow this message format: ||| -integer QUERY_POINT_KEY = -58932; -// This query is automatically triggered and the REPLY signal immediately spawns in particles via the SetParticles function -// Replies to this query are posted on the REPLY_POINT_KEY -// Message format for QUERY is: | kID identifier -integer REPLY_POINT_KEY = -58933; -// Reply format: |kID -integer CLEAR_ALL_CHAINS = -58934; -integer STOP_CUFF_POSE = -58935; // <-- stops all active animations originating from this cuff -integer DESUMMON_PARTICLES = -58936; // Message only includes the From point name +list g_lMyPoints = []; +string g_sActivePose; +list GetKey(string LinkName) { + integer i=0; + integer end = llGetNumberOfPrims(); + for(i=LINK_ROOT;i<=end;i++){ + if(llGetLinkName(i)==LinkName){ + return [i, llGetLinkKey(i)]; + } + } -integer LM_SETTING_SAVE = 2000; //scripts send messages on this channel to have settings saved, must be in form of "token=value" -integer LM_SETTING_REQUEST = 2001; //when startup, scripts send requests for settings on this channel -integer LM_SETTING_RESPONSE = 2002; //the settings script sends responses on this channel -integer LM_SETTING_DELETE = 2003; //delete token from settings -//integer LM_SETTING_EMPTY = 2004; //sent when a token has no value + return [LINK_ROOT, llGetLinkKey(LINK_ROOT)]; +} +integer g_iLMV2Listen=-1; -Link(string sPkt, integer iNum, string sMsg, key kID) -{ - llMessageLinked(LINK_SET, 999, llList2Json(JSON_OBJECT, ["pkt", sPkt, "iNum", iNum, "sMsg", sMsg, "kID", kID]), ""); -} -StartCuffPose(list lParams, integer iSave) -{ - if(iSave)Link("from_addon", LM_SETTING_SAVE, "occuffs_"+g_sPoseName+"pose="+llList2String(lParams,0), ""); +list g_lLMV2Map; +list g_lLGv2Map; - //llSay(0, ".\nENTER StartCuffPose(list[], int)\n{\n\targ0 = "+llDumpList2String(lParams," ~ ")+"\n\targ1 = "+(string)iSave+"\n}\n\nAnimation = "+llList2String(lParams,1)); - currentAnim=(llList2String(lParams,1)); - llStartAnimation(currentAnim); - llSetTimerEvent(2.5); +integer g_iLGV2Listen; +integer g_iTmpHide=0; - // param 2 = chain options - list opts = llParseString2List(llList2String(lParams,2), ["~"],[]); - Summon(opts, llList2String(lParams,4), llList2String(lParams,3)); +integer TIMEOUT_REGISTER = 30498; +integer TIMEOUT_FIRED = 30499; + +integer g_iCuffLocked=FALSE; +integer g_iLocked; +string g_sCurrentPose="NONE"; + +PosesMenu (key kAv, integer iAuth, integer iPage) { + if (iAuth <= CMD_LEVEL) { + string sPrompt = "\n[OpenCollar Cuffs]\n> Poses selection\n\n* Current Pose: "; + sPrompt += g_sCurrentPose; + list lButtons = g_lPoses; + Dialog(kAv, sPrompt, lButtons, ["*STOP*", "BACK"], iPage, iAuth, "Cuffs~Poses"); + } + else { + string sPrompt = "\n[OpenCollar Cuffs]\n> Poses selection\n\n* Only your owner can access this menu! "; + } } -EndCuffPose(string anm) { - llSetTimerEvent(FALSE); - g_sCurrentPose="NONE"; - currentAnim=""; - llStopAnimation(anm); -} -Desummon(list lPoints) -{ +Desummon(list lPoints) { integer ix=0; integer end = llGetListLength(lPoints); for(ix=0;ix