Difference between revisions of "TMCP"

From Second Life Wiki
Jump to navigation Jump to search
(TMCP - MLP Compatible Pose Scripts - Open source (BSD license))
 
Line 8: Line 8:
Resident 'Camden McAndrews' voluntueered as new maintainer. I hope the MLPv2 team will pick it up.
Resident 'Camden McAndrews' voluntueered as new maintainer. I hope the MLPv2 team will pick it up.


==Features==
* MLPv2 notecard & plugin compatible
* Fast & Stable operation
* Intelligent menu
* No poseballs
* Plenty memory for larger animation sets
==Limitations==
* No facial animations (yet)
* No properties
(Both limitations are relative easily added, yet not implemented in version 0.37)


==TMCP Sitmanager==
==TMCP Sitmanager==

Revision as of 12:36, 15 August 2012

TMCP - Tano's MLP Compatible Pose scripts

  • a drop in replacement for MLPv2
  • configuration notecard compatible
  • Since august 14. 2012 released under the open source BSD license

Collection of scripts as of version 0.37 below, or available on marketplace for your convenience https://marketplace.secondlife.com/p/TMCP-Tanos-MLP-Compatible-Pose-Scripts-Source-Edition/2964611

Resident 'Camden McAndrews' voluntueered as new maintainer. I hope the MLPv2 team will pick it up.


Features

  • MLPv2 notecard & plugin compatible
  • Fast & Stable operation
  • Intelligent menu
  • No poseballs
  • Plenty memory for larger animation sets

Limitations

  • No facial animations (yet)
  • No properties

(Both limitations are relative easily added, yet not implemented in version 0.37)

TMCP Sitmanager

<lsl> //keeps track of sitted target, position them and animate them.

list animPool; //pool of scripts that are able to pose targets

list poolCache; //try re-use same script for same agent, to avoid reasking permissions

list sitted; //strided, holds: AGENT_KEY, logical offset (`ball number`), [animscript?], current animation?, current position? integer sittedStride=3;

list sittedAgents; list sitScripts;

integer MSG_AGENT_UNSIT = 380; integer MSG_ALL_UNSIT =381; integer MSG_DISABLE_SITTING =382; integer MSG_REGISTER_SITSCRIPT =383; integer MSG_QUERY_SITSCRIPT = 385; integer MSG_RESET =0; integer MSG_ENABLE_SITTING=0; integer MSG_SET_SITTARGET=375; integer MSG_PLAY_ANIM=390; integer MSG_STOP_ANIM=391; integer MSG_AGENT_PLAY_ANIM=395; // to notify the slave script integer MSG_AGENT_STOP_ANIM=396; integer MSG_ATTACH_AGENT = 398; integer MSG_DETACH_AGENT = 399; integer MSG_RUN_ANIMS=379;

integer MSG_DO_STOP=353; integer MSG_DO_SWAP=354; integer MSG_MENU_SWAP=355;

integer MSG_MENU_META=333;

integer MSG_MLP_SITTED=-11000; integer MSG_MLP_UNSITTED=-11001;

integer MSG_MODE_BALLUSERS=366;

integer MSG_DO_DEFAULT_POSE = 358;

integer MSG_RLV_GRAB=308; integer MSG_RLV_RELEASE=309; integer MSG_RLV_VICTIM_LIST=307;

integer MSG_DO_MENU_CURRENTMENU = 305;




//keys to controlling: //animname+index is key to both real animation name and it's position.

list agentAnims;

list agentScripts; list agentScriptCache;

list agentPoseIdx;

list usedIndexes; //logical pose positions, register in here.

integer MAX_SITTING=1;

string currentAnim; integer currentAnimSeats;

list menuMeta;

integer mode_ballusers;

key grabberID;

list potentionalVictims; list victimsWithRelay;

list grabbingVictims; //commanded to, but not yet suited list grabbedVictims; //victims tagged as victim, sitted

integer CAPTURE_RANGE = 35; integer RELAY_CHANNEL = -1812221819; integer relay_handle;

integer timer_reason_versioncheck; integer rlv_victim_dialog_handle; integer rlv_victim_dialog_channel;

integer timer_versioncheck=50; integer timer_closechannel=51; integer timer_closechannel2=53; integer timer_defaultpose=52; integer timer_cleargrabbing=55;


assignScript (key id) {

   integer i=llListFindList (agentScripts, [id]);
   if (i>=0) //agent already got assignment
       return;    
   i=llListFindList (agentScriptCache, [id]);
   string candidate;
   if (i>=0) {
       candidate=llList2String(agentScriptCache, i+1);
       if (-1!=llListFindList(agentScripts, [candidate]))
           candidate="";
   }
   if (candidate);else {
       //try to find a new one
       for (i=0; i<llGetListLength(sitScripts); i++) {
           string ss=llList2String(sitScripts, i);
           if (-1==llListFindList(agentScripts, [ss])) {//found free one
               candidate=ss;   
               jump break;
           }
       }
       @break;        
   }
   if (candidate) {
       agentScripts+=[id, candidate];
       i=llListFindList (agentScriptCache, [candidate]);
       if (-1<i) 
           agentScriptCache=llDeleteSubList(agentScriptCache, i-1, i);    
       agentScriptCache+=[id, candidate];
       //llOwnerSay ((string)id+" attached to "+candidate);
       llMessageLinked(LINK_THIS, MSG_ATTACH_AGENT, candidate, id);
   } else {
       llRegionSayTo (id, 0, "Sorry, but there is no available script to animate your avatar.");
   }

}

integer assignPoseIdx (key id) {

   //start counting at 1.. just because we can.
   integer i=llListFindList (agentPoseIdx, [id]);
   if (-1<i)
       return llList2Integer(agentPoseIdx, i+1); //agent already got a pose assigned
   for (i=1; i<100; i++) {
       if (-1==llListFindList(agentPoseIdx, [i])) //found empty one
       {
           agentPoseIdx+=[id, i];   
           //llInstantMessage (id, "You are assigned to pose index "+(string)i);
           return i;
       }
   }
   return 0;

}

dropAgent (key id) {

   //clean up stuff agent had
      

}

stopAnims(key id) {

   llMessageLinked (LINK_THIS, MSG_AGENT_STOP_ANIM, "", id);

}

applyAnims() {

   //apply animation (and position?) for all agents involved
   
   //send list of keys with indexes to position. it should know which anim is current.
   llMessageLinked (LINK_THIS, MSG_RUN_ANIMS, llList2CSV(agentPoseIdx), "");
   //llOwnerSay ("anims/poses "+llList2CSV(agentPoseIdx));
      

}

cleanUp(key id) {

   stopAnims(id);
   integer i=llListFindList (agentScripts, [id]);
   if (-1<i)
       agentScripts = llDeleteSubList (agentScripts, i, i+1);
   i=llListFindList (agentPoseIdx, [id]);
   if (-1<i)
       agentPoseIdx = llDeleteSubList (agentPoseIdx, i, i+1);
   i=llListFindList (grabbedVictims, [id]);
   if (-1<i)
       grabbedVictims = llDeleteSubList (grabbedVictims, i, i);
          

}

setSitTarget() {

 //needs pose info.. so we calculate the first available index, and match info with current pose.. it's for position script, i think.
   //linkedmessage (setsittarget, [posename,] index)..    
   //posename might be cached by positioner and/or set to defaults.
   integer idx=1;
   //find first available
   while (-1<llListFindList(agentPoseIdx, [idx]))
       idx++;
   if (idx>MAX_SITTING) {
       llSitTarget (ZERO_VECTOR, ZERO_ROTATION/llGetRot());   
   } else {
       llSitTarget (<0,0,0.01>, ZERO_ROTATION/llGetRot()); //backup option.
       llMessageLinked(LINK_THIS, MSG_SET_SITTARGET, (string)idx, "");
   }
   

}

integer maySit(key id) {

   if (!mode_ballusers) //everyone
       return TRUE;
   if (id==llGetOwner()) //always
       return TRUE;
   if (mode_ballusers==1) //group
       return llSameGroup(id);
   return FALSE; //nope

}

broadcastVictims() {

   llMessageLinked (LINK_THIS, MSG_RLV_VICTIM_LIST, llList2CSV(grabbedVictims), "");   

}

requestMenu(key id) {

   llMessageLinked (LINK_THIS, MSG_DO_MENU_CURRENTMENU, "", id);      

}

inventarise() {

   //someone sitted or unsitted. do the bookkeeping
   list agents;
   integer i;
   
   //get sitted avatars
   for (i=0; i<=llGetNumberOfPrims(); i++) {
       key id=llGetLinkKey(i);
       if (llGetAgentSize(id)) {
           //this is an agent..
           agents+=id;
           if (-1!=llListFindList(grabbingVictims, [id])) {
               //grabbingVictims=[]; //should really do this somewhere else
               if (-1==llListFindList(grabbedVictims, [id])) {
                   rlv_lock(id);
                   grabbedVictims+=id;
                   broadcastVictims();
               }
           }   
       }
   }
   
   //now cross reference against existing agents
   //first see, who's all gone
   for (i=0;i<llGetListLength(sittedAgents); i++) {
       key id=llList2Key(sittedAgents, i);
       if (-1==llListFindList(agents, [id])) {
           //agent unsitted, remove him
           msgUnsit(id, llList2Integer(agentPoseIdx, llListFindList(agentPoseIdx, [id])+1));
           if (-1!=llListFindList(grabbedVictims, [id])) {
               //rlv catch, unlock it
               rlv_release(id);   
           }
           cleanUp(id);                           
           broadcastVictims();            
       }
   }
   
   //now, lets see who's all added
   for (i=0;i<llGetListLength(agents); i++) {
       key id=llList2Key(agents, i);
       if (-1==llListFindList(sittedAgents, [id])) {
           //freshy added, assign it a script, if possible, and a logical offset
           if (maySit(id)) {
               assignScript(id);
               msgSit(id, assignPoseIdx(id));
           } else {
               llUnSit(id);
               llWhisper (0, "Sorry "+llKey2Name(id)+" but you cannot use this object.");
           }
           //play anim 
           
       }
   }

// if (sittedAgents != agents) // setSitTarget();

   sittedAgents = agents;
   setSitTarget();
   applyAnims();
   
   // default pose switching on idle
   if (agents==[])
       scheduleEvent (timer_defaultpose, 120, "");
   else
       cancelEvent (timer_defaultpose);

}

makeSwapMenu(key id, integer channel) {

   //offer nice swap menu
   //list sitted avatars
   string text="";
   integer i;
   integer m=10;
   list buttons=["<BACK"];
   if (MAX_SITTING<m)
       m=MAX_SITTING;
   for (i=1; i<=m; i++) {
       integer j=llListFindList(agentPoseIdx, [i]);
       string agent = "<Nobody>";
       key aid;
       if (-1<j) {
           aid=llList2Key(agentPoseIdx, j-1);
           agent=llKey2Name(aid);
       }
       text+=(string)i + ". " + llList2String(menuMeta, i) + " "+agent+"\n";
                  
       if (aid!=id)
           buttons+=["SWAP::"+(string)i];
   }
   llDialog (id, text, buttons, channel);       

}

