User:Nandana Singh/nPose

From Second Life Wiki
Jump to navigation Jump to search

nPose v0.028

Content edited by Pudenta Magic 13Apr2012 (admin for nPose group)


nPose Portal

nPose is a system for coordinating animations between several avatars seated on the same object (linkset), such as a couch or bed, as well as rezzable, coordinated props. Unlike previous systems, nPose does not use poseballs to accomplish this.

Scripts and examples can be found inworld at various locations (check the nPose Portal for locations) and updates are posted as notices to the inworld nPose group Current Version: nPose v0.028 13Mar2012.

The scripts below are provided primarily for reference. If you're making objects using nPose, all you need to is edit some notecards -- you don't need to worry about what's in the scripts. Non-scripters may well find it easiest just to grab a copy of the nPose example at above locations or by joining the in-world nPose group and asking there for a copy.

Or IM Innula Zenovka or Pudenta Magic, who will send you the latest version.

WHAT IS THIS AND WHY SHOULD I CARE?

For a long time the SL wiki has had a "UpdateSitTarget" trick that allows you to move an av seated on a prim using llSetLinkPrimitiveParams (treating the av as a linked prim). Lots of single-user furniture uses this to switch the avatar's position as he/she changes poses.

The nPose system applies this to multi-user furniture. Sit on it to pose. Click on it for a menu of poses.

There are several advantages to posing the avatar sitting on your object instead of having the avatar sit on a rezzed poseball:

1 - Blue anim permission dialogs SUCK. Animation permissions are automatically granted if the av is sitting on your prim instead of a separate poseball, so users never have to be bothered by a blue popup. This is especially important when you consider that script dialogs and animation permission dialogs use the same part of the SL screen, so it's very common for people to accidentally refuse anim permissions when they think they're clicking on the MLP menu, or accidentally click an MLP menu button when they're trying to grant animation permissions. By getting rid of the permissions dialog we eliminate this problem.

2 - When your object moves, so do the avatars. In rezzed-balls systems like MLP, the unlinked poseballs and avatars seated on them are left behind when the base moves. nPose allows you to re-arrange your furniture while sitting on it, without having to stand and re-rez the balls. Even better, nPose will allow you to build a multi pose system into a vehicle! (Think of a multi pose parade float, or a pleasure yacht, or a party bus).

3 - No need to spend part of your parcel's prim budget on rezzed poseballs.

4 - Fewer calls to llListen (previously used for communicating with poseballs) and fewer total scripts mean less lag on the sim.

INSTRUCTIONS

To set up your object with nPose, do the following:

1 - Put all the animations in your object.

