This script was tested and works on OpenSim version 0.7.5!

poseball_system_menu.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.        //
///////////////////////////////////////////////////////////////////////////
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
string wasKeyValueGet(string k, string data) {
    if(llStringLength(data) == 0) return "";
    if(llStringLength(k) == 0) return "";
    list a = llParseStringKeepNulls(data, ["&", "="], []);
    integer i = llListFindList(llList2ListStrided(a, 0, -1, 2), [ k ]);
    if(i != -1) return llList2String(a, 2*i+1);
    return "";
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
string wasKeyValueSet(string k, string v, string data) {
    if(llStringLength(k) == 0) return "";
    if(llStringLength(v) == 0) return "";
    if(llStringLength(data) == 0) return k + "=" + v;
    integer i = llListFindList(
        llList2ListStrided(
            llParseString2List(data, ["&", "="], []), 
            0, -1, 2
        ), 
    [ k ]);
    if(i != -1) return llDumpList2String(
        llListReplaceList(
            llParseString2List(data, ["&"], []), 
            [ k + "=" + v ], 
        i, i), 
    "&");
    return data + "&" + k + "=" + v;
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
string wasKeyValueEncode(list data) {
    list k = llList2ListStrided(data, 0, -1, 2);
    list v = llList2ListStrided(llDeleteSubList(data, 0, 0), 0, -1, 2);
    data = [];
    do {
        data += llList2String(k, 0) + "=" + llList2String(v, 0);
        k = llDeleteSubList(k, 0, 0);
        v = llDeleteSubList(v, 0, 0);
    } while(llGetListLength(k) != 0);
    return llDumpList2String(data, "&");
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
list wasKeyValueDecode(string data) {
    return llParseString2List(data, ["&", "="], []);
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
list wasDialogMenu(list input, list actions, string direction) {
    integer cut = 11-wasListCountExclude(actions, [""]);
    integer wasMenuIndex = (integer)wasKeyValueGet("index", llGetObjectDesc());
    if(direction == ">" &&  (wasMenuIndex+1)*cut+wasMenuIndex+1 < llGetListLength(input)) {
        ++wasMenuIndex;
        llSetObjectDesc(wasKeyValueSet("index", (string)wasMenuIndex, llGetObjectDesc()));
        jump slice;
    }
    if(direction == "<" && wasMenuIndex-1 >= 0) {
        --wasMenuIndex;
        llSetObjectDesc(wasKeyValueSet("index", (string)wasMenuIndex, llGetObjectDesc()));
        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 wasProgress(integer percent, integer length, list symbols) {
    percent /= (integer)((float)100.0/(length));
    string p = llList2String(symbols,0);
    integer itra = 0;
    do {
        if(itra>percent-1) p += llList2String(symbols,2);
        else p += llList2String(symbols,1);
    } while(++itra<length);
    return p + llList2String(symbols,3);
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
vector wasPercentToGradient(float percent, string rgb) {
    if(llStringLength(rgb) != 2) {
        llSay(DEBUG_CHANNEL, "Assert failed, rgb parameter must consist of a pair of either r, g, or b.");
        return ZERO_VECTOR;
    }
    string a = llGetSubString(rgb, 0, 0);
    string b = llGetSubString(rgb, 1, 1);
    list col = [ "r", "g", "b" ];
    integer ax = llListFindList(col, (list)a);
    integer bx = llListFindList(col, (list)b);
    if(ax == -1 || bx == -1) {
        llSay(DEBUG_CHANNEL, "Asset failed, rgb parameters must contain either r, g, or b letters.");
        return ZERO_VECTOR;
    }
    col = llListReplaceList(col, (list)((100-percent)/100), ax, ax);
    col = llListReplaceList(col, (list)(percent/100), bx, bx);
    return 2*<llList2Float(col, 0), llList2Float(col, 1), llList2Float(col, 2)>;
}
 
// Reading variables.
integer nLine = 0;
integer lines = 0;
// Animation list
list animations = [];
// Menu list
list menuList = [];
// Toucher storage.
key mav = NULL_KEY;
// Current animation set.
string set = "";
 
default
{
    state_entry() {
        if((integer)wasKeyValueGet("session", llGetObjectDesc()) == 0) llSetObjectDesc(wasKeyValueSet("session", (string)((integer)llFrand(1879048193)), llGetObjectDesc()));
        menuList = ["◉ STOP", "⌠ Settings ⌡", "⌠ Items ⌡" ];
        string mode = wasKeyValueGet("mode", llGetObjectDesc());
        nLine = llGetInventoryNumber(INVENTORY_NOTECARD)-1;
        do {
            list m = wasKeyValueDecode(llGetInventoryName(INVENTORY_NOTECARD, nLine));
            string itemMaturity = llList2String(m, 1);
            string item = llList2String(m,0);
            if(itemMaturity == "pg") {
                menuList += "☀ " + item;
                jump continue;
            }
            if(itemMaturity == "mature" && (mode == "mature" || mode == "adult")) {
                menuList += "♥ " + item;
                jump continue;
            }
            if(itemMaturity == "adult" && mode == "adult") {
                menuList += "☯ " + item;
                jump continue;
            }
@continue;
        } while(--nLine>-1);
        if(mav == NULL_KEY) return;
        integer comChannel = (integer)("0x8" + llGetSubString(llGetOwner(), 0, 6)) + (integer)wasKeyValueGet("session", llGetObjectDesc());
        llListen(comChannel, "", mav, "");
        llDialog(mav, "\n           Welcome to the Lounge Armchair.\nCreated in 2014 by Wizardry and Steamworks\n                 26 May 2014: Version: 1.0", menuList, comChannel);
    }
    touch_start(integer num) {
        mav = llDetectedKey(0);
        integer comChannel = (integer)("0x8" + llGetSubString(llGetOwner(), 0, 6)) + (integer)wasKeyValueGet("session", llGetObjectDesc());
        llListen(comChannel, "", mav, "");
        llDialog(mav, "\n           Welcome to the Lounge Armchair.\nCreated in 2014 by Wizardry and Steamworks\n                 26 May 2014: Version: 1.0", menuList, comChannel);
    }
    listen(integer channel, string name, key id, string message) {
        if(message == "◉ STOP") {
            llWhisper(channel, "sex=f&die=f");
            llSetObjectDesc(wasKeyValueSet("f", "off", llGetObjectDesc()));
            llWhisper(channel, "sex=m&die=m");
            llSetObjectDesc(wasKeyValueSet("m", "off", llGetObjectDesc()));
            llResetScript();
        }
        if(message == "⌠ Settings ⌡") {
            if(id != llGetOwner()) {
                llInstantMessage(id, "Sorry, this option is restricted to the owner.");
                return;
            }
            menuList = [];
            state settings;
        }
        if(message == "⌠ Items ⌡") {
            menuList = [];
            state items;
        }
        list m = llParseString2List(message, [" "], []);
        if(llList2String(m, 0) == "☀") {
            set = wasKeyValueEncode([llList2String(m, 1), "pg"]);
            jump count;
        }
        if(llList2String(m, 0) == "♥") {
            set = wasKeyValueEncode([llList2String(m, 1), "mature"]);
            jump count;
        }
        if(llList2String(m, 0) == "☯") {
            set = wasKeyValueEncode([llList2String(m, 1), "adult"]);
            jump count;
        }
@count;
        menuList = [];
        state count;
    }
    on_rez(integer num) {
        llSetObjectDesc(wasKeyValueSet("session", "0", llGetObjectDesc()));
        llResetScript();
    }
}
 
state items {
    state_entry() {
        menuList = [ "⏏ Exit" ];
        integer i = llGetInventoryNumber(INVENTORY_OBJECT)-1;
        do {
            string name = llGetInventoryName(INVENTORY_OBJECT, i);
            if(name == "m" || name == "f") jump continue;
            menuList += name;
@continue;
        } while(--i>-1);
        if(mav == NULL_KEY) return;
        integer comChannel = (integer)("0x8" + llGetSubString(llGetOwner(), 0, 6)) + (integer)wasKeyValueGet("session", llGetObjectDesc());
        llListen(comChannel, "", mav, "");
        llDialog(mav, "Please select an item from the list below or click the exit button to return to the previous menu.", menuList, comChannel);
    }
    listen(integer channel, string name, key id, string message) {
        if(message == "⏏ Exit") {
            menuList = [];
            state default;
        }
        llGiveInventory(id, message);
        llDialog(id, "Please select an item from the list below or click the exit button to return to the previous menu.", menuList, channel);
    }
    on_rez(integer num) {
        llSetObjectDesc(wasKeyValueSet("session", "0", llGetObjectDesc()));
        llResetScript();
    }
}
 
// locked to owner
state settings {
    state_entry() {
        menuList = [ "⏏ Exit", "⌠ Textures ⌡", "⌠ Mode ⌡"];
        if(mav == NULL_KEY) return;
        integer comChannel = (integer)("0x8" + llGetSubString(llGetOwner(), 0, 6)) + (integer)wasKeyValueGet("session", llGetObjectDesc());
        llListen(comChannel, "", mav, "");
        llDialog(mav, "Please select an option from the list below or click the back button to return to the previous menu.", menuList, comChannel);
    }
    touch_start(integer num) {
        mav = llDetectedKey(0);
        // locked to owner, bail out
        if(mav != llGetOwner()) state default;
        integer comChannel = (integer)("0x8" + llGetSubString(llGetOwner(), 0, 6)) + (integer)wasKeyValueGet("session", llGetObjectDesc());
        llListen(comChannel, "", mav, "");
        llDialog(mav, "Please select an option from the list below or click the back button to return to the previous menu.", menuList, comChannel);
    }
    listen(integer channel, string name, key id, string message) {
        if(message == "⏏ Exit") {
            menuList = [];
            state default;
        }
        if(message == "⌠ Textures ⌡") {
            menuList = [];
            state textures;
        }
        if(message == "⌠ Mode ⌡") state mode;
        llDialog(id, "Please select an option from the list below or click the back button to return to the previous menu.", menuList, channel);
    }
    on_rez(integer num) {
        llSetObjectDesc(wasKeyValueSet("session", "0", llGetObjectDesc()));
        llResetScript();
    }
}
 
// locked to owner
state mode {
    state_entry() {
        menuList = [ "⏏ Exit", "☀ PG", "♥ Mature", "☯ Adult"];
        if(mav == NULL_KEY) return;
        integer comChannel = (integer)("0x8" + llGetSubString(llGetOwner(), 0, 6)) + (integer)wasKeyValueGet("session", llGetObjectDesc());
        llListen(comChannel, "", mav, "");
        llDialog(mav, "The chaise lounge can operate in two different modes. Please select an operating mode from the available options below:\n\nPG - general audience with adult entertainment poses hidden.\n\nAdult - both general audience and adult entertainment poses shown.", menuList, comChannel);
    }
    touch_start(integer num) {
        mav = llDetectedKey(0);
        // locked to owner, bail out
        if(mav != llGetOwner()) state default;
        integer comChannel = (integer)("0x8" + llGetSubString(llGetOwner(), 0, 6)) + (integer)wasKeyValueGet("session", llGetObjectDesc());
        llListen(comChannel, "", mav, "");
        llDialog(mav, "The chaise lounge can operate in two different modes. Please select an operating mode from the available options below:\n\nPG - general audience with adult entertainment poses hidden.\n\nAdult - both general audience and adult entertainment poses shown.", menuList, comChannel);
    }
    listen(integer channel, string name, key id, string message) {
        if(message == "☀ PG") {
            llSetObjectDesc(wasKeyValueSet("mode", "pg", llGetObjectDesc()));
            jump settings;
        }
        if(message == "♥ Mature") {
            llSetObjectDesc(wasKeyValueSet("mode", "mature", llGetObjectDesc()));
            jump settings;
        }
        if(message == "☯ Adult") {
            llSetObjectDesc(wasKeyValueSet("mode", "adult", llGetObjectDesc()));
            jump settings;
        }
@settings;
        menuList = [];
        state settings;
    }
    on_rez(integer num) {
        llSetObjectDesc(wasKeyValueSet("session", "0", llGetObjectDesc()));
        llResetScript();
    }
}
 
// locked to owner
state textures {
    state_entry() {
        nLine = llGetInventoryNumber(INVENTORY_TEXTURE)-1;
        do {
            menuList += llGetInventoryName(INVENTORY_TEXTURE, nLine);
        } while(--nLine>-1);
        if(mav == NULL_KEY) return;
        integer comChannel = (integer)("0x8" + llGetSubString(llGetOwner(), 0, 6)) + (integer)wasKeyValueGet("session", llGetObjectDesc());
        llListen(comChannel, "", mav, "");
        llSetObjectDesc(wasKeyValueSet("index", "0", llGetObjectDesc()));
        llDialog(mav, "Please select an option from the list below or click the exit button to return to the previous menu.", wasDialogMenu(menuList, ["⏏ Exit", "⟻ Back", "Next ⟼"], ""), comChannel);
    }
    touch_start(integer num) {
        mav = llDetectedKey(0);
        // locked to owner, bail out
        if(mav != llGetOwner()) state default;
        integer comChannel = (integer)("0x8" + llGetSubString(llGetOwner(), 0, 6)) + (integer)wasKeyValueGet("session", llGetObjectDesc());
        llListen(comChannel, "", mav, "");
        llDialog(mav, "Please select an option from the list below or click the exit button to return to the previous menu.", wasDialogMenu(menuList, ["⏏ Exit", "⟻ Back", "Next ⟼"], ""), comChannel);
    }
    listen(integer channel, string name, key id, string message) {
        if(message == "⏏ Exit") {
            menuList = [];
            state settings;
        }
        if(message == "⟻ Back") {
            llDialog(id, "Please select a texture from the list below:\n", wasDialogMenu(menuList, ["⏏ Exit", "⟻ Back", "Next ⟼"], "<"), channel);
            return;
        }
        if(message == "Next ⟼") {
            llDialog(id, "Please select a texture from the list below:\n", wasDialogMenu(menuList, ["⏏ Exit", "⟻ Back", "Next ⟼"], ">"), channel);
            return;
        }
        llSetLinkTexture(2, message, ALL_SIDES);
        llDialog(id, "Please select a texture from the list below:\n", wasDialogMenu(menuList, ["⏏ Exit", "⟻ Back", "Next ⟼"], ""), channel);
    }
    on_rez(integer num) {
        llSetObjectDesc(wasKeyValueSet("session", "0", llGetObjectDesc()));
        llResetScript();
    }
}
 
state count {
    state_entry() {
        llGetNumberOfNotecardLines(set);
    }
    dataserver(key id, string data) {
        lines = (integer)data;
        state read;
    }
    on_rez(integer num) {
        llSetObjectDesc(wasKeyValueSet("session", "0", llGetObjectDesc()));
        llResetScript();
    }
}
 
state read {
    state_entry() {
        nLine = llGetInventoryNumber(INVENTORY_NOTECARD)-1;
        do {
            if(llGetInventoryName(INVENTORY_NOTECARD, nLine) == set)
            jump found_notecard;
        } while(--nLine>-1);
        llSay(DEBUG_CHANNEL, "Failed to find notecard for chosen set.");
        return;
@found_notecard;
        nLine = 0;
        llGetNotecardLine(set, nLine);
    }
    dataserver(key id, string data) {
        if(data == EOF) {
            llSetText("", <1,1,1>, 1);
            state menu;
        }
        if(data == "") jump continue;
        animations += data;
        integer percent = (integer)(100.0*(float)nLine/(float)lines);
        llSetText("Please wait, loading menu...\n" + wasProgress(percent, 10, ["[", "█", "░", "]"]) + "\n Memory: " + (string)llGetFreeMemory() + "\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n", wasPercentToGradient(percent, "rg"), 1);
@continue;
        llGetNotecardLine(set, ++nLine);
    }
    on_rez(integer num) {
        llSetObjectDesc(wasKeyValueSet("session", "0", llGetObjectDesc()));
        llResetScript();
    }
}
 
state menu {
    state_entry() {
        nLine = llGetListLength(animations)-1;
        do {
            string animation = wasKeyValueGet("n", llList2String(animations, nLine));
            if(llListFindList(menuList, (list)animation) != -1) jump continue;
            menuList += animation;
@continue;
        } while(--nLine>-1);
        if(mav == NULL_KEY) return;
        integer comChannel = (integer)("0x8" + llGetSubString(llGetOwner(), 0, 6)) + (integer)wasKeyValueGet("session", llGetObjectDesc());
        llListen(comChannel, "", mav, "");
        llSetObjectDesc(wasKeyValueSet("index", "0", llGetObjectDesc()));
        llDialog(mav, "Please select an animation from the list below:\n", wasDialogMenu(menuList, ["⏏ Exit", "⟻ Back", "Next ⟼", "⟺ Swap"], ""), comChannel);
    }
    touch_start(integer num) {
        mav = llDetectedKey(0);
        integer comChannel = (integer)("0x8" + llGetSubString(llGetOwner(), 0, 6)) + (integer)wasKeyValueGet("session", llGetObjectDesc());
        llListen(comChannel, "", mav, "");
        llDialog(mav, "Please select an animation from the list below:\n", wasDialogMenu(menuList, ["⏏ Exit", "⟻ Back", "Next ⟼", "⟺ Swap"], ""), comChannel);
    }
    listen(integer channel, string name, key id, string message) {
        if(message == "⏏ Exit") {
            animations = [];
            menuList = [];
            state default;
        }
        if(message == "⟻ Back") {
            llDialog(id, "Please select an animation from the list below:\n", wasDialogMenu(menuList, ["⏏ Exit", "⟻ Back", "Next ⟼", "⟺ Swap"], "<"), channel);
            return;
        }
        if(message == "Next ⟼") {
            llDialog(id, "Please select an animation from the list below:\n", wasDialogMenu(menuList, ["⏏ Exit", "⟻ Back", "Next ⟼", "⟺ Swap"], ">"), channel);
            return;
        }
        list swap = [];
        if(message == "⟺ Swap") {
            swap = llCSV2List(wasKeyValueGet("swap", llGetObjectDesc()));
            set = llList2String(swap, 0);
            swap = llDeleteSubList(swap, 0, 0);
            swap += set;
            llSetObjectDesc(wasKeyValueSet("swap", llList2CSV(swap), llGetObjectDesc()));
            jump process;
        }
        llSetObjectDesc(wasKeyValueSet("select", message, llGetObjectDesc()));
@process;
        string select = wasKeyValueGet("select", llGetObjectDesc());
        list sex = [];
        list pos = [];
        list rot = [];
        nLine = llGetListLength(animations)-1;
        do {
            string kvp = llList2String(animations, nLine);
            string animation = wasKeyValueGet("n", kvp);
            if(animation != select) jump continue_processing;
            sex += wasKeyValueGet("sex", kvp);
            pos += wasKeyValueGet("p", kvp);
            rot += wasKeyValueGet("r", kvp);
@continue_processing;
        } while(--nLine>-1);
        swap = llCSV2List(wasKeyValueGet("swap", llGetObjectDesc()));
        nLine = llGetListLength(swap)-1;
        do {
            set = llList2String(swap, nLine);
            if(llListFindList(sex, (list)set) != -1) jump continue_derez;
            llWhisper(channel, wasKeyValueEncode(["sex", set, "die", set]));
            llSetObjectDesc(wasKeyValueSet(set, "off", llGetObjectDesc()));
            swap = llDeleteSubList(swap, nLine, nLine);
@continue_derez;
        } while(--nLine>-1);
        nLine = llGetListLength(swap)-1;
        do {
            set = llList2String(swap, nLine);
            if(wasKeyValueGet(set, llGetObjectDesc()) == "off") {
                llRezObject(set, llGetPos()+<0,0,1>, ZERO_VECTOR, ZERO_ROTATION, (integer)wasKeyValueGet("session", llGetObjectDesc()));
                llSetObjectDesc(wasKeyValueSet(set, "on", llGetObjectDesc()));
                llSleep(1);
            }
            llWhisper(channel, wasKeyValueEncode(["n", select, "sex", set, "swap", llList2String(sex, nLine), "p", llList2String(pos, nLine), "r", llList2String(rot, nLine), "rp", (string)llGetRootPosition(), "rr", (string)llGetRootRotation()]));
@continue_messages;
        } while(--nLine>-1);
        swap = [];
        sex = [];
        pos = [];
        rot = [];
    }
    on_rez(integer num) {
        llSetObjectDesc(wasKeyValueSet("session", "0", llGetObjectDesc()));
        llResetScript();
    }
}