//consideration: send out sit/unsit events or not.. i think, might as well not. in order not to confuse rlv. swapAgent (key id, integer newpos) {

   //llSay (0, "Swapping "+llKey2Name(id)+" to position #"+(string)newpos);
   integer o=llListFindList(agentPoseIdx, [id]);
   if (o<0) {
       //llOwnerSay ("swap key not found "+(string)id);
       //agent not found. see if we have a rlv target
       if (llGetListLength(grabbedVictims)) {
           //just default to first best sitted. Stuff gets complex with multiple RLV but who cares that.
           id=llList2Key(grabbedVictims,0);
           o=llListFindList(agentPoseIdx, [id]);
           if (o<0) //OUCH, should nevah happen
               return;            
       }        
       else return;
   }
   integer oldidx=llList2Integer(agentPoseIdx, o+1);
   if (oldidx==newpos) {
       llRegionSayTo (id, 0, "You choosen the same position");
       return;
   }
   agentPoseIdx=llDeleteSubList(agentPoseIdx, o, o+1);
   integer topos=llListFindList(agentPoseIdx, [newpos]);
   if (-1<topos) {
       agentPoseIdx=llDeleteSubList(agentPoseIdx, topos-1, topos) + [llList2Key(agentPoseIdx, topos-1),oldidx];        
   }
   agentPoseIdx += [id, newpos];
   applyAnims();

}

unsitAll() {

   integer i;
   for (i=0; i<=llGetNumberOfPrims(); i++) {
       key id=llGetLinkKey(i);
       if (llGetAgentSize(id))
           llUnSit(id);    
   }   

}

msgSit(key id, integer poseidx) {

   //we start count at 1, mlp at zero. fix right here
   llMessageLinked(LINK_THIS, MSG_MLP_SITTED, (string)(poseidx-1), id);   

}

msgUnsit(key id, integer poseidx) {

   //we start count at 1, mlp at zero. fix right here
   llMessageLinked(LINK_THIS, MSG_MLP_UNSITTED, (string)(poseidx-1), id);   

}

log(string t){

   //llOwnerSay(t);

}

RLVGrabMenu(key id) {

   grabberID = id;
   llSensor("", NULL_KEY, AGENT, CAPTURE_RANGE, TWO_PI);    

// llInstantMessage (id, "Scanning ... menu in 5.. 4.. 3.."); }

RLVReleaseAll() {

   integer i;
   for (i=0; i<llGetListLength(grabbedVictims); i++) {
       key id=llList2Key(grabbedVictims,i);
       rlv_release (id);
       llWhisper (0,"releasing "+llKey2Name(id));
       //unsit?
       llUnSit(id);
   }
   grabbedVictims=[];

}

rlv_checkversion (key id) {

   rlv_relay(id, "!version=" + (string)1);    //version_handle

}


rlv_capture(key avatar) {

   rlv_relay(avatar, "@sit:" + (string)llGetKey() + "=force");
   //llSay (menu_ban_channel, "- "+(string)avatar);    
   llWhisper (0, llKey2Name(grabberID)+" captures "+llKey2Name(avatar));

}


rlv_lock(key avatar) {

   rlv_relay(avatar, "@unsit=n");

}

rlv_release(key avatar) {

   rlv_relay(avatar, "@unsit=y");
   rlv_relay(avatar, "!release");
   //llSay (menu_ban_channel, "- "+(string)avatar);    

}

// write a message to the RLV Relay rlv_relay(key avatar, string message) {

   if (avatar != NULL_KEY)
   {
       llSay(RELAY_CHANNEL, llEscapeURL(llGetObjectName()) + "," + (string) avatar + "," + message);
       log("RLV: " + llGetObjectName() + "," + (string) avatar + "," + message);
   }

}

//scheduler

list events = []; integer nextEvent = 0;

scheduleEvent(integer id, integer time, string data) {

   //adjust timestamp
   time+=llGetUnixTime();
   
   //cancel any previous requests, this case we only want one per type, really
   cancelEvent(id);    
   
   events = llListSort(events + [time, id, data], 3, TRUE);
   setTimer(FALSE);

}

cancelEvent(integer id) {

   //reasonable failsafe as long as you don't use id's that could be current timestamp.    
   integer i=llListFindList(events, [id]);
   if ((-1<i) && ((i % 3) == 1)) {
       //it was an id
       events=llDeleteSubList(events, i-1, i+1);
       
       //recurse when found, delete all with said id... ?
       cancelEvent(id);
       
       setTimer(FALSE);
   }

}

integer setTimer(integer executing) {

   if ((events != []) > 0) { // Are there any list items?
       integer id = llList2Integer(events, 1);

       integer time = llList2Integer(events, 0);
       nextEvent = id;

       float t = (float)(time - llGetUnixTime());
       if (t <= 0.0) {
           if (executing) return TRUE;
           else t = 0.01;
       }
       llSetTimerEvent(t);
   } else {
       llSetTimerEvent(0.0);
       nextEvent = -1;
   }
   return FALSE;

}

handleEvent(integer id, string data) {

   if (id==timer_versioncheck) {
           //time to display our dialog
           if ([]==victimsWithRelay) {
               llRegionSayTo (grabberID, 0, "No eligible victims found (they must wear a relay).");
               requestMenu(grabberID);
           } else {
               list nn;
               integer i;
               for (i=0; (i<llGetListLength(victimsWithRelay)) && (i<23); i+=2) {
                   string s=llKey2Name(llList2Key(victimsWithRelay, i));
                   if (s)
                       nn+=llGetSubString(s,0,23);                     
               }
               
               llListenControl(rlv_victim_dialog_handle, TRUE);          
               scheduleEvent (timer_closechannel, 90, (string)rlv_victim_dialog_handle);
               llDialog (grabberID, "Choose a victim", nn, rlv_victim_dialog_channel);
               //scheduleEvent (timer_versioncheck, 5, "");                 
           }  
           //set timer to new timeout
           
           //llSetTimerEvent(TIMER_TIMEOUT);
           //llListenControl (version_handle, FALSE);            
           //return;   
   }
   else
   if ((id==timer_closechannel) || (id==timer_closechannel2)) {
       llListenControl ((integer)data, FALSE);            
   }
   else
   if (id==timer_defaultpose) {
       llMessageLinked(LINK_THIS, MSG_DO_DEFAULT_POSE, "", "");        
   }
   else
   if (id==timer_cleargrabbing) {
       grabbingVictims=[];   
   }
   

}


upgrade() {string self = llGetScriptName(); string basename = self; if (llSubStringIndex(self, " ") >= 0) {integer start = 2; string tail = llGetSubString(self, llStringLength(self) - start, -1); while (llGetSubString(tail, 0, 0) != " ") {start++; tail = llGetSubString(self, llStringLength(self) - start, -1);} if ((integer)tail > 0) {basename = llGetSubString(self, 0, -llStringLength(tail) - 1);}} integer n = llGetInventoryNumber(INVENTORY_SCRIPT); while (n-- > 0) {string item = llGetInventoryName(INVENTORY_SCRIPT, n); if (item != self && 0 == llSubStringIndex(item, basename)) {llRemoveInventory(item);}}}

default {

   state_entry()
   {
       upgrade();
       llOwnerSay ("sitmanager memory free : "+(string)llGetFreeMemory());
       state active;  
   }

}

state active {

   state_entry() {
       setSitTarget();
       llMessageLinked (LINK_THIS, MSG_QUERY_SITSCRIPT, "", "");
       
       rlv_victim_dialog_channel = 124904342+llFloor(llFrand(42902543));
       rlv_victim_dialog_handle = llListen(rlv_victim_dialog_channel, "", "", "");
       llListenControl (rlv_victim_dialog_handle, FALSE); 
       relay_handle=llListen(RELAY_CHANNEL, "", "", "");
       llListenControl(relay_handle, FALSE);   
   } 
   link_message(integer sn, integer n, string m, key id) {
       
       if (n==MSG_RESET)
           llResetScript();
       else
       if (n==MSG_ENABLE_SITTING) {
           MAX_SITTING=llGetListLength(sitScripts);
           setSitTarget();
       }
       else
       if (n==MSG_AGENT_UNSIT) {
           //unsit agent                
           llUnSit(id);
       }
       else
       if (n==MSG_ALL_UNSIT) {
           //just kick everyone
           unsitAll();    
       }
       else
       if (n==MSG_DISABLE_SITTING) {
           //state inactive;
           MAX_SITTING=0;
           setSitTarget();
       }        
       
       else
       if (n==MSG_REGISTER_SITSCRIPT) {
           if (m)
           if (INVENTORY_SCRIPT==llGetInventoryType(m))
           if (-1==llListFindList(sitScripts, [m])) {
               sitScripts+=m;    
               MAX_SITTING=llGetListLength(sitScripts);
           }
       }
       
       else
       if (n==MSG_PLAY_ANIM) {
           currentAnim=m;
           applyAnims();   
       }
       else
       if (n==MSG_MENU_SWAP) {
           makeSwapMenu (id, (integer)m);   
       }
       else
       if (n==MSG_DO_SWAP) {
           swapAgent(id, (integer)m);   
       }
       else
       if (n==MSG_MENU_META) {
           menuMeta=llParseString2List(m, ["|", " "], []);   
       }
       else
       if (n==MSG_DO_STOP) {
           llUnSit(id); //let changed event handle the rest   
       }
       else
       if (n==MSG_MODE_BALLUSERS) {
           mode_ballusers=(integer)m;   
       }
       else
       if (n==MSG_RLV_GRAB) {
           RLVGrabMenu(id);       
       }
       else
       if (n==MSG_RLV_RELEASE) {
           RLVReleaseAll();
       }
       
   }
   
   changed (integer c) {
       if (c & CHANGED_LINK) {
           //don't even bother about getting sitted or not or leaving..
           //just fetch all current avatars and match them against our list of known avis
           inventarise();   
       }
       
   }
   
   timer() {
       // Clear timer or it might fire again before we're done
       llSetTimerEvent(0.0);

       do {
           // Fire the event
           handleEvent(nextEvent, llList2String(events, 2));

           // Get rid of the first item as we've executed it
           integer l = events != [];
           if (l > 0) {
               if (l > 3)
                   events = llList2List((events = []) + events, 3, -1);
               else events = [];
           }

           // Prepare the timer for the next event
       } while (setTimer(TRUE));
   }    
   
   /*
   timer() {
       /* default pose.. do somewhere else please
       llSetTimerEvent(0.);
       llMessageLinked(LINK_THIS, MSG_DO_DEFAULT_POSE, "", "");   
       */
       /*
   }
   */
   
   no_sensor()
   {        
       llRegionSayTo (grabberID, 0, "No nearby potentional victims found");
       requestMenu(grabberID);
   }

   // some av in sensor range
   sensor(integer total_number)
   {
       //llListenControl (rlv_victim_dialog_handle, TRUE);
       //scheduleEvent (timer_closechannel, 120, (string)rlv_victim_dialog_handle);
       llListenControl (relay_handle, TRUE);
       scheduleEvent (timer_closechannel2, 7, (string)relay_handle);
       
       
       potentionalVictims=[];
       victimsWithRelay=[];
       integer i;
       for(i=0; i < total_number; i++)
       {
           key id = llDetectedKey(i);
           string name = llKey2Name(id);
           if (llStringLength(name) > 24)
           {
               name = llGetSubString(name, 0, 23);
           }
           potentionalVictims+=[id, name];
           rlv_checkversion(id);            
       }

       // show dialog if list contains names
       /*
       if (llGetListLength(sensor_names) > 0)
       {
           llListenControl(dialog_handle, TRUE);
           llSetTimerEvent(TIMER_TIMEOUT);
           llDialog(avatar_menu, MSG_CHOOSE_AV, sensor_names, dialog_channel);
       }
       */
       scheduleEvent (timer_versioncheck, 5, (string)grabberID);
       llRegionSayTo (grabberID, 0, "Verifying "+(string)total_number+" potentional victims. Menu in 5.. 4.. 3..");          }    
   
   listen (integer ch, string n, key id, string m) {
       if (ch==RELAY_CHANNEL) {
           if (-1!=llSubStringIndex(m, "version=")) {//bit dirty, we might catch other objects too
               //victims+=llGetOwnerKey(id);
               //victim selected, grab it   
               victimsWithRelay+=[llGetOwnerKey(id),llGetSubString(llKey2Name(llGetOwnerKey(id)),0,23)];                
           }            
       }
       else
       if (ch==rlv_victim_dialog_channel) {
           //victim selected, grab it
           integer i=llListFindList (victimsWithRelay, [m]);
           if (-1<i) {
               key vid=llList2Key(victimsWithRelay, i-1);
               rlv_capture (vid);
               if (-1==llListFindList(grabbingVictims, [vid])) {
                   grabbingVictims+=vid;
               }
               scheduleEvent (timer_cleargrabbing, 120, (string)id);                
           }
           requestMenu(grabberID);   
       }
       
   }

}

