TMCP
Jump to navigation
Jump to search
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.
A much newer version is available through the Marketplace, https://marketplace.secondlife.com/p/TMCP-Developer-Edition/2964601
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
//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;
}
}
*/
TMCP - pose
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;
}
}
TMCP - positions
//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);
}
}
}
TMCP - actions
//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();
}
}
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");
}
}
TMCP - notecard reader
//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);
}
}
}
TMCP RLV extensions (optional)
//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);
}
}
}