Shortnote

This is a RLV-based outfit locker that keeps clothes and attachments into place so that they cannot be detached for example, by switching to a different outfit or as a consequence of a trap command.

Number of Slots

The decision to have only 10 toggle slots is calculated based on the string lengths of the outfit and attachment slots. This is done by sorting the slots by string length in decreasing order and then counting characters from the top until we reach 127-7 - that is the maximal number of characters the description can hold minus the lock=-1 string from LID™.

Branching on Execution Time

 Oh no, not this shit again!

The decision for Turing machines to rule out "time" as a component in favor of sequential execution is based on the assumption that code is meant to execute as fast as possible. However, in Second Life, where a large series of events can occur that include the human component (just like in real life), "time" becomes an significant factor.

The following code uses "time" in order to make a branching decision based on how much time it takes for the script to go through the read state, compared to the time it takes for the user to type the /1ock command on local chat, and click the ⏏ Exit button. By measuring that amount of time, the script determines whether the script has just started (as in script restart, log-in, etc…) or whether the avatar has been using the script for a while and just pressed the ⏏ Exit button.

This is used to implement the (wanted) re-menu feature of LSL scripts. If the script has just started, then the script decides to not show the menu - obviously, since the avatar has just logged in, attached the object, etc… And does not want to be spammed. If however, the execution time is much longer, then the user has most likely pressed the ⏏ Exit button and the script re-menus by showing the previous menu.

This same type of measurement is applied when touching the ⎚ Clear button.

Code

outfitlocker.lsl
///////////////////////////////////////////////////////////////////////////
//  Copyright (C) Wizardry and Steamworks 2013 - License: GNU GPLv3      //
//  Please see: http://www.gnu.org/licenses/gpl.html for legal details,  //
//  rights of fair usage, the disclaimer and warranty conditions.        //
///////////////////////////////////////////////////////////////////////////
 