/* state inactive {

   state_entry() {
       llSitTarget(ZERO_VECTOR, ZERO_ROTATION);
   }   
   link_message(integer sn, integer n, string m, key id) {
       if (n==MSG_RESET)
           llResetScript();
       else
       if (n=MSG_ENABLE_SITTING)
           state active;
   }    

}

  • /

</lsl>

TMCP - pose

<lsl> key ourAgent; string currentAnim;

integer hasPerms;

integer MSG_AGENT_PLAY_ANIM=395; // to notify the slave script integer MSG_AGENT_STOP_ANIM=396; integer MSG_ATTACH_AGENT = 398; integer MSG_DETACH_AGENT = 399; integer MSG_REGISTER_SITSCRIPT =383; integer MSG_QUERY_SITSCRIPT = 385; integer MSG_SYNC_ANIM=361;

integer MSG_ADJUST_POSROT=520; integer MSG_TEMPORARY_POSROT=521;

integer MSG_MODE_ADJUST=368;


vector o=ZERO_VECTOR; vector r=ZERO_VECTOR; integer hastimer;

integer is_adjusting; integer cursormode;


stopAnim() {

   if (hasPerms) if (currentAnim)
       llStopAnimation(currentAnim);   

}

runAnim() {

   if (hasPerms) if (currentAnim)
       llStartAnimation(currentAnim);

}

integer checkPerms() {

   if (llGetPermissionsKey()!=ourAgent) {
       hasPerms = FALSE;
       return FALSE;   
   }
   hasPerms=(PERMISSION_TAKE_CONTROLS|PERMISSION_TRIGGER_ANIMATION) == ((PERMISSION_TAKE_CONTROLS|PERMISSION_TRIGGER_ANIMATION) & llGetPermissions());
   return hasPerms;

}

queryPerms() {

   if (!checkPerms()) {
       //must query them
       llRequestPermissions(ourAgent, PERMISSION_TAKE_CONTROLS|PERMISSION_TRIGGER_ANIMATION);   
   } else {
       stopDefAnims();
       if (currentAnim)
           llStartAnimation(currentAnim);
       getControls();   
   }

}

stopDefAnims() {

   //easy way would be:
   //llStopAnimation("sit");   
   //let's go for the hard way
   list a=llGetAnimationList(ourAgent);
   integer i;
   for (i=0; i<llGetListLength(a); i++)
       llStopAnimation(llList2Key(a,i));

}

register() {

   llMessageLinked (LINK_THIS, MSG_REGISTER_SITSCRIPT, llGetScriptName(), "");   

}

getControls() {

   llTakeControls (CONTROL_UP|CONTROL_DOWN|CONTROL_LEFT|CONTROL_RIGHT|CONTROL_FWD|CONTROL_BACK|CONTROL_ROT_LEFT|CONTROL_ROT_RIGHT, TRUE, FALSE);
   //llInstantMessage (ourAgent, "Adjust your position with PGUP, PGDOWN and cursorkeys. Shift rotates.");   
   whispHelp(ourAgent);

}

parseControls (integer level, integer edge) {

   integer l = level; //hold down
   
   if ((CONTROL_UP|CONTROL_DOWN)==(l & edge & (CONTROL_UP | CONTROL_DOWN))) {
       cursormode=!cursormode;
       whispHelp(ourAgent);
       return;   
   }
   
   if (is_adjusting) {
       integer cm=cursormode;
       if (l & edge & CONTROL_LEFT) 
           cursormode--;
       if (l & edge & CONTROL_RIGHT)
           cursormode++;
       if (cursormode<1)
           cursormode=3;
       if (cursormode>3)
           cursormode=1;
       if (cm!=cursormode)
           whispHelp(ourAgent);        
   }


   if (cursormode==1) {//fine adjust    
       if (l & CONTROL_FWD) o+=<0,2,0>;
       if (l & CONTROL_BACK) o+=<0,-2,0>;
       if (l & CONTROL_ROT_LEFT) o+=<-2,0,0>;
       if (l & CONTROL_ROT_RIGHT) o+=<2,0,0>;
       if (l & CONTROL_UP) o+=<0,0,2>;
       if (l & CONTROL_DOWN) o+=<0,0,-2>;
   }
   else
   if (cursormode==2) {//90 degrees rotation    
       if (l & edge & CONTROL_FWD) r+=<0,90,0>;
       if (l & edge & CONTROL_BACK) r+=<0,-90,0>;
       if (l & edge & CONTROL_ROT_LEFT) r+=<0,0,-90>;
       if (l & edge & CONTROL_ROT_RIGHT) r+=<0,0,90>;
       if (l & edge & CONTROL_UP) r+=<90,0,0>;
       if (l & edge & CONTROL_DOWN) r+=<-90,-0,0>;
   }
   else
   if (cursormode==3) {//fine rotation    
       if (l & CONTROL_FWD) r+=<0,1,0>;
       if (l & CONTROL_BACK) r+=<0,-1,0>; 
       if (l & CONTROL_ROT_LEFT) r+=<0,0,-1>;
       if (l & CONTROL_ROT_RIGHT) r+=<0,0,1>;
       if (l & CONTROL_UP) r+=<1,0,0>;
       if (l & CONTROL_DOWN) r+=<-1,0,0>;
   }
   
   //if (l & edge & CONTROL_LEFT) r += <0,0,90>;
   //if (l & edge & CONTROL_RIGHT) r += <0,-90,0>;    
       
   if (!hastimer) {
       llSetTimerEvent (0.25);
       hastimer=TRUE;
   }

}

whispHelp (key id) {

   if (!is_adjusting) {
       if (!cursormode) {
           llRegionSayTo (id, 0, "Press PAGE-UP and PAGE-DOWN simultanious to fine adjust position");
       } else {
           llRegionSayTo (id, 0, "Use the cursor keys and up/down to adjust position. Changes made are temporary.");
       }        
   } else {
       if (!cursormode)
           llRegionSayTo (id, 0, "Use PAGE-UP and PAGE-DOWN to start adjusting");
       if (1==cursormode)
           llRegionSayTo (id, 0, "Mode: position (fine adjust). Use cursor keys and up/down to adjust position. Shift-left or shift-right to change mode");
       if (2==cursormode)
           llRegionSayTo (id, 0, "Mode: rotation (90 degrees) . Use cursor keys and up/down to adjust rotation. Shift-left or shift-right to change mode");
       if (3==cursormode)
           llRegionSayTo (id, 0, "Mode: rotation (fine adjust). Use cursor keys and up/down to adjust rotation. Shift-left or shift-right to change mode");
   }

}

upgrade() {string self = llGetScriptName(); string basename = self; if (llSubStringIndex(self, " ") >= 0) {integer start = 2; string tail = llGetSubString(self, llStringLength(self) - start, -1); while (llGetSubString(tail, 0, 0) != " ") {start++; tail = llGetSubString(self, llStringLength(self) - start, -1);} if ((integer)tail > 0) {basename = llGetSubString(self, 0, -llStringLength(tail) - 1);}} integer n = llGetInventoryNumber(INVENTORY_SCRIPT); while (n-- > 0) {string item = llGetInventoryName(INVENTORY_SCRIPT, n); if (item != self && 0 == llSubStringIndex(item, basename)) {llRemoveInventory(item);}}}


default {

   state_entry()
   {
       //upgrade();
       register();
   }
   link_message(integer sn, integer n, string m, key id) {
       if (n==MSG_ATTACH_AGENT) {
           if (m==llGetScriptName()) {
               if (ourAgent)
                   stopAnim();
               currentAnim="";   
               ourAgent = id;
               queryPerms();
           }
       }
       else
       if (n==MSG_DETACH_AGENT) {
           if (ourAgent==id) {
               llReleaseControls();
               stopAnim();
           }
           ourAgent=NULL_KEY;
           currentAnim="";   
       }
       else
       if (n==MSG_AGENT_PLAY_ANIM) {
           if (ourAgent==id) {
               stopAnim();
               currentAnim=m;
               runAnim();
           }
       }
       else
       if (n==MSG_AGENT_STOP_ANIM) {
           if (ourAgent==id) {
               stopAnim();
               currentAnim="";    
           }   
       }
       else
       if (n==MSG_SYNC_ANIM) {
           if (ourAgent) {
               stopAnim();
               runAnim();
           }   
       }
       else
       if (n==MSG_QUERY_SITSCRIPT) {
           register();
           //llOwnerSay ("Registering");   
       }
       else
       if (n==MSG_MODE_ADJUST) {
           is_adjusting = (integer)m;
           if (!is_adjusting)
               cursormode=0;
           else
               cursormode=1;
           if (id==ourAgent)
               whispHelp(id);   
       }
   }
   
   run_time_permissions(integer p) {
       if (checkPerms()) {
           stopDefAnims();
           if (currentAnim)
               llStartAnimation(currentAnim);   
           getControls();
       } 
   }
   
   control (key id, integer level, integer edge) {
       if (id!=ourAgent)
           return;
       parseControls (level, edge);   
   }
   
   timer () {
       llSetTimerEvent (0.);
       hastimer=FALSE;
       if (ZERO_VECTOR!=(o+r)) {
           if (is_adjusting)
               llMessageLinked (LINK_THIS, MSG_ADJUST_POSROT, (string)(0.005*o)+"&"+(string)r, ourAgent);    
           else
               llMessageLinked (LINK_THIS, MSG_TEMPORARY_POSROT, (string)(0.005*o)+"&"+(string)r, ourAgent);            
       }
       o=ZERO_VECTOR;
       r=ZERO_VECTOR;
       
   }
   

}

</lsl>

TMCP - positions

<lsl> //basically, we need a large datastore for our command set list poseNames; list poseData;

list positionNames; list positionData;


integer MSG_DATA_MENU=310; integer MSG_DATA_TYPE=320; integer MSG_DATA_POSE=330; integer MSG_DATA_POSITION=331; integer MSG_DATA_LM=335;

integer MSG_DO_POSE=350; integer MSG_DO_LM=351; integer MSG_DO_SPECIAL=352;

integer MSG_SET_RUNNING=390; integer MSG_STORAGE_RESET=391;

integer MSG_DATA_READY=309;

integer MSG_RUN_ANIMS=379;

integer MSG_AGENT_PLAY_ANIM=395;

integer MSG_SET_SITTARGET=375;

integer MSG_DUMP_POSITIONS=389; integer MSG_UPDATE_POS=363;

integer MSG_DO_DEFAULT_POSE = 358; integer MSG_SET_DEFAULT_POSE = 359;

integer MSG_MLP_SETTING = 1;

integer MSG_ADJUST_POSROT=520; integer MSG_TEMPORARY_POSROT=521;


string currentPose="default"; list currentAnims; list currentPositions;

list agentPoseIdx;

list agentAdjust;


string defaultPose="default";

runAnims() {

   list data=agentPoseIdx;
   integer i;
   list idpos;
   for (i=0; i<llGetListLength(data); i+=2) {
       key id=(key)llList2String(data, i);
       integer idx=-1+(integer)llList2String(data, i+1);
       if (id) if (idx>=0) {
           if (idx>=llGetListLength(currentAnims))
               llWhisper (0, "No animation available for "+llKey2Name(id));
           else {
               llMessageLinked (LINK_THIS, MSG_AGENT_PLAY_ANIM, llList2String(currentAnims, idx), id);
               //llOwnerSay ("Playing anim "+llList2String(currentAnims, idx)+" on id "+(string)id);
           }
               
           if (idx/2>=llGetListLength(currentPositions))
               llSay (0, "No position data for "+llKey2Name(id));   
           else {
               //set agents position
               idpos+=[id, (vector)llList2String(currentPositions, idx*2),(vector)llList2String(currentPositions, idx*2+1)];
           }
       } else {
           //llOwnerSay ("Corrupt agent anim position request");   
       }
   }    
   //apply positions
   for (i=0; i<=llGetNumberOfPrims(); i++) {
       integer j=llListFindList(idpos, [llGetLinkKey(i)]);
       if (-1<j) {
           vector pos = llList2Vector(idpos, j+1)+(<0,0,0.42+0.01*(integer)llGetObjectDesc()>/llGetRot());
           vector rot = llList2Vector(idpos, j+2) * DEG_TO_RAD;
           integer l=llListFindList (agentAdjust, [llGetLinkKey(i)]);
           if (-1<l) {
               pos+=llList2Vector (agentAdjust, l+1);
               rot=llRot2Euler(llEuler2Rot(llList2Vector (agentAdjust, l+2)) * llEuler2Rot(rot));                   
           }
           llSetLinkPrimitiveParamsFast(i, [PRIM_POS_LOCAL, pos,PRIM_ROT_LOCAL, llEuler2Rot(rot)]);
           //llOwnerSay ("Setting position for "+llKey2Name(llGetLinkKey(i))+" position: "+(string)pos+" rotation: "+ (string)rot );
       }        
   }

}

setSitTarget(integer poseidx) {

   poseidx--;
   vector v=(vector)llList2String(currentPositions, poseidx*2);
   vector r=(vector)llList2String(currentPositions, poseidx*2+1);
   llSitTarget (v+(<0,0,0.42+0.01*(integer)llGetObjectDesc()>/llGetRot()) , llEuler2Rot(r*DEG_TO_RAD));
   //llOwnerSay ("Sit target #"+(string)poseidx+" "+(string)(v+(<0,0,0.42>/llGetRot()))+" "+(string)(r));

}

string shortString (float n) {

   string s=(string)n;
   while (llStringLength(s)>1) {
       integer hasdot=-1!=llSubStringIndex(s, ".");
       string d=llGetSubString(s,-1,-1);
       if ((hasdot && (d=="0")) || (d=="."))
           s=llDeleteSubString(s,-1,-1);
       else
           jump ok;   
   }
   @ok;
   return s;

}

string shortVec (vector v) {

   return "<"+shortString(v.x)+","+shortString(v.y)+","+shortString(v.z)+">";

}

dumpPos() {

   llOwnerSay ("TMCP Position Dump");
   integer i;
   for (i=0; i<llGetListLength(positionNames); i++) {
       //llOwnerSay ("{"+llList2String(positionNames, i)+"} "+llList2String(positionData, i));    
       list v=llParseString2List(llList2String(positionData, i), ["&"], []);
       string s="{"+llList2String(positionNames, i)+"}";
       integer j;
       for (j=0; j<llGetListLength(v); j++)
           //notice that function shortvec takes string as param
           s+=" "+shortVec((vector)llList2String(v, j));        
       llOwnerSay (s);
   }   

}

doPose (string m) {

   if (m!=currentPose)
       agentAdjust=[];
       
           currentPose=m;
           currentAnims=[];
           currentPositions=[];
           integer i=llListFindList(positionNames, [m]);
           integer j=llListFindList(poseNames, [m]);
           currentAnims=llParseString2List (llList2String(poseData, j), ["|"], []);
           currentPositions=llParseString2List (llList2String(positionData, i), ["&"], []);
           
           //fill missing data with defaults
           i = llListFindList(positionNames, ["default"]);
           j = llListFindList(poseNames, ["default"]);
           list animdef=llParseString2List (llList2String(poseData, j), ["|"], []);
           list posdef=llParseString2List (llList2String(positionData, i), ["&"], []);
           currentAnims+=llList2List (animdef, llGetListLength(currentAnims), -1);
           currentPositions+=llList2List(posdef, llGetListLength(currentPositions), -1);            
           
           //this should be obsolete now:
           if (/*(i>=0) && */(j>=0)) {
               //list l=llParseString2List(llList2String(lmAction, i), [","],[]);
               //parse pose data. todo.
               if (i<0) {
                   //need to fetch default data... just try it.
                   i=llListFindList(positionNames, ["default"]);
               }
               //llOwnerSay ("Do pose "+m+" anims "+llList2String(poseData,j)+" data "+llList2String(positionData, i));
           } else {                                
               //llOwnerSay ("No animations defined for "+m);
           }
           
           runAnims();  

}