2 - For each pose set, create a notecard named in the format "SET:<category>:<name>" (without the quotes). Look inside the pink nPose object for examples. You can name one of your cards with the format "DEFAULT:<category>:<name>". The core will then load that pose set when it first starts up. (See http://code.google.com/p/npose/wiki/nPoseMenuConfiguration for more elaborate menu examples).

3 - On each line in the notecard you will define an animation, position, rotation, and face anims for an avatar. Open the notecards in the pink nPose object for examples. Do not worry about setting exact positions and rotations yet. You can use the adjustment tool for that later.

4 - Add the nPose scripts to your object (Core, Dialog, Menu Control). Add a copy of the nPose Slave script for each simultaneously sitting av. (E.g., if there are only couples sets in your object, you only need two slave scripts. For an eight person square dance, you'd need eight slave scripts.)

5 - Touch your object and use the menu to select your pose set. Then click to the Admin menu and select Adjust. You will see a multicolored object rezzed for each pose in the set. You can edit and move these around to re-position avatars. Each time you move one of these handles, the base will chat out the new line with the position and rotation. Copy and paste this into the set notecard, overwriting the old line for that slot. The new position is not permanently recorded until you've saved it to the notecard.


Basic nPose scripts (Core, Dialog, Menu Control and Slave):

nPose Core

/*//( nPose Core 1.27 )//*/

////on getting DOPOSE, read NC from string, fill in data structure, position/animate avs if present
//on av sit, position/animate.  So we'll need 

integer DIALOG = -300000;
integer DIALOG_RESPONSE = -300002;
integer DIALOG_TIMEOUT = -300003;

integer DOPOSE = 200;
integer ADJUST = 201;
integer SWAP = 202;
integer KEYBOARD_CONTROL = 203;
integer DUMP = 204;
integer STOPADJUST = 205;
integer SYNC = 206;
integer DOACTIONS = 207;

integer SLAVEOFFSET = 700000;

string cardprefix = "SET:";
string defaultprefix = "DEFAULT:";
string card;
integer line;
key dataid;
key cardid;//for recognizing adjustments automatically

//for reading BTN notecards that have non-pose commands
string btncard;
integer btnline;
key btnid;
key clicker;//person who clicked an action button

//a 7-strided list with those columns: Anim, Pos, Rot, Faceanim, Av, SatMsg, NotSatMsg
//Av is the key of the av seated in that slot, else ""
//Anim and Faceanim may be tilde ("~") delimited lists.  
list slots;
integer stride = 7;

list adjusters;//when aduster objects are rezzed, their keys are stored here in parallel to slots list

integer primcount;

integer chatchannel;
integer listener;

integer rezadjusters;

integer swapcount;//increment/loop on getting SWAP message.  On loading pose, rotate avs through slots by this much

list SeatedAvs()
{//like AvCount() but returns a list of seated avs, starting with lowest link number and moving up from there
    list avs;
    integer linkcount = llGetNumberOfPrims();
    integer n;
    for (n = linkcount; n >= 0; n--)
    {
        key id = llGetLinkKey(n);
        if (llGetAgentSize(id) != ZERO_VECTOR)
        {
            //it's a real av. add to list
            avs = [id] + avs;//adding it this way prevents having to reverse the av list later
        }
        else
        {
            //we've gotten down to a regular prim.  Break loop and return list
            return avs;
        }
    }
    //there must not have been anyone seated.  Shouldn't ever get here but LSL doesn't know that and wants a return value
    return [];
}

integer AvCount()
{
    //returns the number of avatars sitting on the object
    integer linkcount = llGetNumberOfPrims();
    integer counter = linkcount;
    while (llGetAgentSize(llGetLinkKey(counter)) != ZERO_VECTOR)
    {
        counter--;
    }
    return linkcount - counter;
}


UpdateSlots()
{
    //get list of seated avs
    //stick into an empty slot in slots if there is one
    //if not, unseat the av
    list avs = SeatedAvs();
    list avqueue = avs;//a copy used for populating slots
    integer n;
    integer stop = llGetListLength(slots);
    
    for (n = 0; n < stop; n += stride)
    {
        string slot = llList2String(slots, n + 4);
        if (slot == "")
        {
            //this slot is empty.  stick the next av into it, if there's one waiting in queue
            if (llGetListLength(avqueue))
            {
                //find the first av in queue that is not already in slots and stick it into this slot
                integer counter = 0;
                integer found = FALSE;
                while (!found && llGetListLength(avqueue))
                {
                    key av = llList2Key(avqueue, 0);
                    integer slots_index = llListFindList(slots, [av]);
                    if (slots_index == -1)
                    {
                        slots = llListReplaceList(slots, [av], n + 4, n + 4);
                        found = TRUE;
                    }
                    avqueue = llDeleteSubList(avqueue, 0, 0);
                }
            }
        }
        else
        {
            //there's an av in this slot.  Make sure he/she is still seated
            integer index = llListFindList(avs, [(key)slot]);
            if (index == -1)
            {                
                //av in slots is no longer seated.  assign slot to someone who is
                if (llGetListLength(avqueue))
                {
                    //find the first av in queue that is not already in slots and stick it into this slot
                    integer counter = 0;
                    integer found = FALSE;
                    while (!found && llGetListLength(avqueue))
                    {
                        key av = llList2Key(avqueue, 0);
                        integer slots_index = llListFindList(slots, [av]);
                        if (slots_index == -1)
                        {
                            slots = llListReplaceList(slots, [av], n + 4, n + 4);
                            found = TRUE;
                        }
                        avqueue = llDeleteSubList(avqueue, 0, 0);
                    }
                }             
                else
                {
                    //there's no one in the queue to fill this slot.   Set it to blank
                    slots = llListReplaceList(slots, [""], n + 4, n + 4);                    
                }
            }
            else
            {
                //slot has an av in it, and that av is still seated.  pull them out of the queue
                integer queueindex = llListFindList(avqueue, [(key)slot]);
                if (queueindex != -1)
                {
                    avqueue = llDeleteSubList(avqueue, queueindex, queueindex);
                }
            }
        }
        
        llMessageLinked(LINK_SET, SLAVEOFFSET + n / stride, llDumpList2String(llList2List(slots, n, n + 4), "|"), NULL_KEY);
        
        //send sitmsg for this av if defined and av seated in slot
        if (llList2String(slots, n + 4) != "")
        {
            string sm = llList2String(slots, n + 5);
            if (sm != "")
            {
                list parts = llParseString2List(sm, ["|"], []);
                llMessageLinked(LINK_SET, (integer)llList2String(parts, 0), llList2String(parts, 1), (key)llList2String(slots, n + 4));
            }
        }        
        else
        {
            //since av is no longer in this slot, send NotSatMsg if defined
            string nsm = llList2String(slots, n + 6);
            if (nsm != "")
            {
                list parts = llParseString2List(nsm, ["|"], []);
                llMessageLinked(LINK_SET, (integer)llList2String(parts, 0), llList2String(parts, 1), "");
            }            
        }
    }
    
    //if there's anyone left in avqueue by now we have no slot for them, so unsit them
    stop = llGetListLength(avqueue);
    for (n = 0; n < stop; n++)
    {
        llUnSit(llList2Key(avqueue, n));
    }    
}

ReadCard()
{
    //clear data and kill any rezzed objects
    slots = [];                
    
    //kill props
    llSay(chatchannel, "die");
    
    //kill adjusters
    llSay(chatchannel, "adjuster_die");    
    adjusters = [];    
    
    line = 0;
    cardid = llGetInventoryKey(card);
    dataid = llGetNotecardLine(card, line);    
}

RezNextAdjuster()
{
    llRezObject("Adjuster", llGetPos() + <0,0,1>, ZERO_VECTOR, llGetRot(), chatchannel);    
}

debug(string str)
{
    //llOwnerSay(llGetScriptName() + ": " + str);
}

ChatAdjusterPos(integer slotnum)
{
    integer index = slotnum * stride;
    vector pos = llGetPos() + llList2Vector(slots, index + 1) * llGetRot();
    //rotation rot = ZERO_ROTATION * llList2Rot(slots, index + 2) / llGetRot();
    rotation rot = llList2Rot(slots, index + 2) * llGetRot();
    string out = llList2String(adjusters, slotnum) + "|posrot|" + (string)pos + "|" + (string)rot;
    llSay(chatchannel, out);    
}

SwapSlots()
{
    //swap avs through "count" slots
    integer n;
    integer stop = llGetListLength(slots);
    //get list of just the avatars
    list avslots;
    for (n = 0; n < stop; n += stride)
    {
        key av = llList2Key(slots, n + 4);
        avslots += [av];
    }
    //rotate it
    avslots = llList2List(avslots, 1, -1) + llList2List(avslots, 0, 0);
    //replace
    for (n = 0; n < stop; n += stride)
    {
       slots = llListReplaceList(slots, llList2List(avslots, n / stride, n / stride), n + 4, n + 4);
    }
    
    //now slice it back so the same av's in the same slot but with new anim,pos,rot
    slots = llList2List(slots, -stride, -1) + llList2List(slots, 0, -stride - 1);    
}

string str_replace(string src, string from, string to)
{//replaces all occurrences of 'from' with 'to' in 'src'.
    integer len = (~-(llStringLength(from)));
    if(~len)
    {
        string  buffer = src;
        integer b_pos = -1;
        integer to_len = (~-(llStringLength(to)));
        @loop;//instead of a while loop, saves 5 bytes (and run faster).
        integer to_pos = ~llSubStringIndex(buffer, from);
        if(to_pos)
        {
            buffer = llGetSubString(src = llInsertString(llDeleteSubString(src, b_pos -= to_pos, b_pos + len), b_pos, to), (-~(b_pos += to_len)), 0x8000);
            jump loop;
        }
    }
    return src;
}

string LineReplacements(string line, key av)
{
    return str_replace(line, "%AVKEY%", (string)av);
}

ProcessLine(string line, key av)
{
    line = LineReplacements(line, av);
    list params = llParseString2List(line, ["|"], []);
    string action = llList2String(params, 0);
    if (action == "ANIM")
    {
        //other params are anim_name|pos|rot|faceanims
        list newslot = [llList2String(params, 1), (vector)llList2String(params, 2), llEuler2Rot((vector)llList2String(params, 3) * DEG_TO_RAD), llList2String(params, 4), "", "", ""];
        slots += newslot;//add the params to slots, plus blank spots for "Av", "SitMsg", and "UnSitMsg"
    }
    else if (action == "PROP")
    {
        //rez object at provided pos/rot
        string obj = llList2String(params, 1);
        //make sure we have the object
        if (llGetInventoryType(obj) == INVENTORY_OBJECT)
        {
            vector pos = llGetPos() + ((vector)llList2String(params, 2) * llGetRot());
            rotation rot = llEuler2Rot((vector)llList2String(params, 3) * DEG_TO_RAD) * llGetRot();
            llRezAtRoot(obj, pos, ZERO_VECTOR, rot, chatchannel);
        }
    }
    else if (action == "LINKMSG")
    {
        integer num = (integer)llList2String(params, 1);
        string str = llList2String(params, 2);
        key lmid = (key)llList2String(params, 3);
        llMessageLinked(LINK_SET, num, str, lmid);
    }
    else if (action == "SATMSG")
    {
        //will be in same format as linkmsg, but instead of sending it now we'll save it til the av sits and send in UpdateSlots
        //also, instead of specifying the key field, it will have the av's key in it
        //so replace the SitMsg field in the last stride
        integer index = llGetListLength(slots) - stride + 5;
        slots = llListReplaceList(slots, [llDumpList2String(llDeleteSubList(params, 0, 0), "|")], index, index);
    }
    else if (action == "NOTSATMSG")
    {
        //will be in same format as linkmsg, but instead of sending it now we'll save it til the av stands and send in UpdateSlots
        //also, instead of specifying the key field, it will have the av's key in it                
        //so replace the UnSitMsg field in the last stride
        integer index = llGetListLength(slots) - stride + 6;
        slots = llListReplaceList(slots, [llDumpList2String(llDeleteSubList(params, 0, 0), "|")], index, index);                
    }
    
}

default
{    
    state_entry()
    {   
        chatchannel = (integer)("0x" + llGetSubString((string)llGetKey(), 0, 7));
        listener = llListen(chatchannel, "", "", "");        
        
        //find first pose set card in inventory and set it as default
        integer n;
        integer stop = llGetInventoryNumber(INVENTORY_NOTECARD);
        
        string setcard;
        string defaultcard;
        //find the DEFAULT card.  If not found use the first SET card
        //the loop below works because D (Default) comes before S (Set)
        for (n = 0; n < stop; n++)
        {
            string name = llGetInventoryName(INVENTORY_NOTECARD, n);
            if (llSubStringIndex(name, defaultprefix) == 0)
            {
                card = name;
                ReadCard();
                return;                
            }            
            else if (llSubStringIndex(name, cardprefix) == 0)
            {
                card = name;
                ReadCard();
                return;
            }
        }
    }

    link_message(integer sender, integer num, string str, key id)
    {
        if (num == DOPOSE)
        {
            //read notecard identified by str
            if (llGetInventoryType(str) == INVENTORY_NOTECARD)
            {                              
                card = str;
                ReadCard();         
                clicker = id;
            }
        }
        else if (num == DOACTIONS)
        {
            //str should be a valid notecard.
            //read it and take any messages
            btncard = str;
            btnline = 0;
            btnid = llGetNotecardLine(btncard, btnline);
            clicker = id;
        }        
        else if (num == ADJUST)
        {
            llSay(chatchannel, "adjuster_die");
            adjusters = [];
            rezadjusters = TRUE;
            RezNextAdjuster();
        }
        else if (num == STOPADJUST)
        {
            llSay(chatchannel, "adjuster_die");
            adjusters = [];
            rezadjusters = FALSE;            
        }
        else if (num == DUMP)
        {
            //loop through slots and ownersay each
            integer n;
            integer stop= llGetListLength(slots);
            for (n = 0; n < stop; n += stride)
            {              
                //pull out our slot
                list slice = llList2List(slots, n, n + 3);
                //put the rot in degree format for the notecard
                slice = llListReplaceList(slice, [RAD_TO_DEG * llRot2Euler(llList2Rot(slice, 2))], 2, 2);
                llOwnerSay("ANIM|" + llDumpList2String(slice, "|"));
            }          
            
            //also have any props chat out their positions
            llSay(chatchannel, "posdump");
        }
        else if (num == SWAP)
        {
            debug("swapping");
            //remember the number of times we've swapped, so we can re-apply it on pose change
            swapcount++;
            if (swapcount > (llGetListLength(slots) / stride)- 1)
            {
                swapcount = 0;
            }
            //no need to swap if there's only 1 slot
            if (llGetListLength(slots) > stride)
            {                
                SwapSlots();
                //debug("postswap: " + llDumpList2String(slots, "\n"));            
                UpdateSlots();           
                
                //do adjusters too
                integer n;
                integer adjcount = llGetListLength(adjusters);
                for (n = 0; n < adjcount; n++)
                {
                    ChatAdjusterPos(n);
                }
            }
            else
            {
                swapcount = 0;//if there's only one slot, nothing other than 0 swapcount makes sense
            }
        }
    }
    
    object_rez(key id)
    {
        if (llKey2Name(id) == "Adjuster")
        {
            //we just rezzed an adjuster.  Rez another one if there are more slots
            adjusters += [id];
            ChatAdjusterPos(llGetListLength(adjusters) - 1);
            //llSay(0, out);
            
            if (llGetListLength(adjusters) < llGetListLength(slots) / stride)
            {
                RezNextAdjuster();
            }            
        }
        else
        {
            //we just rezzed a prop
        }
    }
    
    listen(integer channel, string name, key id, string message)
    {
        if (name == "Adjuster")
        {
            integer index = llListFindList(adjusters, [id]);
            if (index != -1)
            {
                //we've heard from one of the adjusters
                //update slots
                list params = llParseString2List(message, ["|"], []);
                //get pos relative to 
                vector newpos = (vector)llList2String(params, 0) - llGetPos();
                //correct for parent rotation
                newpos = newpos / llGetRot();
                integer slotsindex = index * stride;            
                rotation newrot = (rotation)llList2String(params, 1) / llGetRot();           
                slots = llListReplaceList(slots, [newpos, newrot], slotsindex + 1, slotsindex + 2);
                llOwnerSay("ANIM|" + llList2String(slots, slotsindex) + "|" + (string)newpos + "|" + (string)(llRot2Euler(newrot) * RAD_TO_DEG) + "|" + llList2String(slots, slotsindex + 3));
                UpdateSlots();
            }            
        }
        else
        {
            //it was probably a prop
            if (llGetOwnerKey(id) == llGetOwner())
            {
                if (message == "ping")
                {
                    llSay(chatchannel, "pong");
                }
                else
                {
                    list params = llParseString2List(message, ["|"], []);
                    //get pos relative to 
                    vector newpos = (vector)llList2String(params, 0) - llGetPos();
                    //correct for parent rotation
                    newpos = newpos / llGetRot();          
                    rotation newrot = (rotation)llList2String(params, 1) / llGetRot();
                    llOwnerSay("PROP|" + name + "|" + (string)newpos + "|" + (string)(llRot2Euler(newrot) * RAD_TO_DEG));
                    UpdateSlots();                                    
                }
            }
        }
    }
    
    dataserver(key id, string data)
    {
        if (id == dataid)
        {
            if (data == EOF)
            {
                //swap the slots by slotcount to restore user's swap
                if (swapcount > (llGetListLength(slots) / stride)- 1)
                {
                    swapcount = 0;
                }
                integer n;
                for (n = 0; n < swapcount; n++)
                {
                    SwapSlots();
                }
                
                //we're done reading the card.  let's set the anims now.
                UpdateSlots();
                
                if (rezadjusters)
                {
                    adjusters = [];
                    RezNextAdjuster();
                }
            }
            else
            {
                //process data and get the next line
                ProcessLine(data, clicker);                
                //since we're not EOF yet there's at least one more line.  get it
                line++;
                dataid = llGetNotecardLine(card, line);
            }            
        }
        else if (id == btnid)
        {
            if (data != EOF)
            {
                ProcessLine(data, clicker);
                btnline++;
                btnid = llGetNotecardLine(btncard, btnline);
            }
        }
    }
    
    changed(integer change)
    {
        if (change & CHANGED_LINK)
        {
            UpdateSlots();
        }
        
        if (change & CHANGED_INVENTORY)
        {
            if (card != "")
            {
                //only re-read same card if still present
                if (llGetInventoryType(card) == INVENTORY_NOTECARD)
                {
                    if (cardid != llGetInventoryKey(card))
                    {
                        //the card has changed. re-read!
                        ReadCard();                         
                    }                                             
                }
                else
                {
                    llResetScript();
                }                              
            }
        }
        
        if (change & CHANGED_REGION)
        {
            //when entering a new region, SL puts the avs at their sit targets instead of their set positions
            //so resend their slot info to slaves to correct for this after crossing
            UpdateSlots();
        }
    }
    
    on_rez(integer param)
    {
        llResetScript();
    }
    
}

nPose Dialog

/*//( nPose Dialog 1.27 )//*/
//an adaptation of Schmobag Hogfather's SchmoDialog script

integer DIALOG = -900;
integer DIALOG_RESPONSE = -901;
integer DIALOG_TIMEOUT = -902;

integer pagesize = 12;
string MORE = ">";
//string BACKBTN = "^";
//string SWAPBTN = "swap";
//string SYNCBTN = "sync";
string BLANK = " ";
integer timeout = 60;
integer repeat = 5;//how often the timer will go off, in seconds

list menus;//9-strided list in form listenchannel, dialogid, listener, starttime, recipient, prompt, list buttons, page buttons, currentpage
//where "list buttons" means the big list of choices presented to the user
//and "page buttons" means utility buttons that will appear on every page, such as one saying "go up one level"
//and "currentpage" is an integer meaning which page of the menu the user is currently viewing

integer stridelength = 9;

list avs;//fill this on start and update on changed_link.  leave dialogs open until avs stand

list SeatedAvs()
{//like AvCount() but returns a list of seated avs, starting with lowest link number and moving up from there
    list avs;
    integer linkcount = llGetNumberOfPrims();
    integer n;
    for (n = linkcount; n >= 0; n--)
    {
        key id = llGetLinkKey(n);
        if (llGetAgentSize(id) != ZERO_VECTOR)
        {
            //it's a real av. add to list
            avs = [id] + avs;//adding it this way prevents having to reverse the av list later
        }
        else
        {
            //we've gotten down to a regular prim.  Break loop and return list
            return avs;
        }
    }
    //there must not have been anyone seated.  Shouldn't ever get here but LSL doesn't know that and wants a return value
    return [];
}

integer RandomUniqueChannel()
{
    integer out = llRound(llFrand(10000000)) + 100000;
    if (~llListFindList(menus, [out]))
    {
        out = RandomUniqueChannel();
    }
    return out;
}

Dialog(key recipient, string prompt, list menuitems, list utilitybuttons, integer page, key id)
{
    string thisprompt = prompt + " (Timeout in 60 seconds.)\n";
    list buttons;
    list currentitems;
    integer numitems = llGetListLength(menuitems + utilitybuttons);
    integer start;
    integer mypagesize = pagesize - llGetListLength(utilitybuttons);
        
    //slice the menuitems by page
    if (numitems > pagesize)
    {
        mypagesize--;//we'll use one slot for the MORE button, so shrink the page accordingly
        start = page * mypagesize;
        integer end = start + mypagesize - 1;
        //multi page menu
        currentitems = llList2List(menuitems, start, end);
    }
    else
    {
        start = 0;
        currentitems = menuitems;
    }
    
    integer stop = llGetListLength(currentitems);
    integer n;
    for (n = 0; n < stop; n++)
    {
        string name = llList2String(menuitems, start + n);
        buttons += [name];
    }
    
    buttons = SanitizeButtons(buttons);
    utilitybuttons = SanitizeButtons(utilitybuttons);
    
    integer channel = RandomUniqueChannel();
    integer listener = llListen(channel, "", recipient, "");
    llSetTimerEvent(repeat);
    if (numitems > mypagesize)
    {
        llDialog(recipient, thisprompt, PrettyButtons(buttons, utilitybuttons + [MORE]), channel);      
    }
    else
    {
        llDialog(recipient, thisprompt, PrettyButtons(buttons, utilitybuttons), channel);
    }    
    integer ts = -1;
    if (llListFindList(avs, [recipient]) == -1)
    {
        ts = llGetUnixTime();
    }
    menus += [channel, id, listener, ts, recipient, prompt, llDumpList2String(menuitems, "|"), llDumpList2String(utilitybuttons, "|"), page];
}

list SanitizeButtons(list in)
{
    integer length = llGetListLength(in);
    integer n;
    for (n = length - 1; n >= 0; n--)
    {
        integer type = llGetListEntryType(in, n);
        if (llList2String(in, n) == "") //remove empty strings
        {
            in = llDeleteSubList(in, n, n);
        }        
        else if (type != TYPE_STRING)        //cast anything else to string
        {
            in = llListReplaceList(in, [llList2String(in, n)], n, n);
        }
    }
    return in;
}

list PrettyButtons(list options, list utilitybuttons)
{//returns a list formatted to that "options" will start in the top left of a dialog, and "utilitybuttons" will start in the bottom right
    list spacers;
    list combined = options + utilitybuttons;
    while (llGetListLength(combined) % 3 != 0 && llGetListLength(combined) < 12)    
    {
        spacers += [BLANK];
        combined = options + spacers + utilitybuttons;
    }    
    
    list out = llList2List(combined, 9, 11);
    out += llList2List(combined, 6, 8);
    out += llList2List(combined, 3, 5);    
    out += llList2List(combined, 0, 2);    
    return out;    
}


list RemoveMenuStride(list menu, integer index)
{
    //tell this function the menu you wish to remove, identified by list index
    //it will close the listener, remove the menu's entry from the list, and return the new list
    //should be called in the listen event, and on menu timeout    
    integer listener = llList2Integer(menu, index + 2);
    llListenRemove(listener);
    return llDeleteSubList(menu, index, index + stridelength - 1);
}

CleanList()
{
    debug("cleaning list");
    //loop through menus, check their start times against current time, remove any that are more than <timeout> seconds old
    //start at end of list and loop down so that indexes don't get messed up as we remove items
    integer length = llGetListLength(menus);
    integer n;
    for (n = length - stridelength; n >= 0; n -= stridelength)
    {
        integer starttime = llList2Integer(menus, n + 3);
        debug("starttime: " + (string)starttime);
        if (starttime == -1)
        {          
            //menu was for seated av.  close if they're not seated anymore
            key av = (key)llList2String(menus, n + 4);
            if (llListFindList(avs, [av]) == -1)
            {
                debug("mainmenu stood");
                menus = RemoveMenuStride(menus, n);
            }
        }
        else
        {//was a plain old non-seated menu, most likely for owner.  Do timeouts normally
            integer age = llGetUnixTime() - starttime;
            if (age > timeout)
            {
                debug("mainmenu timeout");                
                key id = llList2Key(menus, n + 1);
                llMessageLinked(LINK_SET, DIALOG_TIMEOUT, "", id);
                menus = RemoveMenuStride(menus, n);
            }            
        }
    } 
}

debug(string str)
{
    //llOwnerSay(llGetScriptName() + ": " + str);
}

default
{    
    on_rez(integer param)
    {
        llResetScript();
    }

    state_entry()
    {
        avs = SeatedAvs();
    }
    
    changed(integer change)
    {
        if (change & CHANGED_LINK)
        {
            avs = SeatedAvs();
            //loop through dialogs and close any for avs that aren't seated.  except for obj owner
        }
    }

    link_message(integer sender, integer num, string str, key id)
    {
        if (num == DIALOG)
        {//give a dialog with the options on the button labels
            //str will be pipe-delimited list with rcpt|prompt|page|backtick-delimited-list-buttons|backtick-delimited-utility-buttons
            debug(str);
            list params = llParseStringKeepNulls(str, ["|"], []);
            key rcpt = (key)llList2String(params, 0);
            string prompt = llList2String(params, 1);
            integer page = (integer)llList2String(params, 2);
            list lbuttons = llParseStringKeepNulls(llList2String(params, 3), ["`"], []);
            list ubuttons = llParseStringKeepNulls(llList2String(params, 4), ["`"], []);            
            Dialog(rcpt, prompt, lbuttons, ubuttons, page, id);
        }
    }
    
    listen(integer channel, string name, key id, string message)
    {
        integer menuindex = llListFindList(menus, [channel]);
        if (~menuindex)
        {
            key menuid = llList2Key(menus, menuindex + 1);
            string prompt = llList2String(menus, menuindex + 5);            
            list items = llParseStringKeepNulls(llList2String(menus, menuindex + 6), ["|"], []);
            list ubuttons = llParseStringKeepNulls(llList2String(menus, menuindex + 7), ["|"], []);
            integer page = llList2Integer(menus, menuindex + 8);    
            menus = RemoveMenuStride(menus, menuindex);              
            if (message == MORE)
            {
                debug((string)page);
                //increase the page num and give new menu
                page++;
                integer thispagesize = pagesize - llGetListLength(ubuttons) - 1;
                if (page * thispagesize > llGetListLength(items))
                {
                    page = 0;
                }
                Dialog(id, prompt, items, ubuttons, page, menuid);
            }
            else if (message == BLANK)
            {
                //give the same menu back
                Dialog(id, prompt, items, ubuttons, page, menuid);
            }            
            else
            {
                llMessageLinked(LINK_SET, DIALOG_RESPONSE, (string)page + "|" + message, menuid);
            }       
        }
    }
    
    timer()
    {
        CleanList();    
        
        //if list is empty after that, then stop timer
        
        if (!llGetListLength(menus))
        {
            llSetTimerEvent(0.0);
        }
    }
}

nPose Menu Control

/*//( nPose Menu Control 1.27 )//*/
list menus;//strided list in form [colon-delimited menupath,pipe-delimited items in that menu]

string setprefix = "SET";
string defaultprefix = "DEFAULT";
string btnprefix = "BTN";

list cardprefixes = [setprefix, defaultprefix, btnprefix];

list dialogids;//3-strided list of dialog ids, the avs they belong to, and the menu path.
integer stride = 3;

integer DIALOG = -900;
integer DIALOG_RESPONSE = -901;
integer DIALOG_TIMEOUT = -902;

integer DOPOSE = 200;
integer ADJUST = 201;
integer SWAP = 202;
integer KEYBOARD_CONTROL = 203;
integer DUMP = 204;
integer STOPADJUST = 205;
integer SYNC = 206;
integer DOBUTTON = 207;

integer DOMENU = -800;

string SWAPBTN = "swap";
string SYNCBTN = "sync";
string BACKBTN = "^";

string ROOTMENU = "Main";

string ADMINBTN = "admin";
string ADJUSTBTN = "Adjust";
string STOPADJUSTBTN = "StopAdjust";
string POSDUMPBTN = "PosDump";
string UNSITBTN = "Unsit";
list adminbuttons = [ADJUSTBTN, STOPADJUSTBTN, POSDUMPBTN, UNSITBTN];

key Dialog(key rcpt, string prompt, list choices, list utilitybuttons, integer page)
{
    //do a spurious request just to get a uuid.  Http is better than dataserver because it only involves local sim
    key id = llHTTPRequest("http://google.com", [HTTP_METHOD, "GET"], "");
    llMessageLinked(LINK_SET, DIALOG, (string)rcpt + "|" + prompt + "|" + (string)page + "|" + llDumpList2String(choices, "`") + "|" + llDumpList2String(utilitybuttons, "`"), id);
    return id;
}

list SeatedAvs()
{
    list avs;
    integer counter = llGetNumberOfPrims();
    //llOwnerSay("there are " + (string)linkcount + " prims in me");
    while (llGetAgentSize(llGetLinkKey(counter)) != ZERO_VECTOR)
    {
        avs += [llGetLinkKey(counter)];
        counter--;
    }    
    return avs;
}

UnseatByName(string avname)
{
    list avs = SeatedAvs();
    integer n;
    integer stop = llGetListLength(avs);
    for (n = 0; n < stop; n++)
    {
        key av = llList2Key(avs, n);
        if (llKey2Name(av) == avname)
        {
            llUnSit(av);
            return;
        }
    }    
}

integer AvCount()
{
    //returns the number of avatars sitting on the object
    integer linkcount = llGetNumberOfPrims();
    integer counter = linkcount;
    //llOwnerSay("there are " + (string)linkcount + " prims in me");
    while (llGetAgentSize(llGetLinkKey(counter)) != ZERO_VECTOR)
    {
        counter--;
    }
    //llOwnerSay("there are " + (string)avcount + " avs on me");
    return linkcount - counter;
}

AdminMenu(key toucher, string path)
{
    list buttons = adminbuttons;
    key id = Dialog(toucher, "Pick an option.", buttons, [BACKBTN], 0);
    integer index = llListFindList(dialogids, [toucher]);
    
    list addme = [id, toucher, path];
    if (index == -1)
    {
        dialogids += addme;
    }    
    else
    {
        dialogids = llListReplaceList(dialogids, addme, index - 1, index + 1);        
    }
}

UnsitMenu(key av, string path)
{
    list avs = SeatedAvs();
    list buttons;
    integer n;
    integer stop = llGetListLength(avs);
    for (n = 0; n < stop; n++)
    {
        buttons += [llKey2Name((key)llList2String(avs, n))];
    }    
    
    key dialogid = Dialog(av, "Pick an avatar to unsit.", buttons, [BACKBTN], 0);
    list addme = [dialogid, av, path];
    integer index = llListFindList(dialogids, [av]);
    if (index == -1)
    {
        dialogids += addme;
    }    
    else
    {
        dialogids = llListReplaceList(dialogids, addme, index - 1, index + 1); 
    }    
}

DoMenu(key toucher, string path, integer page)
{
    //get list of buttons from menus list
    //give dialog to toucher
    integer index = llListFindList(menus, [path]);
    if (index != -1)
    {
        list buttons = llParseStringKeepNulls(llList2String(menus, index + 1), ["|"], []);
    
        list utility = [SYNCBTN, SWAPBTN];
        if (toucher == llGetOwner())//admin menu 
        {
            utility += [ADMINBTN];
        }    
        
        //add back button if we're in anything other than root menu
        if (path != ROOTMENU)
        {
            utility += [BACKBTN];
        }
        
        key id = Dialog(toucher, "Pick your pose.", buttons, utility, page);    
        
        //record dialog id, av, path
        list addme = [id, toucher, path];
        
        //we don't support simultaneous menus from the same person.  so if they're av key is in here, replace the entry
        integer index = llListFindList(dialogids, [toucher]);
        if (index == -1)
        {
            dialogids += addme;
        }
        else
        {
            //he's already using the menu, replace his dialog id
            dialogids = llListReplaceList(dialogids, addme, index - 1, index + 2);        
        }        
    }
    else
    {
        debug("error: path '" + path + "' not present in list");
    }
}

DoNode(list path)
{
    //debug("doing node: " + llDumpList2String(path, ":"));
    string last = llList2String(path, -1);
    string parentpath = llDumpList2String([ROOTMENU] + llDeleteSubList(path, -1, -1), ":");
    
    //if parentpath is in "menus" list, then just add this child
    //else add parentpath and child to menus list
    integer index = llListFindList(menus, [parentpath]);
    if (index != -1 && !(index % 2))
    {
        //parentpath found in menus list, in path part of the stride.  add this child to the existing one(s)
        list children = llParseStringKeepNulls(llList2String(menus, index + 1), ["|"], []);
        //it should already be impossible to add duplicates but let's make sure
        if (llListFindList(children, [last]) == -1)
        {
            children += [last];
            menus = llListReplaceList(menus, [llDumpList2String(children, "|")], index + 1, index + 1);
        }
    }
    else
    {
        //parentpath was not found in menus list in path part of the stride.  add both parentpath and child in one swoop
        menus += [parentpath, last];
    }
}

DoPath(list path)
{
    //loop down to make sure that each branch in path exists in menu structure
    while(llGetListLength(path))
    {
        DoNode(path);
        path = llDeleteSubList(path, -1, -1);
    }
}

BuildMenus()
{
    menus = [];
    integer n;
    integer stop = llGetInventoryNumber(INVENTORY_NOTECARD);
    for (n = 0; n < stop; n++)
    {
        string name = llGetInventoryName(INVENTORY_NOTECARD, n);
        list path = llParseStringKeepNulls(name, [":"], []);
        string prefix = llList2String(path, 0);
        if (llListFindList(cardprefixes, [prefix]) != -1)
        {
            DoPath(llDeleteSubList(path, 0, 0));            
        }
    }    
}

debug(string str)
{
    //llOwnerSay(llGetScriptName() + ": " + str);
}

default
{
    state_entry()
    {
        BuildMenus();        
        debug("menus:\n" + llDumpList2String(menus, "\n"));        
    }

    touch_start(integer total_number)
    {
        key toucher = llDetectedKey(0);        
        integer authorized = FALSE;
        
        //only give menu to owner or seated av
        if (toucher == llGetOwner())
        {
            authorized = TRUE;
        }
        else if (llListFindList(SeatedAvs(), [toucher]) != -1)
        {
            authorized = TRUE;
        }
        
        if (authorized)
        {
            //toucher is allowed to use menu.
            DoMenu(toucher, ROOTMENU, 0);
        }
    }
    
    link_message(integer sender, integer num, string str, key id)
    {
        //debug((string)num + ", " + str + ", " + (string)id);        
        //debug("linkmsg, dialogids: " + llDumpList2String(dialogids, ", "));
        
        if (num == DIALOG_RESPONSE)
        {
            debug("dialog response: " + str);
            //distinguish between main menu and submenus
            integer index = llListFindList(dialogids, [id]);      
            if (index != -1)
            {
                list params = llParseString2List(str, ["|"], []);
                integer page = (integer)llList2String(params, 0);
                string selection = llList2String(params, 1);
                
                key toucher = llList2Key(dialogids, index + 1);              
                string path = llList2String(dialogids, index + 2);
                
                debug("dialog path: " + path);
                //remove this dialog id
                dialogids = llDeleteSubList(dialogids, index, index + stride - 1);
                
                //return menus always                
                if (selection == ADMINBTN)
                {
                    //give admin menu
                    AdminMenu(toucher, path + ":" + ADMINBTN);
                }
                else if (selection == BACKBTN)
                {
                    //give the parent menu
                    list pathparts = llParseString2List(path, [":"], []);
                    pathparts = llDeleteSubList(pathparts, -1, -1);
                    
                    if (llList2String(pathparts, -1) == ADMINBTN)//allows for having submenus inside admin
                    {
                        AdminMenu(toucher, llDumpList2String(pathparts, ":"));
                    }
                    else if (llGetListLength(pathparts))
                    {
                        DoMenu(toucher, llDumpList2String(pathparts, ":"), 0);
                    }
                    else
                    {
                        DoMenu(toucher, ROOTMENU, 0);
                    }
                }
                else if (selection == SWAPBTN)
                {
                    llMessageLinked(LINK_SET, SWAP, "", "");
                    DoMenu(toucher, path, page);
                }          
                else if (selection == SYNCBTN)
                {
                    llMessageLinked(LINK_SET, SYNC, "", "");
                    DoMenu(toucher, path, page);                    
                }                    
                else if (~llListFindList(menus, [path + ":" + selection]))
                {
                    //there's a menu for the selection.
                    DoMenu(toucher, path + ":" + selection, 0);
                }
                else if (llList2String(llParseString2List(path, [":"], []), -1) == ADMINBTN)
                {
                    //we're inside an admin menu
                    if (selection == ADJUSTBTN)
                    {
                        llMessageLinked(LINK_SET, ADJUST, "", "");
                        AdminMenu(toucher, path);
                    }
                    else if (selection == STOPADJUSTBTN)
                    {
                        llMessageLinked(LINK_SET, STOPADJUST, "", "");                        
                        AdminMenu(toucher, path);
                    }
                    else if (selection == POSDUMPBTN)
                    {
                        llMessageLinked(LINK_SET, DUMP, "", "");
                        AdminMenu(toucher, path);
                    }                  
                    else if (selection == UNSITBTN)
                    {
                        UnsitMenu(toucher, path + ":" + selection);
                    }
                }
                else if (llList2String(llParseString2List(path, [":"], []), -1) == UNSITBTN)
                {
                    //we just got a response from inside an unsit menu, so it should be an av name.
                    UnseatByName(selection);
                    UnsitMenu(toucher, path);
                }
                else
                {
                    DoMenu(toucher, path, page);
                }

                //do pose.  "DEFAULT" having priority over "SET" set if both exist
                //first remove the "Main:" from the path since that's not in card names
                list pathlist = llDeleteSubList(llParseStringKeepNulls(path, [":"], []), 0, 0);
                string defaultname = llDumpList2String([defaultprefix] + pathlist + [selection], ":");                
                string setname = llDumpList2String([setprefix] + pathlist + [selection], ":");
                if (llGetInventoryType(defaultname) == INVENTORY_NOTECARD)
                {
                    llMessageLinked(LINK_SET, DOPOSE, defaultname, toucher);                    
                }
                else if (llGetInventoryType(setname) == INVENTORY_NOTECARD)
                {
                    llMessageLinked(LINK_SET, DOPOSE, setname, toucher);
                }                             
                
                //or if there's a BTN card then just read it and send its messages
                //note that this means you can have a BTN card and SET card with same path and name.
                //both will take effect                
                string btnname = llDumpList2String([btnprefix] + pathlist + [selection], ":");
                if (llGetInventoryType(btnname) == INVENTORY_NOTECARD)
                {
                    llMessageLinked(LINK_SET, DOBUTTON, btnname, toucher);
                }
            }            
        }         
        else if (num == DIALOG_TIMEOUT)
        {
            integer index = llListFindList(dialogids, [id]);
            if (index != -1)
            {
                dialogids = llDeleteSubList(dialogids, index, index + stride - 1);
            }                
        }      
        else if (num == DOMENU)
        {
            if (str)
            {
                DoMenu(id, str, 0);
            }
            else
            {
                DoMenu(id, ROOTMENU, 0);                
            }
        }
    }
    
    changed(integer change)
    {
        if (change & CHANGED_INVENTORY)
        {
            BuildMenus();           
        }
        
        if (change & CHANGED_OWNER)
        {
            llResetScript();
        }
    }
}

nPose Slave

/*//( nPose Slave script 1.27 ) -- Place one copy of the script in the prim for each avatar you wish to seat//*/
//
integer DOPOSE = 200;
integer ADJUST = 201;
integer SWAP = 202;
//integer KEYBOARD_CONTROL = 203;
integer DUMP = 204;
integer STOPADJUST = 205;
integer SYNC = 206;
integer SLAVEOFFSET = 700000;

integer slavenum;

list anims;
integer animcount;
integer animindex;
integer ticker;//used to count seconds so we can use timer for both face anims and long anim sequences

string currentanim;
vector pos;
rotation rot;
list faceanims;
integer facecount;
integer faceindex;
key av;

integer avlinknum;

float syncsleep = 0.05;

MoveLinkedAv(integer linknum, vector avpos, rotation avrot)
{
    key user = llGetLinkKey(linknum);
    if(user)//true if there is a user seated on the sittarget, if so update their position
    {
        vector size = llGetAgentSize(user);
        if(size)//This tests to make sure the user really exists.
        {
            //We need to make the position and rotation local to the current prim
            rotation localrot = ZERO_ROTATION;
            vector localpos = ZERO_VECTOR;
            if(llGetLinkNumber() > 1)//only need the local rot if it's not the root.
            {
                localrot = llGetLocalRot();
                localpos = llGetLocalPos();
            }
            avpos.z += 0.4;
            llSetLinkPrimitiveParams(linknum, [PRIM_POSITION, ((avpos - (llRot2Up(avrot) * size.z * 0.02638)) * localrot) + localpos, PRIM_ROTATION, avrot * localrot / llGetRootRotation()]);
        }
    }    
}


integer AvLinkNum(key av)
{
    integer linkcount = llGetNumberOfPrims();
    while (av != llGetLinkKey(linkcount))
    {
        if (llGetAgentSize(llGetLinkKey(linkcount)) == ZERO_VECTOR)
        {
            return -1;
        }
        linkcount--;
    }
    return linkcount;
}

debug(string str)
{
    //llOwnerSay(llGetScriptName() + ": " + str);
}

CleanUp()
{
    //stop the current anim if there is one
    if (llGetInventoryType(currentanim) == INVENTORY_ANIMATION)
    {
        if (llKey2Name(llGetPermissionsKey()))
        {
            if (llGetPermissions() & PERMISSION_TRIGGER_ANIMATION)
            {
                llStopAnimation(currentanim);
            }                    
        }
    }    
    
    llSetTimerEvent(0.0);
    ticker = 0;
    animindex = 0;
    faceindex = 0;
}

default
{
    state_entry()
    {
        slavenum = (integer)llList2String(llParseString2List(llGetScriptName(), [" "], []), -1);
    }
    
    link_message(integer sender, integer num, string str, key id)
    {
        if (num - SLAVEOFFSET == slavenum)
        {
            debug(str);
            //we've been given our marching orders
            list params = llParseStringKeepNulls(str, ["|"], []);            

            key newav = (key)llList2String(params, 4);
            list newanims = llParseString2List(llList2String(params, 0), ["~"], []);
            string newanim = llList2String(newanims, 0);
            
            if (newav != av || newanim != currentanim)
            {
                CleanUp();
            }
            
            av = newav;
            debug("av=" + (string)av);
            
            if (av != "")
            {
                anims = newanims;
                animcount = llGetListLength(anims);
                pos = (vector)llList2String(params, 1);
                rot = (rotation)llList2String(params, 2);
                faceanims = llParseString2List(llList2String(params, 3), ["~"], []);     
                facecount = llGetListLength(faceanims);                
                
                avlinknum = AvLinkNum(av);
                if (avlinknum != -1)
                {
                    debug("moving av");
                    MoveLinkedAv(avlinknum, pos, rot);
                    debug("requesting perms");                    
                    llRequestPermissions(av, PERMISSION_TRIGGER_ANIMATION);
                }
            }
        }
        else if (num == SYNC)
        {
            //stop and restart current anim, assuming it's set and we have an av with anim permissions.
            if (llKey2Name(av))
            {
                if (llGetPermissions() & PERMISSION_TRIGGER_ANIMATION)
                {
                    string oldanim = currentanim;
                    ticker = 0;
                    animindex = 0;
                    currentanim = llList2String(anims, animindex);
                    if (currentanim == oldanim)
                    {
                        //we need to make sure the viewer really stops the old anim
                        debug("syncing");
                        llStopAnimation(oldanim);
                        llStartAnimation("sit");
                        llSleep(syncsleep);
                        llStopAnimation("sit");
                        llStartAnimation(currentanim);
                    }               
                    else
                    {
                        llStopAnimation(oldanim);
                        llStartAnimation(currentanim);
                    }     
                }
            }
        }
    }
    
    run_time_permissions(integer perms)
    {
        if (perms & PERMISSION_TRIGGER_ANIMATION)
        {
            debug("got perms");
            //stop existing anim if set 
            if (llGetInventoryType(currentanim) == INVENTORY_ANIMATION)
            {
                debug("stopping " + currentanim);
                llStopAnimation(currentanim);
            }
            
            currentanim = llList2String(anims, 0);
            debug("starting " + currentanim);
            llStopAnimation("sit");
            llStartAnimation(currentanim);
            
            if (facecount)
            {
                faceindex = 0;
                llStartAnimation(llList2String(faceanims, faceindex));
            }
            
            if (animcount > 1 || facecount)
            {
                llSetTimerEvent(1.0);
            }
        }
    }
    
    timer()
    {        
        //check permissions and sanity up here once so we don't duplicate code below
        if (!(llGetPermissions() & PERMISSION_TRIGGER_ANIMATION))
        {
            llResetScript();        
        }
        else if (llKey2Name(llGetPermissionsKey()) == "")
        {
            llResetScript();          
        }
        
    
        if (facecount)
        {
            faceindex++;
            if (faceindex >= facecount)
            {
                faceindex = 0;
            }            
        
            llStartAnimation(llList2String(faceanims, faceindex));
        }
        
        if (animcount > 1)
        {
            //we have a sequence of anims
            ticker++;
            debug((string)ticker);
            if (ticker >= 29)
            {
                ticker = 0;
                animindex++;
                debug("animcount: " + (string)animcount);
                if (animindex < animcount)
                {
                    string newanim = llList2String(anims, animindex);
                    if (newanim == "RESTART")
                    {
                        animindex = 0;
                        newanim = llList2String(anims, animindex);
                    }
                    llStartAnimation(newanim);                                               
                    //stop existing anim if set 
                    if (llGetInventoryType(currentanim) == INVENTORY_ANIMATION)
                    {
                        llStopAnimation(currentanim);
                    } 
                    currentanim = newanim;                               
                }   
                else
                {
                    //no more anims in sequence
                    //stop timer unless we're using it for face anims
                    if (!facecount)
                    {
                        llSetTimerEvent(0.0);
                    }
                }           
            }
        }
    }
    
    changed(integer change)
    {
        if (change & CHANGED_LINK)
        {
            avlinknum = AvLinkNum(av);
            
            if (avlinknum == -1)
            {
                llReleaseControls();    // Avatar unsit release controls no matter what
                llResetScript();
            }
        }
    }
}

nPose adjustment script -- needed in set up

Put this script in an adjuster prim -- the one included in the nPose object is a box, size: x= 0.15, y =0.1, z = 2.0, rotated at 180 on the z axis, and coloured red on faces 2&4, green on faces 1&3, and blue on 0&5 (to correspond with the prim's local axes) but do what you like with it.

Name the prim Adjuster, put this script in it, and put the prim in the nPose object.

/*//( nPose Adjuster 1.27 )//*/
integer chatchannel;

float timeout = 2.0;
vector pos;
rotation rot;

key parent = NULL_KEY;

default
{
    on_rez(integer param)
    {
        if (param)
        {
            chatchannel = param;
            llListen(chatchannel, "", "", "");
            llSetTimerEvent(timeout);
        }
    }

    listen(integer channel, string name, key id, string message)
    {
        //only pay attention of obj owner is my owner
        if (llGetOwnerKey(id) == llGetOwner())
        {
            list params = llParseString2List(message, ["|"], []);
            //prevent crosstalk
            if ((key)llList2String(params, 0) == llGetKey())
            {
                parent = id;                
                string msg = llList2String(params, 1);
                if (msg == "posrot")
                {
                    pos = (vector)llList2String(params, 2);
                    rot = (rotation)llList2String(params, 3);
                    llSetPos(pos);
                    llSetRot(rot);
                }
            }
            else if (message == "adjuster_die")
            {
                llDie();
            }            
        }
    }
    
    timer()
    {
        if (parent != NULL_KEY)
        {
            if (llKey2Name(parent) == "")
            {
                //parent has died.  do likewise
                llDie();
            }
        }
        
        integer chat_out = FALSE;
        if (llGetPos() != pos)
        {
            pos = llGetPos();
            chat_out = TRUE;
        }
        
        if (llGetRot() != rot)
        {
            rot = llGetRot();
            chat_out = TRUE;
        }
        
        if (chat_out)
        {
            llSay(chatchannel, (string)pos + "|" + (string)rot);
        }
    }
}
return to top