list WORN_SLOTS = [];
list ATTACHED_SLOTS = [];
list C = [];
list A = [];
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
integer wasMenuIndex = 0;
list wasDialogMenu(list input, list actions, string direction) {
    integer cut = 11-wasListCountExclude(actions, [""]);
    if(direction == ">" &&  (wasMenuIndex+1)*cut+wasMenuIndex+1 < llGetListLength(input)) {
        ++wasMenuIndex;
        jump slice;
    }
    if(direction == "<" && wasMenuIndex-1 >= 0) {
        --wasMenuIndex;
        jump slice;
    }
@slice;
    integer multiple = wasMenuIndex*cut;
    input = llList2List(input, multiple+wasMenuIndex, multiple+cut+wasMenuIndex);
    input = wasListMerge(input, actions, "");
    return input;
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
integer wasListCountExclude(list input, list exclude) {
    if(llGetListLength(input) == 0) return 0;
    if(llListFindList(exclude, (list)llList2String(input, 0)) == -1) 
        return 1 + wasListCountExclude(llDeleteSubList(input, 0, 0), exclude);
    return wasListCountExclude(llDeleteSubList(input, 0, 0), exclude);
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
list wasListMerge(list l, list m, string merge) {
    if(llGetListLength(l) == 0 && llGetListLength(m) == 0) return [];
    string a = llList2String(m, 0);
    if(a != merge) return [ a ] + wasListMerge(l, llDeleteSubList(m, 0, 0), merge);
    return [ llList2String(l, 0) ] + wasListMerge(llDeleteSubList(l, 0, 0), llDeleteSubList(m, 0, 0), merge);
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
string wasKeyValueGet(string var, string kvp) {
    list dVars = llParseString2List(kvp, ["&"], []);
    do {
        list data = llParseString2List(llList2String(dVars, 0), ["="], []);
        string k = llList2String(data, 0);
        if(k != var) jump continue;
        return llList2String(data, 1);
@continue;
        dVars = llDeleteSubList(dVars, 0, 0);
    } while(llGetListLength(dVars));
    return "";
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
string wasKeyValueSet(string var, string val, string kvp) {
    list dVars = llParseString2List(kvp, ["&"], []);
    if(llGetListLength(dVars) == 0) return var + "=" + val;
    list result = [];
    do {
        list data = llParseString2List(llList2String(dVars, 0), ["="], []);
        string k = llList2String(data, 0);
        if(k == "") jump continue;
        if(k == var && val == "") jump continue;
        if(k == var) {
            result += k + "=" + val;
            val = "";
            jump continue;
        }
        string v = llList2String(data, 1);
        if(v == "") jump continue;
        result += k + "=" + v;
@continue;
        dVars = llDeleteSubList(dVars, 0, 0);
    } while(llGetListLength(dVars));
    if(val != "") result += var + "=" + val;
    return llDumpList2String(result, "&");
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
string wasKeyValueDelete(string var, string kvp) {
    list dVars = llParseString2List(kvp, ["&"], []);
    list result = [];
    list added = [];
    do {
        list data = llParseString2List(llList2String(dVars, 0), ["="], []);
        string k = llList2String(data, 0);
        if(k == var) jump continue;
        string v = llList2String(data, 1);
        if(v == "") jump continue;
        if(llListFindList(added, (list)k) != -1) jump continue;
        result += k + "=" + v;
        added += k;
@continue;
        dVars = llDeleteSubList(dVars, 0 ,0);
    } while(llGetListLength(dVars));
    return llDumpList2String(result, "&");
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
LID(key id) {
    if(id != NULL_KEY && llGetAttached() != 0) {
        llSetLinkPrimitiveParamsFast(2, [PRIM_DESC, wasKeyValueSet("login", (string)((integer)wasKeyValueGet("login", llList2String(llGetLinkPrimitiveParams(2, [PRIM_DESC]), 0))+1), llList2String(llGetLinkPrimitiveParams(2, [PRIM_DESC]), 0))]);
        llResetScript();
    }
    llSetLinkPrimitiveParamsFast(2, [PRIM_DESC, wasKeyValueSet("login", "-1", llList2String(llGetLinkPrimitiveParams(2, [PRIM_DESC]), 0))]);
}
 
default {
    state_entry() {
        // LID™ - http://grimore.org/fuss:lsl#log-in_detection_with_attachments
        // if LID™, then login = 1
        // else login = 0 - script running | parameter undefined
        if((integer)wasKeyValueGet("login", llList2String(llGetLinkPrimitiveParams(2, [PRIM_DESC]), 0)) > 0) {
            llSetLinkPrimitiveParamsFast(2, [PRIM_DESC, wasKeyValueSet("login", "0", llList2String(llGetLinkPrimitiveParams(2, [PRIM_DESC]), 0))]);
            llSetTimerEvent(25);
            return;
        }
        llSetLinkPrimitiveParamsFast(2, [PRIM_DESC, wasKeyValueSet("login", "0", llList2String(llGetLinkPrimitiveParams(2, [PRIM_DESC]), 0))]);
        state check;
    }
    timer() { state check; }
 
    changed(integer change) { if(change & CHANGED_INVENTORY || change & CHANGED_REGION || change & CHANGED_OWNER) llResetScript(); }
 
    // LID™ - http://grimore.org/fuss:lsl#log-in_detection_with_attachments
    attach(key id) { LID(id); }
 
}
 
state check {
    state_entry() {
        llSetTimerEvent(5);
        integer channel = 10+(integer)llFrand(10);
        llListen(channel, "", llGetOwner(), "");
        llOwnerSay("@version=" + (string)channel);
    }
    timer() {
        llSetTimerEvent(0);
        llOwnerSay("Your viewer is not RLV-enabled. This gizmo requires a RLV-enabled viewer. Cannot proceed.");
    }
    listen(integer channel, string name, key owner, string message) {
        llSetTimerEvent(0);
        // mark -> t=0 
        llGetAndResetTime();
        state read;
    }
 
    changed(integer change) { if(change & CHANGED_INVENTORY || change & CHANGED_REGION || change & CHANGED_OWNER) llResetScript(); }
 
    // LID™ - http://grimore.org/fuss:lsl#log-in_detection_with_attachments
    attach(key id) {
        LID(id);
    }
}
 
state read {
    state_entry() {
        WORN_SLOTS = ["◎ gloves", "◎ jacket", "◎ pants", "◎ shirt", "◎ shoes", "◎ skirt", "◎ socks", "◎ underpants", "◎ undershirt", "◎ skin", "◎ eyes", "◎ hair", "◎ shape", "◎ alpha", "◎ tattoo"];
        ATTACHED_SLOTS = [ "◎ none", "◎ chest", "◎ skull", "◎ left shoulder", "◎ right shoulder", "◎ left hand", "◎ right hand", "◎ left foot", "◎ right foot", "◎ spine", "◎ pelvis", "◎ mouth", "◎ chin", "◎ left ear", "◎ right ear", "◎ left eyeball", "◎ right eyeball", "◎ nose", "◎ r upper arm", "◎ r forearm", "◎ l upper arm", "◎ l forearm", "◎ right hip", "◎ r upper leg", "◎ r lower leg", "◎ left hip", "◎ l upper leg", "◎ l lower leg", "◎ stomach", "◎ left pec", "◎ right pec", "◎ center 2", "◎ top right", "◎ top", "◎ top left", "◎ center", "◎ bottom left", "◎ bottom", "◎ bottom right" ];
        llListen(2, "", llGetOwner(), "");
        llListen(3, "", llGetOwner(), "");
        llOwnerSay("@getoutfit=2");
        llOwnerSay("@getattach=3");
        llSetTimerEvent(1);
    }
    listen(integer channel, string name, key owner, string message) {
        if(channel == 2) {
            C = llParseString2List(message, [""], ["0", "1"]);
            jump alarm;
        }
        if(channel == 3) {
            A = llParseString2List(message, [""], ["0", "1"]);
            jump alarm;
        }
@alarm;
        llSetTimerEvent(1);
    }
    timer() {
        if(llGetListLength(C) == 0 || llGetListLength(A) == 0) return;
        list mem = llParseString2List(
                wasKeyValueGet("mem", llList2String(llGetLinkPrimitiveParams(2, [PRIM_DESC]), 0))
                , [","], []);
        integer i = llGetListLength(C)-1;
        do {
            string slot = llDeleteSubString(llList2String(WORN_SLOTS, i), 0, 1);
            if(llGetListLength(mem) == 0) jump skip_mem_worn;
            integer idx = llListFindList(mem, (list)slot);
            if(idx != -1) {
                mem = llDeleteSubList(mem, idx, idx);
                WORN_SLOTS = llListReplaceList(WORN_SLOTS, (list)("◉ " + slot), i, i);
                llOwnerSay("@remoutfit:" + slot + "=n");
                jump nextC;
            }
@skip_mem_worn;
            if(llList2Integer(C, i) != 0) {
                llOwnerSay("@remoutfit:" + slot + "=y");
                jump nextC;
            }
            WORN_SLOTS = llDeleteSubList(WORN_SLOTS, i, i);
@nextC;
        } while(--i>-1);
        C = [];
        i = llGetListLength(A)-1;
        do {
            string slot = llDeleteSubString(llList2String(ATTACHED_SLOTS, i), 0, 1);
            if(llGetListLength(mem) == 0) jump skip_mem_attached;
            integer idx = llListFindList(mem, (list)slot);
            if(idx != -1) {
                mem = llDeleteSubList(mem, idx, idx);
                ATTACHED_SLOTS = llListReplaceList(ATTACHED_SLOTS, (list)("◉ " + slot), i, i);
                llOwnerSay("@remattach:" + slot + "=n");
                jump nextA;
            }
@skip_mem_attached;
            if(llList2Integer(A, i) != 0) {
                llOwnerSay("@remattach:" + slot + "=y");
                jump nextA;
            }
            ATTACHED_SLOTS = llDeleteSubList(ATTACHED_SLOTS, i, i);
@nextA;
        } while(--i>-1);
        A = [];
        llSetTimerEvent(0);
        // alarm -> t = t + [3..4]
        state main;
    }
 
    changed(integer change) { if(change & CHANGED_INVENTORY || change & CHANGED_REGION || change & CHANGED_OWNER) llResetScript(); }
 
    // LID™ - http://grimore.org/fuss:lsl#log-in_detection_with_attachments
    attach(key id) {
        LID(id);
    }
}
 
state main {
    state_entry() {
        llListen(1, "", llGetOwner(), "");
        // if t < 3 then script reset, so wait for /1ock
        // if t >> 3 then returning from lock_worn or lock_attached, so re-menu
        if(llGetAndResetTime() < 4) return;
        llDialog(llGetOwner(), "\n            Welcome to the Outfit Locker.\nCreated in 2013 by Wizardry and Steamworks\n                 10 August 2013: Version: 1.0\n", ["▦ Worn", "▣ Attached", "⎚ Clear"], 1);
    }
    listen(integer channel, string name, key owner, string message) {
        if(message == "ock") { 
            llDialog(owner, "\n            Welcome to the Outfit Locker.\nCreated in 2013 by Wizardry and Steamworks\n                 10 August 2013: Version: 1.0\n", ["▦ Worn", "▣ Attached", "⎚ Clear"], channel);
            return;
        }
        if(message == "⎚ Clear") state clear;
        if(message == "▦ Worn") state lock_worn;
        if(message == "▣ Attached") state lock_attached;
    }
 
    changed(integer change) { if(change & CHANGED_INVENTORY || change & CHANGED_REGION || change & CHANGED_OWNER) llResetScript(); }
 
    // LID™ - http://grimore.org/fuss:lsl#log-in_detection_with_attachments
    attach(key id) {
        LID(id);
    }
}
 
state clear {
    state_entry() {
        llSetTimerEvent(3);
    }
    timer() {
        list mem = llParseString2List(wasKeyValueGet("mem", llList2String(llGetLinkPrimitiveParams(2, [PRIM_DESC]), 0)), [","], []);
        do {
            string slot = llList2String(mem, 0);
            llOwnerSay("@remoutfit:" + slot + "=y");
            llOwnerSay("@remattach:" + slot + "=y");
            mem = llDeleteSubList(mem, 0, 0);
        } while(llGetListLength(mem) != 0);
        llSetLinkPrimitiveParamsFast(2, [PRIM_DESC, wasKeyValueDelete("mem", llList2String(llGetLinkPrimitiveParams(2, [PRIM_DESC]), 0))]);
        state read;
    }
}
 
state lock_worn {
    state_entry() {
        llListen(1, "", llGetOwner(), "");
        llDialog(llGetOwner(), "\n            Welcome to the Outfit Locker.\nCreated in 2013 by Wizardry and Steamworks\n                 10 August 2013: Version: 1.0\n", wasDialogMenu(WORN_SLOTS, ["⟵ Back", "Next ⟶", "⏏ Exit"], ""), 1);
    }
    listen(integer channel, string name, key owner, string message) {
        if(message == "⏏ Exit" || message == "ock") state read;
        if(message == "⟵ Back") {
            llDialog(owner, "Please browse the available items:\n", wasDialogMenu(WORN_SLOTS, ["⟵ Back", "Next ⟶", "⏏ Exit"], "<"), channel);
            return;
        }
        if(message == "Next ⟶") {
            llDialog(owner, "Please browse the available items:\n", wasDialogMenu(WORN_SLOTS, ["⟵ Back", "Next ⟶", "⏏ Exit"], ">"), channel);
            return;
        }
        integer idx = llListFindList(WORN_SLOTS, (list)message);
        string status = llGetSubString(message, 0, 0);
        message = llDeleteSubString(message, 0, 1);
        list mem = llParseString2List(wasKeyValueGet("mem", llList2String(llGetLinkPrimitiveParams(2, [PRIM_DESC]), 0)), [","], []);
 
        // if all slots are occupied, bail out.
        if(status == "◎" && llGetListLength(mem) == 10) {
            llOwnerSay("Sorry, only 10 slots are supported. Please untick some items to lock this item.");
            jump remenu;
        }
 
        // lock
        if(status == "◎" && llListFindList(mem, (list)message) == -1) {
            WORN_SLOTS = llListReplaceList(WORN_SLOTS, (list)("◉ " + message), idx, idx);
            mem += message;
            llSetLinkPrimitiveParamsFast(2, [PRIM_DESC, wasKeyValueSet("mem", llDumpList2String(mem, ","), llList2String(llGetLinkPrimitiveParams(2, [PRIM_DESC]), 0))]);
            llOwnerSay("@remoutfit:" + message + "=n");
            jump remenu;
        }
        // unlock
        WORN_SLOTS = llListReplaceList(WORN_SLOTS, (list)("◎ " + message), idx, idx);
        idx = llListFindList(mem, (list)message);
        mem = llDeleteSubList(mem, idx, idx);
        llSetLinkPrimitiveParamsFast(2, [PRIM_DESC, wasKeyValueSet("mem", llDumpList2String(mem, ","), llList2String(llGetLinkPrimitiveParams(2, [PRIM_DESC]), 0))]);
        llOwnerSay("@remoutfit:" + message + "=y");
@remenu;
        llDialog(owner, "Please browse the available items:\n", wasDialogMenu(WORN_SLOTS, ["⟵ Back", "Next ⟶", "⏏ Exit"], ""), channel);
    }
 
    changed(integer change) { if(change & CHANGED_INVENTORY || change & CHANGED_REGION || change & CHANGED_OWNER) llResetScript(); }
 
    // LID™ - http://grimore.org/fuss:lsl#log-in_detection_with_attachments
    attach(key id) {
        LID(id);
    }
}
 
state lock_attached {
    state_entry() {
        llListen(1, "", llGetOwner(), "");
        llDialog(llGetOwner(), "\n            Welcome to the Outfit Locker.\nCreated in 2013 by Wizardry and Steamworks\n                 10 August 2013: Version: 1.0\n", wasDialogMenu(ATTACHED_SLOTS, ["⟵ Back", "Next ⟶", "⏏ Exit"], ""), 1);
    }
    listen(integer channel, string name, key owner, string message) {
        if(message == "⏏ Exit" || message == "ock") state read;
        if(message == "⟵ Back") {
            llDialog(owner, "Please browse the available items:\n", wasDialogMenu(ATTACHED_SLOTS, ["⟵ Back", "Next ⟶", "⏏ Exit"], "<"), channel);
            return;
        }
        if(message == "Next ⟶") {
            llDialog(owner, "Please browse the available items:\n", wasDialogMenu(ATTACHED_SLOTS, ["⟵ Back", "Next ⟶", "⏏ Exit"], ">"), channel);
            return;
        }
        integer idx = llListFindList(ATTACHED_SLOTS, (list)message);
        string status = llGetSubString(message, 0, 0);
        message = llDeleteSubString(message, 0, 1);
        list mem = llParseString2List(wasKeyValueGet("mem", llList2String(llGetLinkPrimitiveParams(2, [PRIM_DESC]), 0)), [","], []);
 
        // if all slots are occupied, bail out.
        if(status == "◎" && llGetListLength(mem) == 10) {
            llOwnerSay("Sorry, only 10 slots are supported. Please untick some items to lock this item.");
            jump remenu;
        }
 
        // lock
        if(status == "◎" && llListFindList(mem, (list)message) == -1) {
            ATTACHED_SLOTS = llListReplaceList(ATTACHED_SLOTS, (list)("◉ " + message), idx, idx);
            mem += message;
            llSetLinkPrimitiveParamsFast(2, [PRIM_DESC, wasKeyValueSet("mem", llDumpList2String(mem, ","), llList2String(llGetLinkPrimitiveParams(2, [PRIM_DESC]), 0))]);
            llOwnerSay("@remattach:" + message + "=n");
            jump remenu;
        }
        // unlock
        ATTACHED_SLOTS = llListReplaceList(ATTACHED_SLOTS, (list)("◎ " + message), idx, idx);
        idx = llListFindList(mem, (list)message);
        mem = llDeleteSubList(mem, idx, idx);
        llSetLinkPrimitiveParamsFast(2, [PRIM_DESC, wasKeyValueSet("mem", llDumpList2String(mem, ","), llList2String(llGetLinkPrimitiveParams(2, [PRIM_DESC]), 0))]);
        llOwnerSay("@remattach:" + message + "=y");
@remenu;
        llDialog(owner, "Please browse the available items:\n", wasDialogMenu(ATTACHED_SLOTS, ["⟵ Back", "Next ⟶", "⏏ Exit"], ""), channel);
    }
 
    changed(integer change) { if(change & CHANGED_INVENTORY || change & CHANGED_REGION || change & CHANGED_OWNER) llResetScript(); }
 
    // LID™ - http://grimore.org/fuss:lsl#log-in_detection_with_attachments
    attach(key id) {
        LID(id);
    }
}

secondlife/outfit_locker.txt · Last modified: 2023/09/27 09:54 by office

Access website using Tor Access website using i2p Wizardry and Steamworks PGP Key


For the contact, copyright, license, warranty and privacy terms for the usage of this website please see the contact, license, privacy, copyright.