setAgentPosRot (key id, string data) {

   //set temporary agent adjustment.
   list l=llParseString2List(data, ["&"], []);
   vector p=(vector)llList2String(l,0);
   vector r=DEG_TO_RAD * (vector)llList2String(l,1);
   integer i=llListFindList (agentAdjust, [id]);
   if (-1<i) {
       p+=llList2Vector(agentAdjust, i+1);
       r=llRot2Euler(llEuler2Rot(llList2Vector(agentAdjust, i+2))*llEuler2Rot(r));        
       agentAdjust = llDeleteSubList(agentAdjust, i, i+2);
   }
   if (llVecMag(p)>1)
       p=llVecNorm(p);
   agentAdjust+=[id,p,r];       
   runAnims();

}

adjustPosition (string name, integer index, vector p, vector r) {

                             //positionNames
   integer i=llListFindList (positionNames, [name]); 
   if (-1<i) {
       string data=llList2String(positionData, i);
       list pos=llParseString2List(data, ["&"], []);
       if (index>0) {
           //alter only index   
           integer j=(index-1)*2;
           if (j<llGetListLength(positionData)) {
               vector v=(vector)llList2String(pos, j);   
               v+=p;
               vector R0=(vector)llList2String(pos, j+1);   
               rotation R1=llEuler2Rot(DEG_TO_RAD *R0);
               rotation Rd=llEuler2Rot(DEG_TO_RAD * r);
               R1 = R1 * Rd;
               R0=RAD_TO_DEG * llRot2Euler(R1);                
               pos=llListReplaceList(pos, [shortVec(v),shortVec(R0)], j, j+1);                             
           }
           
       } else {
           //alter all
           integer j;
           for (j=0; j<llGetListLength(pos); j+=2) {
               vector v=(vector)llList2String(pos, j);   
               v+=p;
               vector R0=(vector)llList2String(pos, j+1);   
               rotation R1=llEuler2Rot(DEG_TO_RAD *R0);
               rotation Rd=llEuler2Rot(DEG_TO_RAD * r);
               R1 = R1 * Rd;
               R0=RAD_TO_DEG * llRot2Euler(R1);                
               pos=llListReplaceList(pos, [shortVec(v),shortVec(R0)], j, j+1);                
           }               
       }
       data=llDumpList2String(pos, "&");
       //llOwnerSay ("new pos data for "+name+" : "+data);
       positionData=llListReplaceList (positionData, [data], i, i);
          
   }

}

adjustAllPositions (vector p, vector r) {

   //loop all pose, adjust them
   integer i;
   for (i=0; i<llGetListLength(positionNames); i++) {
       adjustPosition (llList2String(positionNames, i),-1, p,r);
   }   

}

parseSetting (string m, key id) {

   //seperated by =
   //might contain ROT or POS adjustments
   list l=llParseString2List(m, [" = ", " =", "=", "= ","="],[]);
   string cmd=llList2String(l, 0);
   string cmd2=llList2String(l,1);
   if (cmd=="REORIENT") {
       vector v=(vector)llList2String(l,2);
       /*
       list lv=llParseString2List(llList2String(l,2), ["<",",",">"], []); //rotations come as vector too
       llOwnerSay ("P: "+llList2String(l,2)+" : "+llDumpList2String(lv, "*"));
       v.x=(float)llList2String(lv,0);
       v.y=(float)llList2String(lv,1);
       v.z=(float)llList2String(lv,2);
       */
       
       llOwnerSay ("adjust "+cmd2+" "+(string)v);
       if (cmd2=="OFF") {
           adjustAllPositions (0.01*v, ZERO_VECTOR);    
       } else
       if (cmd2=="ROT") {
           adjustAllPositions (ZERO_VECTOR,v);    
       }
       
       doPose(currentPose);
       //runAnims();
   }    

}

adjustPosRot (key id, string data) {

   list l=llParseString2List(data, ["&"], []);
   integer i=llListFindList (agentPoseIdx, [(string)id]);
   //llOwnerSay ("posrot "+(string)i+" "+(string)id+" "+data);
   if ((-1<i)&&(llGetListLength(l)==2)) {
       integer lidx=llList2Integer (agentPoseIdx, i+1);
       adjustPosition (currentPose, lidx, (vector)llList2String(l,0), (vector)llList2String(l,1));
       doPose(currentPose);        
   }

}


upgrade() {string self = llGetScriptName(); string basename = self; if (llSubStringIndex(self, " ") >= 0) {integer start = 2; string tail = llGetSubString(self, llStringLength(self) - start, -1); while (llGetSubString(tail, 0, 0) != " ") {start++; tail = llGetSubString(self, llStringLength(self) - start, -1);} if ((integer)tail > 0) {basename = llGetSubString(self, 0, -llStringLength(tail) - 1);}} integer n = llGetInventoryNumber(INVENTORY_SCRIPT); while (n-- > 0) {string item = llGetInventoryName(INVENTORY_SCRIPT, n); if (item != self && 0 == llSubStringIndex(item, basename)) {llRemoveInventory(item);}}}

default {

   state_entry()
   {
       upgrade();
   }
   link_message (integer sn, integer n, string m, key id) {
       if (n==MSG_DATA_POSE) {
           //add posedata    
           poseNames+=m;
           poseData+=(string)id;
       } else
       if (n==MSG_DATA_POSITION) {
           positionNames+=m;
           positionData+=(string)id;            
       } else
       if (n==MSG_SET_DEFAULT_POSE) {
               defaultPose=m;
       }
       else
       if (n==MSG_SET_RUNNING)
           state running;
   }

}

state running {

   state_entry()
   {
       llOwnerSay ("Position free memory: "+(string)llGetFreeMemory());
       doPose(defaultPose);
   }
   link_message (integer sn, integer n, string m, key id) {
       if (n==MSG_DO_POSE) {
           doPose(m);
       } else
       if (n==MSG_STORAGE_RESET)
           llResetScript();
       else
       if (n==MSG_RUN_ANIMS) {
           agentPoseIdx=llCSV2List(m);
           runAnims();    
       }
       else
       if (n==MSG_SET_SITTARGET) {
           setSitTarget((integer)m);    
       }
       else
       if (n==MSG_DUMP_POSITIONS) {
           dumpPos();
       }
       else
       if (n==MSG_UPDATE_POS) {
           runAnims();       
       }
       else
       if (n==MSG_SET_DEFAULT_POSE)
           defaultPose=m;
       else
       if (n==MSG_DO_DEFAULT_POSE)
           doPose(defaultPose);
       else
       if (n==MSG_MLP_SETTING) {
           parseSetting (m, id);   
       }
       else
       if (n==MSG_ADJUST_POSROT) {
           adjustPosRot(id, m);      
       }
       if (n==MSG_TEMPORARY_POSROT) {
           setAgentPosRot(id, m);      
       }
   }
   

} </lsl>

TMCP - actions

<lsl> //basically, we need a large datastore for our command set list lmNames; list lmAction;

integer MSG_DATA_MENU=310; integer MSG_DATA_TYPE=320; integer MSG_DATA_POSE=330; integer MSG_DATA_LM=335;

integer MSG_DO_POSE=350; integer MSG_DO_LM=351; integer MSG_DO_SPECIAL=352;

integer MSG_DO_SOUND=356;

integer MSG_SET_RUNNING=390; integer MSG_STORAGE_RESET=391; integer MSG_DATA_READY=309;


upgrade() {string self = llGetScriptName(); string basename = self; if (llSubStringIndex(self, " ") >= 0) {integer start = 2; string tail = llGetSubString(self, llStringLength(self) - start, -1); while (llGetSubString(tail, 0, 0) != " ") {start++; tail = llGetSubString(self, llStringLength(self) - start, -1);} if ((integer)tail > 0) {basename = llGetSubString(self, 0, -llStringLength(tail) - 1);}} integer n = llGetInventoryNumber(INVENTORY_SCRIPT); while (n-- > 0) {string item = llGetInventoryName(INVENTORY_SCRIPT, n); if (item != self && 0 == llSubStringIndex(item, basename)) {llRemoveInventory(item);}}}

default {

   state_entry()
   {
       upgrade();
   }
   link_message (integer sn, integer n, string m, key id) {
       if (n==MSG_DATA_LM) {
           //add action    
           lmNames+=m;
           lmAction+=(string)id;
       } else
       if (n==MSG_SET_RUNNING)
           state running;
   }

}

state running {

   state_entry()
   {
       llOwnerSay ("Action manager free memory : "+(string)llGetFreeMemory());    
   }    
   link_message (integer sn, integer n, string m, key id) {
       if (n==MSG_DO_LM) {
           integer i=llListFindList(lmNames, [m]);
           if (i>=0) {
               list l=llParseString2List(llList2String(lmAction, i), [","],[]);
               integer set=(integer)llList2String(l,1);    
               integer num=(integer)llList2String(l,2);    
               //string mess=llStringTrim(llList2String(l,3),STRING_TRIM);
               string mess=llDumpList2String(llList2List(l,3,-1), ",");
               integer nomenu=(integer)llList2String(l,0);    
               //perform requested action
               llMessageLinked (set, num, mess, id);
               
               if (!nomenu) //this will fail when not found, which it shouldnt.
                   llMessageLinked (LINK_THIS, 370, "", id);
           }   
       } else
       if (n==MSG_DO_SOUND) {
           //LM list was abused for sound info
           integer i=llListFindList(lmNames, [m]);
           if (i>=0)
               llTriggerSound(llList2String(lmAction, i), 1.);
       }
       else
       if (n==MSG_STORAGE_RESET)
           llResetScript();
   }
   

} </lsl>

TMCP - menurender

<lsl> string version = "TMCP v0.3x";

//Render menu

list menuNames; list menuEntries; //menus that appear in 'auto' menu list menuDetails;

//some hardcoded stuff that this script adds, not any menu. prefill. list itemNames=["<<<",">>>","<BACK"]; list itemTypes=[7,7,7];

list menuHistory; string currentMenu; string currentPose="default"; string defaultPose="default";


integer channel=999; integer listenhandle;

integer mode_chat=1; integer mode_adjust=0; integer mode_menu=3; //user integer mode_ball=0; //all

integer hibernate;

list rlvVictims;


integer TYPE_MENU=1; integer TYPE_POSE=2; integer TYPE_LM_NOMENU=3; integer TYPE_LM_MENU=4; integer TYPE_IGNORE=5; integer TYPE_SPECIAL=6; integer TYPE_UNKNOWN=7; integer TYPE_SOUND=8;


integer MSG_DATA_MENU=310; integer MSG_DATA_MENU_DETAILS=311;

integer MSG_DATA_TYPE=320; integer MSG_DATA_POSE=330; integer MSG_DATA_POSITION=331; integer MSG_DATA_LM=335;

integer MSG_DO_POSE=350; integer MSG_DO_LM=351; integer MSG_DO_SPECIAL=352;

integer MSG_DO_STOP=353; integer MSG_DO_SWAP=354; integer MSG_MENU_SWAP=355; integer MSG_DO_SOUND=356;

integer MSG_SET_RUNNING=390; integer MSG_STORAGE_RESET=391; integer MSG_DATA_READY=309;

integer MSG_DATA_START_READ=301;

integer MSG_MENU_META=333;

integer MSG_DUMP_POSITIONS=389; integer MSG_MODE_ADJUST=368; integer MSG_MODE_CHAT=367; integer MSG_MODE_BALLUSERS=366; integer MSG_MODE_OFF=365;

integer MSG_UPDATE_POS=363; integer MSG_ALL_UNSIT =381;

integer MSG_SET_DEFAULT_POSE = 359; integer MSG_DO_DEFAULT_POSE = 358;

integer MSG_RLV_GRAB=308; integer MSG_RLV_RELEASE=309; integer MSG_RLV_VICTIM_LIST=307;

integer MSG_DO_MENU_CURRENTMENU = 305;

//incoming integer MSG_DO_MENU_COMMAND = -12002;

verbose(string text) {

   //llOwnerSay (text);
   if (mode_chat)
       llSay (0, text);

}

string onOff(integer value) {

   if (value) return "On"; else return "Off";   

}

string aclMode(integer value) {

   if (0==value)
       return "ALL";
   else
   if (1==value)
       return "GROUP";
   else
   if (2==value)
       return "OWNER";
   else
   if (3==value)
       return "USER";
   else
       return "UNKNOWN";

}

reset(integer resetSelf) {

   integer i;
   for (i=0; i<llGetInventoryNumber(INVENTORY_SCRIPT); i++) {
       string s=llGetInventoryName(INVENTORY_SCRIPT, i);
       if (-1<llSubStringIndex(s, "~TMCP ")) {
           if (s!=llGetScriptName()) {
               llResetOtherScript(s);    
           }   
       }
   }
   if (resetSelf)
       llResetScript();        

}

setRunning(integer running) {

   integer i;
   for (i=0; i<llGetInventoryNumber(INVENTORY_SCRIPT); i++) {
       string s=llGetInventoryName(INVENTORY_SCRIPT, i);
       if (-1<llSubStringIndex(s, "~TMCP ")) {
           if (s!=llGetScriptName()) {
               llSetScriptState (s, running);
           }   
       }
   }

}


integer hasDialogAccess(key id) {

   //OWNER ALWAYS
   if (id==llGetOwner())
       return TRUE;
       
   if (-1<llListFindList(rlvVictims, [(string)id]))
       return FALSE;
       
   //ALL
   if ((!mode_menu)/* || (id==llGetOwner())*/)
       return TRUE; //little speed optimalization to avoid call to samegroup
       
   //USER
   if (mode_menu==3) {
       //user must be sitted to access, or no-one sitted at all
       integer someSit=FALSE;
       integer i;
       for (i=0; i<=llGetNumberOfPrims(); i++) {
           if (llGetLinkKey(i)==id)
               return TRUE;
           if (llGetAgentSize(llGetLinkKey(i)))
               someSit=TRUE;   
       }
       return (!someSit);
   }
   
   //GROUP
   return (mode_menu==1) && llSameGroup(id);

}

integer hasMenuAccess (key id, string menu) {

   //supposed to be trimmed already?
   if (id==llGetOwner())
       return TRUE;
   string access=
       llStringTrim(
           llList2String(
               llParseString2List(
                   llList2String(menuDetails, 1+llListFindList(menuDetails, [menu]) )
               , ["|"], [])
           , 0)
       , STRING_TRIM);
   if (access=="OWNER") {
       return id==llGetOwner();
   }
   if (access=="GROUP") {
       return (id==llGetOwner()) || llSameGroup(id);        
   }
   if (access=="USER") {
       integer i;
       for (i=0; i<=llGetNumberOfPrims(); i++)
           if (llGetLinkKey(i)==id)
               return TRUE;
       return FALSE;   
   }
   //defaults to all.. learn to edit notecards if you made an error.
   //future options might be a name or some. dunnow.
   return TRUE;

}

makeMenu(string name, key id) {

   if (id); else return; //might be invoked by remote scripts. can't pop menu's to none-avi's.
   
   //present user a fine menu   
   integer idx=0;
   
   integer offset;
   
   if ((name==">>>") || (name=="<<<")) {
       //offset making
       //rename to the last found menu
       /*
       if (name==">>>")
           offset+=10;
       if (name=="<<<")
           offset-=10;                   
       */
       menuHistory+=name; //next step will fix this
       //llOwnerSay ("history: "+(string)menuHistory);
   }
   
   integer ho=llGetListLength(menuHistory)-1;
   while ((ho>1) && (-1<llListFindList(["<<<", ">>>"], [llList2String(menuHistory, ho)])) ) {
       //adjust offset
       string s=llList2String(menuHistory, ho);
       if (s=="<<<")
           offset-=10;
       if (s==">>>")
           offset+=10;
       name=llList2String(menuHistory, ho-1);
       ho--;            
   }
   
       
   if (name) {
       idx=llListFindList(menuNames, [name]);
       if (idx<0) {            
           //llOwnerSay ("No menu entry for "+name);
           return;
       }
       
   } else {
       //make it main menu
       name=llList2String(menuNames, 0); //assume at least 1 menu item please.. else its bugged anyways.
       menuHistory=[];
   }
   currentMenu=name;
       
   integer i=llListFindList (menuHistory, [name]);
   if (i>=0) {
       //truncate rest from history, maybe we went back.
       menuHistory = llList2List(menuHistory, 0, i);   
   } else 
       menuHistory += name;


   list present=llCSV2List(llList2String(menuEntries, idx));
   if (llAbs(offset)>=(llGetListLength(present))) offset=0;
                               
   integer o=offset;
   while (o>0) {
       menuHistory += ">>>";
       o-=10;   
       if (o<0)
           o=0;
   }
   while (o<0) {
       menuHistory += "<<<";
       o+=10;   
   }
   
   string h=">>";
   for (i=0; i<llGetListLength(menuHistory); i++)
       h+="> "+llList2String(menuHistory,i)+" ";
   
   
   if (currentPose)
       h+="\n\nCurrent pose: "+currentPose;
   //todo somewhere. some better wrap around.
   
   
   if ((offset) || (llGetListLength(present)>12))
       present = llList2List(present, offset, offset+8) + ["<<<", ">>>", "BACK"];        
   
   if (llGetListLength(rlvVictims)) {
       h+="\nCaptured (RLV): ";
       for (i=0; i<llGetListLength(rlvVictims); i++)
           h+=llKey2Name((key)llList2String(rlvVictims, i))+"\n";
   }    
   
   //got our filled up menu here? prolly need to sort it, for now just present it
   //llOwnerSay (name+":"+llList2CSV(present));
   
   //invert our list
   list p;
   for (i=0; i<12; i+=3)
       p = p+llList2List (present, -i-3, -i-1);
   
   llDialog (id, version+"\n"+h, p, channel);

}

processCommand (key id, string message) {

       integer typeidx=llListFindList (itemNames, [message]);
       string param;
       integer newMenu=TRUE;
       
       if (typeidx<0) {
           list l=llParseString2List(message, ["::"], []);
           message=llList2String(l,0);
           param=llList2String(l,1);
           typeidx=llListFindList (itemNames, [message]);
           //llOwnerSay ("Message "+message+" got param "+param);            
       }
       
       if (typeidx<0) {
           //llOwnerSay ("Error - no menu entry for "+message);
           return;    
       }
       
          
       integer type=llList2Integer(itemTypes, typeidx);
       //llOwnerSay ("Type for "+message+" is "+(string)type);
       if (type==TYPE_MENU) {
           if (hasMenuAccess(id, message)) {
               makeMenu(message, id);           
               newMenu=FALSE;                
           } else {
               llRegionSayTo (id, 0, "Access to menu "+message+" is denied.");
               //makeMenu(currentMenu, id);                   
               //return;
           }
       }
       else
       if (type==TYPE_POSE) {
           llMessageLinked (LINK_THIS, MSG_DO_POSE, message, "");
           currentPose=message;
           llMessageLinked (LINK_THIS, MSG_MENU_META,llList2String(menuDetails, 1+llListFindList(menuDetails, [currentMenu])), "");            
       }
       else
       if (type==TYPE_LM_NOMENU) {
           llMessageLinked (LINK_THIS, MSG_DO_LM, message, id);
           newMenu=FALSE;
       }
       else
       if (type==TYPE_LM_MENU) {
           llMessageLinked (LINK_THIS, MSG_DO_LM, message, id);
       }
       else
       if (type==TYPE_SOUND) {
           llMessageLinked (LINK_THIS, MSG_DO_SOUND, message, id);               
       }
       else
       //some built in messages
       if (type=TYPE_SPECIAL) {
       
           if  (message=="SWAP") {
               
               if ((integer)param) {            
                   llMessageLinked (LINK_THIS, MSG_DO_SWAP, param, id);
               }
               else {
                   llMessageLinked (LINK_THIS, MSG_MENU_SWAP, (string)channel, id);             
                   newMenu=FALSE;
               }
           }
           else
   
           if  (message=="STOP") {
               llMessageLinked (LINK_THIS, MSG_DO_STOP, "", id);
               newMenu=FALSE;
           }
           else
               
           if  (message=="BACK") {
               //check menu history
               string newM="";
               integer i=llGetListLength (menuHistory)-1;            
               while ((i>0) && (-1<llListFindList(["<<<", ">>>"], [llList2String(menuHistory, i)]))) i--;
               if (i>0) {
                   menuHistory=llList2List(menuHistory, 0, i);
                   newM=llList2String(menuHistory,i-1);                        
               }
               makeMenu(newM, id);
               newMenu=FALSE;
           }
           else
           if ((message=="<<<") || (message==">>>")) {
               //previous and next. let menumaker solve this.
               makeMenu(message, id);
               newMenu=FALSE;
           }
           else
           if (message=="<BACK") {
               //makeMenu(currentMenu, id);
           }
           else
           //anything we don't know off for some reason. include it anyways.
           if (message=="DUMP") {
               llMessageLinked (LINK_THIS, MSG_DUMP_POSITIONS, "", "");                   
           }
           else
           if (message=="CHAT") {
               mode_chat=!mode_chat;
               llMessageLinked (LINK_THIS, MSG_MODE_CHAT, (string)mode_chat, id);
               llRegionSayTo (id, 0, "Chat "+onOff(mode_chat));   
           }
           else
           if (message=="ADJUST") {
               mode_adjust=!mode_adjust;
               llMessageLinked (LINK_THIS, MSG_MODE_ADJUST, (string)mode_adjust, id);                
               llRegionSayTo (id, 0, "Adjusting : "+onOff(mode_adjust));   
           }
           else
           if (message=="MENUUSERS") {
               mode_menu++;
               if (mode_menu>3)
                   mode_menu=0;
               llRegionSayTo (id, 0, "Menu access set to "+aclMode(mode_menu));                   
           }
           else
           if (message=="BALLUSERS") {
               mode_ball++;
               if (mode_ball>2)
                   mode_ball=0;
               llRegionSayTo (id, 0, "Usage access set to "+aclMode(mode_ball));                   
               llMessageLinked (LINK_THIS, MSG_MODE_BALLUSERS, (string)mode_ball, id);                                
           }
           else
           if (-1<llListFindList (["RESET", "RESTART", "RELOAD"], [message])) {
               llSay (0, "Reset sequence started... Touch me again in a few seconds to reload the configuration.");
               reset(TRUE);   
               newMenu=FALSE;
           }
           else
           if (message=="Z") {
               if ("+"==llGetSubString(param,0,0))
                   param=llGetSubString(param,1,-1);                
               llSetObjectDesc((string)((integer)llGetObjectDesc()+(integer)param)+llGetSubString(llGetObjectDesc(), llStringLength((string)((integer)llGetObjectDesc())), -1));
               llMessageLinked (LINK_THIS, MSG_UPDATE_POS, (string)mode_ball, id);                
           }
           else
           if (message=="OFF") {
               state Off;
               newMenu=FALSE;
           }
           else
           if (message=="SAVE") {
               defaultPose=currentPose;
               llMessageLinked (LINK_THIS, MSG_SET_DEFAULT_POSE, currentPose, "");
  
           }
           else
           if (message=="GRAB") {
               llMessageLinked (LINK_THIS, MSG_RLV_GRAB, "", id);
               newMenu=FALSE;                               
           }
           else
           if (message=="RELEASE") {
               llMessageLinked (LINK_THIS, MSG_RLV_RELEASE, "", id);                            
           }
           else {
               //forward to whoever handles it
               llMessageLinked (LINK_THIS, MSG_DO_SPECIAL, message, id);            
           }
       }  
                 
       if (newMenu)
           makeMenu(currentMenu, id);            

}


upgrade() {string self = llGetScriptName(); string basename = self; if (llSubStringIndex(self, " ") >= 0) {integer start = 2; string tail = llGetSubString(self, llStringLength(self) - start, -1); while (llGetSubString(tail, 0, 0) != " ") {start++; tail = llGetSubString(self, llStringLength(self) - start, -1);} if ((integer)tail > 0) {basename = llGetSubString(self, 0, -llStringLength(tail) - 1);}} integer n = llGetInventoryNumber(INVENTORY_SCRIPT); while (n-- > 0) {string item = llGetInventoryName(INVENTORY_SCRIPT, n); if (item != self && 0 == llSubStringIndex(item, basename)) {llRemoveInventory(item);}}}

default {

   state_entry()
   {
       upgrade();
       state WaitTouch;
   }

}

state WaitTouch {

   state_entry()
   {
       reset(FALSE);
       llSay (0,"TMCP resetted - Touch me to load the configuration");
   }    
   touch_start (integer n) {
       llSay (0,"TMCP startup - reading data");
       state reading_data;   
   }

}


state reading_data {

   state_entry() {
       llMessageLinked (LINK_THIS, MSG_DATA_START_READ, "", "");   
   }
   link_message (integer sn, integer n, string m, key id) {
       if (n==MSG_DATA_MENU) {
           //add action    
           menuNames+=m;
           menuEntries+=(string)id;
       } else
       if (n==MSG_DATA_MENU_DETAILS) {
           //add action    
           menuDetails+= [m, (string)id];
       } else
       
       if (n==MSG_DATA_TYPE) {
           //set type
           itemNames+=m;
           itemTypes+=(integer)((string)id);   
       }
       if (n==MSG_SET_RUNNING)
           state running;
   }

}

state running {

   state_entry()
   {
       llOwnerSay ("Menu ready. "+(string)llGetFreeMemory());
       channel=-(integer)llFrand(1309123)-492345;
       listenhandle=llListen (channel, "", "", "");
   }
   
   touch_start(integer total_number)
   {
       key id=llDetectedKey(0);
       if (!hasDialogAccess(id)) {
           if (mode_chat)
               llWhisper (0, "Sorry "+llKey2Name(id)+", but menu usage is limited to "+aclMode(mode_menu));
           return;   
       }        
       makeMenu("",id);
   } 
   
   listen (integer ch, string name, key id, string message) {
       
       if (!hasDialogAccess(id)) {
           if (mode_chat)
               llWhisper (0, "Sorry "+llKey2Name(id)+", but menu usage is limited to "+aclMode(mode_menu));
           return;   
       }
       
       if (hibernate) {
           setRunning(TRUE);
           hibernate=FALSE;   
       }
       
       //llSetTimerEvent (3600.);
       
       processCommand (id, message);
       
   }
   
   link_message (integer sn, integer n, string m, key id) {
       if (n==MSG_DO_MENU_COMMAND) {
           processCommand (id, m);    
       }   
       else
       if (n==MSG_DO_DEFAULT_POSE) {
           currentPose = defaultPose;   
       }
       else
       if (n==MSG_RLV_VICTIM_LIST) {
           rlvVictims=llCSV2List(m);   
       }
       else
       if (n==MSG_DO_MENU_CURRENTMENU) {
           makeMenu(currentMenu, id);   
       }
   }
   
   timer() {
       //inactivity detected, sleep all other scripts.        
       llSetTimerEvent(0.);
       hibernate=TRUE;
       setRunning(FALSE);   
   }

}

state Off {

   state_entry() {
       llMessageLinked (LINK_THIS, MSG_ALL_UNSIT, "", "");    
       llWhisper (0,"Switching off in 10... 9...");
       llSleep (5.0);
       setRunning(FALSE);        
       llSleep (5.0);
       llSitTarget(ZERO_VECTOR, ZERO_ROTATION);        
   }   
   
   touch_start(integer n) {
       key id=llDetectedKey(0);
       if (id==llGetOwner())
           state running;
       if ((mode_menu==1) && llSameGroup(id))
           state running;
       llRegionSayTo (id, 0, "Sorry, but this object is switched OFF. Ask the owner to turn it on.");
   }
   
   state_exit() {
       setRunning(TRUE);
       hibernate=FALSE; 
       llWhisper (0, "Switching on");
         
   }
   

} </lsl>

TMCP - notecard reader

<lsl> //read MLP notecards //see how much memory headroom we got after for other stuff

integer notecardidx; key notecardkey; integer notecardtype; integer notecardline; string notecardname; list positions; list tempMenu; list menuNames; list menuEntries; //menus that appear in 'auto' menu list toMenu; //menus with pointers list linkedMsg;

integer hasReset=FALSE; //indicates if there is a reset option in *some* menu. If absent, we add it for safety.


integer TYPE_MENU=1; integer TYPE_POSE=2; integer TYPE_LM_NOMENU=3; integer TYPE_LM_MENU=4; integer TYPE_IGNORE=5; integer TYPE_SPECIAL=6; integer TYPE_UNKNOWN=7; integer TYPE_SOUND=8;

integer MSG_DATA_MENU=310; integer MSG_DATA_MENU_DETAILS=311; integer MSG_DATA_TYPE=320; integer MSG_DATA_POSE=330; integer MSG_DATA_POSITION=331; integer MSG_DATA_LM=335;

integer MSG_DO_POSE=350; integer MSG_DO_LM=351; integer MSG_DO_SPECIAL=352;

integer MSG_DO_STOP=353; integer MSG_DO_SWAP=354;

integer MSG_SET_RUNNING=390; integer MSG_STORAGE_RESET=391; integer MSG_DATA_READY=309;

integer MSG_DATA_START_READ=301;

verbose(string text) {

   llOwnerSay (text);

}

startNotecard() {

   integer i;
   notecardline=0;    
   for (;notecardidx<llGetInventoryNumber(INVENTORY_NOTECARD); notecardidx++) {                
       //loop until we found appropiate card
       notecardname=llGetInventoryName(INVENTORY_NOTECARD, notecardidx);
       if (llGetSubString(notecardname,0,9)==".MENUITEMS") {
           //neato. go fetch it
           notecardtype=1;
           notecardkey=llGetNotecardLine(notecardname, 0);
           verbose ("Reading menu from "+notecardname);
           return;            
       }
       if (llGetSubString(notecardname,0,9)==".POSITIONS") {
           //neato. go fetch it
           notecardtype=2;
           notecardkey=llGetNotecardLine(notecardname, 0);
           verbose ("Reading positions from "+notecardname);
           return;            
       }     
       verbose ("Skipping "+notecardname);
       
   }
   //still here? then we'r done
   //verbose ("Done reading all notecards");
   postFixMenu();
   sendMenuItems();
   llSleep (2.0);
   llMessageLinked (LINK_THIS, MSG_SET_RUNNING, "", ""); //set to running
   verbose ("Ready.");
   //llOwnerSay ("Notecard reader free memory: "+(string)llGetFreeMemory());

}

addPose (string name, list data) {

   //add pose info.. link to position info exactly when again?   
   //positions+=[name, data];
   llMessageLinked (LINK_THIS, MSG_DATA_POSE, name, llDumpList2String(data, "|"));       

}

addMenuDetails (string name, list data) {

   //add menu details.. access, color of balls.
   llMessageLinked (LINK_THIS, MSG_DATA_MENU_DETAILS, name, llDumpList2String(data, "|"));       

}


addLM (string name, string data) {

   llMessageLinked (LINK_THIS, MSG_DATA_LM, name, data);   

}

setType (string name, integer type) {

   llMessageLinked (LINK_THIS, MSG_DATA_TYPE, name, (string)type);       

}

parseMenu() {

   //parse our temporary menu to a real menu;   
   //this is where the real stuff happens.
   integer i;
   string menuName=""; //MLP 1 compatability: don't add stuff to menu as long as menu name is empty. add poses though.
   list options;
   for (i=0; i<llGetListLength(tempMenu); i++) {
       string data=llList2String(tempMenu, i);
       integer posspace=llSubStringIndex(data, " ");
       string cmd=data;
       string param="";
       string option="";
       if (posspace>0) {
           cmd=llGetSubString(data,0,posspace-1);
           param=llGetSubString(data, posspace+1, -1);   
       }
       list paramsTmp=llParseString2List(param, ["|"], []);
       integer j;
       list params;
       for (j=0; j<llGetListLength(paramsTmp); j++)
           params+=llStringTrim(llList2String(paramsTmp, j), STRING_TRIM);
       //params are now nice seperated on |, still allowed to contain spaces in the middle but trimmed from the outside.
       string p1=llList2String(params, 0);
       string p2=llList2String(params, 1);
       string p3=llList2String(params, 2);
       //got our set of command and optional parameters now. let's test them.
       if (cmd=="MENU") {
           addMenuDetails (p1, llDeleteSubList(params,0,0));
           //menuEntries+=p1;
           menuName=p1; //additional params hint the number of available seats            
           setType (p1, TYPE_MENU);  
       } else
       if (cmd=="POSE") {
           addPose (p1, llDeleteSubList(params,0,0));   
           option = p1;
           setType (p1, TYPE_POSE);
       } else
       if (cmd=="SOUND") {
           addLM (p1, p2);   
           option = p1;
           setType (p1, TYPE_SOUND);
       } else
       if (-1 != llListFindList (["BACK", "STOP","SWAP","RESET","RESTART", "MENUUSERS", "BALLUSERS","ADJUST", "DUMP", "CHAT", "RELOAD", "OFF"], [cmd])) {
           //recognized simple options
           option = cmd;   
           setType (cmd, TYPE_SPECIAL);
           if (-1!= llListFindList (["RESET","RESTART", "RELOAD"], [cmd]))
               hasReset=TRUE;
       } else
       if (-1 != llListFindList (["SAVE", "CHECK"], [cmd])) {
           //recognized simple options
           option = cmd;   
           setType (cmd, TYPE_IGNORE);
       } else
       if (cmd=="TOMENU") {
           if (p1!="-")
               toMenu+=p1;
           option=p1;   
       } else
       if (cmd=="LINKMSG") {
           //linkedMsg+=[p1,p2];
           addLM (p1, p2);
           option=p1;
           if ((integer)p2)
               setType (p1, TYPE_LM_NOMENU);
           else
               setType (p1, TYPE_LM_MENU);
       } else
       if (-1<llListFindList(["Z+", "Z-"], [llGetSubString(cmd, 0, 1)])) {
           //Z command, we'll rewrite it
           option = "Z::"+llGetSubString(cmd, 1, -1);
           setType ("Z", TYPE_SPECIAL);
       }
       else
       if (-1<llListFindList (["GRAB", "Grab.Fem"], [cmd])) {
           option = "GRAB";
           setType ("GRAB", TYPE_SPECIAL);               
       }
       else
       if (-1<llListFindList (["Grab.Male","RELEASE"], [cmd])) {
           option = "RELEASE";
           setType ("RELEASE", TYPE_SPECIAL);                           
       }
       //todo.. fill in a bunch of MLP commands here, implemented or not
       else
       { //we checked all commands. Anything defined here is considered undefined and we list it just as is.
           option = cmd;
           //don't set type. it may be defined elsewhere.
           //setType (cmd, TYPE_UNKNOWN);            
       }
       if (menuName) if (option)
           options+=option;
   }
   tempMenu=[];
   if (options!=[]) {
       llOwnerSay ("Menu "+menuName +" ["+(string)llGetListLength(options)+"]");
       //menuAll+=options+"!--!";
       //menuAll+=[menuName,llList2CSV(options)];
       menuNames+=menuName;
       menuEntries+=llList2CSV(options);
   }

}

addMenuItem(string data) {

   integer posslash=llSubStringIndex(data, "//");
   if (posslash>=0) {
       data=llDeleteSubString(data, posslash, -1);
   }
   data=llStringTrim(data,STRING_TRIM);
   //llOwnerSay ("menu data: "+data);
   
   if (data=="") //comment or some but not empty line, silently ignore
       return;
   
   if (llGetSubString (data,0,3+1)=="MENU ") {
       //what a surprise, new menu. finish old one.
       parseMenu();
   }
   
   //what's left, add it to our temp menu. We'l parse it later.
   tempMenu+=data;

}

postFixMenu() {

   //on failed configurations - add some default stuff
   if (!llGetListLength(menuNames)) {
       
       llOwnerSay ("Warning - no reset function found in menu cards. Adding menu 'Reset'.");
       //add a default menu with a reset button.
       
       addMenuItem ("MENU Reset...|OWNER");
       addMenuItem ("RESET");
       parseMenu();
   }
   
   
   //dont add mainmenu
   list entries=llDeleteSubList(menuNames,0,0);
   
   integer i;
   
   //llOwnerSay ("PF entries: "+(string)entries);
   //llOwnerSay ("PF tomenu: "+(string)toMenu);    
   
   
   for (i=0; i<llGetListLength(toMenu); i++) {        
       integer j=llListFindList (entries, [llList2String(toMenu, i)]);
       if (j>-1) {
           entries=llDeleteSubList(entries, j, j);
       }        
   }
   //llOwnerSay ("PF entries after: "+(string)entries);
   
   list MainMenu=llCSV2List(llList2String(menuEntries, 0));
   for (i=0; i<llGetListLength(entries); i++) {
       integer j=llListFindList(MainMenu, ["-"]);
       if (j>-1) {
           MainMenu=llListReplaceList(MainMenu, [llList2String(entries, i)], j, j);            
       }   
   }
   
   i=llListFindList(MainMenu, ["-"]);
   while (-1<i) {
       MainMenu=llDeleteSubList(MainMenu, i, i);
       i=llListFindList(MainMenu, ["-"]);               
   }
   
   if (MainMenu)
       menuEntries = llListReplaceList(menuEntries, [llList2CSV(MainMenu)], 0, 0);

}

sendMenuItems() {

   //llOwnerSay ("Applying menu information");
   
   integer i;
   for (i=0; i<llGetListLength(menuNames); i++) {
       llMessageLinked (LINK_THIS, MSG_DATA_MENU, llList2String(menuNames, i), llList2String(menuEntries, i));            
       llSleep (0.25);
   }    
   
   llMessageLinked (LINK_THIS, MSG_DATA_READY, "Done", "TTP");    

}

addPosition(string data) {

   //some memory optimalization possible here in last half.
   integer curlyopen = llSubStringIndex(data, "{");
   integer curlyclose = llSubStringIndex(data, "}");
   if ((curlyopen<0) || (curlyclose<curlyopen))
       return;
   string pose=llGetSubString(data, curlyopen+1, curlyclose-1);
   data=llGetSubString(data, curlyclose+1, -1);
   list posrotsraw=llParseString2List(data, [">"], []);
   //llOwnerSay ("raw pos data "+data+" convert to "+(string)posrotsraw);
   
   list posrots;
   integer i;
   for (i=0; i<llGetListLength(posrotsraw); i++) {
       if (-1<llSubStringIndex (llList2String(posrotsraw,i), "<")) {
           list v=llParseString2List(llList2String(posrotsraw,i), ["<", ",", " "], []);         
           posrots += <(float)llList2String(v,0), (float)llList2String(v,1),(float)llList2String(v,2)>;
       }
   }
   //llOwnerSay ("position data for "+pose+" : "+(string)posrots);
   //positions+=[pose, llList2CSV(posrots)];
   llMessageLinked (LINK_THIS,MSG_DATA_POSITION, pose, llDumpList2String(posrots,"&"));

}


upgrade() {string self = llGetScriptName(); string basename = self; if (llSubStringIndex(self, " ") >= 0) {integer start = 2; string tail = llGetSubString(self, llStringLength(self) - start, -1); while (llGetSubString(tail, 0, 0) != " ") {start++; tail = llGetSubString(self, llStringLength(self) - start, -1);} if ((integer)tail > 0) {basename = llGetSubString(self, 0, -llStringLength(tail) - 1);}} integer n = llGetInventoryNumber(INVENTORY_SCRIPT); while (n-- > 0) {string item = llGetInventoryName(INVENTORY_SCRIPT, n); if (item != self && 0 == llSubStringIndex(item, basename)) {llRemoveInventory(item);}}}


default {

   state_entry() {        
       upgrade();
       //state ReadMenu;           
   }
   
   link_message (integer sn, integer n, string m, key id) {
       if (n==301)
           state ReadMenu;
   }
   

}

state ReadMenu {

   state_entry()
   {
       llMessageLinked (LINK_THIS, MSG_STORAGE_RESET, "", ""); //reset data storage scripts
       llSleep (2.0);
       //fetch first notecard, go on from there
       startNotecard ();
   }
   
   dataserver(key id, string data) {
       //llOwnerSay ("card "+notecardname+" line "+(string)notecardline+" data "+data);
       
       if (id!=notecardkey)
           return; //not for us
           
       if (data==EOF) {
           if (notecardtype==1)
               parseMenu(); //clean up any menu leftovers
           //read next notecard
           notecardidx++;
           startNotecard();
       } else   
       {
           if (notecardtype==1)
               addMenuItem(data);
           else
               addPosition(data);
           notecardkey=llGetNotecardLine(notecardname, ++notecardline);
       }
   }

} </lsl>

TMCP RLV extensions (optional)

<lsl> //Changes by Tano Toll for seamless MLCP compatability, and to dodge 'double locking':

//*sit and unsit events do not trigger an RLV command (master script already does this) //*identifier is changed to object name, url encoded. //*delayed but automatic notecard read on inventory change

// ---------------------------------------------------------------------------------- // MLPV2 Plugin for RLV Script V1.02 // // Use with a notecard named .RLV with the format: // Pose|Ball no.: 0,1...*|@RLV-Command // e.g. // Missionary|1|@unsit=n // stand|*|!release // ---------------------------------------------------------------------------------- // Copyright (c) 2010, Jenni Eales. All rights reserved. // ---------------------------------------------------------------------------------- // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // * The names of its contributors may not be used to endorse or promote products // derived from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. // IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, // INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, // OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY // OF SUCH DAMAGE. // // Changes: // ---------------------------------------------------------------------------------- // Version 1.02 // - use | as seperator // - support multiple lines for one pose // - additional link message for direct call from ball menu // // Version 1.01 // - use ; instead of | as seperator // // Version 1.0 // - first version released // ---------------------------------------------------------------------------------- // // constants string NOTECARD = ".RLV"; // name of config notecard integer DEBUG = FALSE; // switch debug on/off integer RELAY_CHANNEL = -1812221819; // channel for RLV relay float TIMER_SEC = 1800.0; // 30 min. string CMD_RELEASE = "!release"; // release command integer LM_NUMBER = -1819; // number for link message

// internal use key kQuery; integer iLine;

string pose = "stand"; // pose set by the Ball Menu list poses = []; // configured poses list balls = []; // configured balls list commands = []; // configured commands list avatars = []; // avatars sitting on balls

integer wants_init;

// log a message log(string message) {

   if(DEBUG) llOwnerSay(message);

}

// only for testing purposes log_data() {

   log("List:");
   integer total_number = llGetListLength(commands);
   integer i;
   for(i=0; i<total_number; i++)
   {
       log("- " + llList2String(poses, i) + ": (" + llList2String(balls, i) + ") "+ llList2String(commands, i));
   }

}

// write a message to the RLV Relay relay(key avatar, string message) {

   list tokens = llParseString2List(message, ["|"], [""]);
   integer total_number = llGetListLength(tokens);
   integer i;
   for(i=0; i<total_number; i++)
   {
       string token = llList2String(tokens, i);
       log("RLV: MLPV2," + (string) avatar + "," + token);
       llSay(RELAY_CHANNEL, llEscapeURL(llGetObjectName())+"," + (string) avatar + "," + token);
   }

}

// find commands for pose and play it for avatars on ball rlv(string pose) {

   log("command for: " + pose);
   llSetTimerEvent(TIMER_SEC);

   integer total_number = llGetListLength(poses);
   integer i;
   for(i=0; i<total_number; i++)
   {
       if (pose == llList2String(poses, i))
       {
           string ball = llList2String(balls, i);
           log("command for pose: " + pose + " (" + ball + ")");
           if (ball == "*")
           {
               integer b;
               for (b=0; b<6; b++)
               {
                   // get avatar for ball index
                   key avatar = llList2Key(avatars, b);
                   if(avatar != NULL_KEY) relay(avatar, llList2String(commands, i));
               }
           }
           else
           {
               key avatar = llList2Key(avatars, (integer) ball);
               if(avatar != NULL_KEY) relay(avatar, llList2String(commands, i));
           }
       }
   }

}

// new find: pose and ball needs to match integer find_pose(string pose, string ball) {

   integer total_number = llGetListLength(poses);
   integer i;
   for(i=0; i<total_number; i++)
   {
       if (pose == llList2String(poses, i) && ball == llList2String(balls, i))
       {
           return i;
       }
   }
   return -1;

}

// add a pose/ball/command entry add_pose(string pose, string ball, string command) {

   // look for existing entry for pose
   integer found = find_pose(pose, ball);
   if (found == -1)
   {
       // add a new pose
       poses += [pose];
       balls += [ball];
       commands += [command];

       log("Added from " + NOTECARD + ": " + pose + " | " + ball + " | " + command);
   }
   else
   {
       // append to existing pose
       string c = llList2String(commands, found) + "|" + command;
       commands = llListReplaceList(commands, [c], found, found);

       log("Added " + command + " to pose " + pose);
   }

}

// parse and store a line process_line(string line) {

   list tokens = llParseString2List(line, ["  |  ","  | "," |  "," | "," |","| ","|"],[""]);
   if(llGetListLength(tokens) < 3) return;

   string pose = llList2String(tokens, 0);
   string ball = llList2String(tokens, 1);

   integer total_number = llGetListLength(tokens);
   integer i;
   for(i=2; i<total_number; i++)
   {
       add_pose(pose, ball, llList2String(tokens, i));
   }

}

// check if object is in the inventory integer exists_notecard(string notecard) {

   return llGetInventoryType(notecard) == INVENTORY_NOTECARD;

}

// initialize me initialize() {

   log("Initializing...");
   poses = [];
   balls = [];
   commands = [];
   avatars = [NULL_KEY, NULL_KEY, NULL_KEY, NULL_KEY, NULL_KEY, NULL_KEY];

   if (exists_notecard(NOTECARD))
   {
       log("Reading " + NOTECARD + "...");
       iLine = 0;
       kQuery = llGetNotecardLine(NOTECARD, iLine);
   }
   else
   {
       llOwnerSay("Not found: " + NOTECARD );
   }

}

upgrade() {string self = llGetScriptName(); string basename = self; if (llSubStringIndex(self, " ") >= 0) {integer start = 2; string tail = llGetSubString(self, llStringLength(self) - start, -1); while (llGetSubString(tail, 0, 0) != " ") {start++; tail = llGetSubString(self, llStringLength(self) - start, -1);} if ((integer)tail > 0) {basename = llGetSubString(self, 0, -llStringLength(tail) - 1);}} integer n = llGetInventoryNumber(INVENTORY_SCRIPT); while (n-- > 0) {string item = llGetInventoryName(INVENTORY_SCRIPT, n); if (item != self && 0 == llSubStringIndex(item, basename)) {llRemoveInventory(item);}}}


default {

   // on state entry initialize me
   state_entry()
   {
       upgrade();
       
       initialize();
   }

   // reset on Inventory change
   changed(integer change)
   {
       if (change & CHANGED_INVENTORY) {
           wants_init=TRUE;
           llSetTimerEvent (15.0);
       }
   }

   // read a line from notecard
   dataserver(key _query_id, string _data) {
       // were we called to work on notecard?
       if (_query_id == kQuery) {
           // this is a line of our notecard
           if (_data == EOF) {
               if (DEBUG) log_data();
               log(NOTECARD + " read.");
           } else {
                   // increment line count
                   // data has the current line from this notecard
                   if(_data != "") {
                       process_line(_data);
                   }

               // request next line
               // read another line when you can
               iLine++;
               kQuery = llGetNotecardLine(NOTECARD, iLine);
           }
       }
   }

   timer()
   {
       if (wants_init) {
           wants_init = FALSE;
             initialize();        
           llSetTimerEvent (0.0);
           return;    
       }
       
       integer b;
       for (b=0; b<6; b++)
       {
           // get avatar for ball index
           key avatar = llList2Key(avatars, b);

           // if not null send command
           if(avatar != NULL_KEY) relay(avatar, CMD_RELEASE);
       }
       llSetTimerEvent(0.0);
   }

   // link message from MLPV2
   link_message(integer sender_number, integer number, string message, key id)
   {
       /*
       // pose from ball menu selected
       if (number == 0 && message == "POSEB") {
           pose = (string)id;
           rlv(pose);
           return;
       }
       */

       // direct call from ball menu
       // configure in .MENUITEMS with
       // LINKMSG Resctriction | 0,-1,-1819,Restriction
       if (number == LM_NUMBER) {
           //llOwnerSay ("RLV: -- "+message);
           pose = message;
           rlv(pose);
           return;
       }

       // unsit avatar
       if (number == -11001)
       {
           integer ball = (integer) message;
           log("Avatar unsit from ball " + (string) ball +": " + (string) llList2Key(avatars, ball));
           //relay(llList2Key(avatars, ball), CMD_RELEASE);
           avatars = llListReplaceList(avatars, [NULL_KEY], ball, ball);
       }

       // sit avatar
       if (number == -11000)
       {
           list tokens = llParseString2List(message, ["|"], [" "]);
           integer ball = llList2Integer(tokens, 0);
           log("Avatar sit on ball " + (string) ball +": " + (string) id);
           avatars = llListReplaceList(avatars, [id], ball, ball);
           //rlv(pose);
       }
   }

} </lsl>