Wiki3DBuilder
LSL Portal | Functions | Events | Types | Operators | Constants | Flow Control | Script Library | Categorized Library | Tutorials |
Introduction
Here a sample of using this tool by a Sloodle fan (Giancarla Loon) for teaching sloodle basics for Teachers:
This had been built following the ideas (but not the code) of similar object "SpatialMap.org (MindMap 3D) v1.0.18" written by Jonny Bee Cioc and Vision Raymaker which was under a CC by,nc,sa license itself
Disclaimer
This is just a POC (Proof of Concept) and it is meant as a very alpha stage prototype I developed during last week. It is not meant to satisfy security and high level coding standards.
In particular the following things are possibly dangerous and might be rethought and made it professionally:
- Using llSetPin using key2int is not good (final solution might have another copy of main script) which BTW will make the rezzing much rapid
- Using key2int to communicate with the rezzed cubes is to say the least quite "optimistic" and might lead to notworking and malfunctioning objects
- Need more sophisticated ways to Lock the object, to Link it in a unique linkset to simplify inventory operations and to avoid that everybody can hack or destroy the mindmap thing
QuickStart
- Build a cube, name it "nodo" and put inside the llSetPin script
- Build another cube, name it Wiki3D_Builder and put inside its content the "nodo" and the main script
- It should work just clicking on this starting cube (try to rez other cubes, to change the type, form etc).
What it is
A tool that helps a user to build rich 3D wiki architectures made up of NODES connected with particles BEAMS
Each NODE can be customized by changing its NAME, COLOR, SIZE, and FORM
Each NODE can contain:
- 1 Notecard
- 1 Landmark
- 1 Object
- 1 Script
- 1 Texture
- URL that can be given
The network is completely open to the participation of a group of people who can change the network, the position of each node, and freely add or remove portions of the graph.
Being a Wiki there is no limitation on who can do what. If you don't want that the work can be changed further, please set to NOT RUNNING the scripts. To save the mindmap you need to select all the nodes and manually link them together or take into inventory as an aggregate (a coalesced object).
How it works
Initially you have a NODE (i.e., a cube) which must be touched to start the process
- Touch object to see a menu with these options: color, name, size, content, move, rez
- Select "color" to enter a color on the local chat
- Select "name" to enter a name on the local chat
- Select "size" to enter a size on the local chat
- Select "rez" to rez a new node; when choosing this option, you must touch one of the faces of the cube and a new node will be rezzed 50cm on that direction.
- Select "move" and then touch a face to pull the node towards in direction
- Any user can move objects, textures and so from their inventory to a node (CTRL-Dragging them).
- Contents: Only one type of inventory item can be in the node at one time, and that content can be given to whom is asking for content
- Changing the texture will also change the texture of the node
Internal architecture
This is done in ONLY one script named "Main Script" and an internal object named "nodo" containing a llSetPin script. The llSetPin is just a way for setting the pin to a number depending on the Owner of the object so it is NOT easy to hack. The "Main Script" is implementing a quite interesting mechanism for dealing with "multi-threading", i.e. multiple avatars touching the object at the same time. This is accomplished using an Array of RECORDs representing the waiting avatars and what they were waiting for. So listen(), touch(), and other events can query this QUEUE and understand what is to be made next. This also allows for an interesting handling of MULTI MENU A part from this the rest is relatively simple, except for the communication between the various nodes.
- A node communicates with its children for example to propagate the DELETE message
- A node communicates with its parent to emit the particles connector
- A node receives messages from its parent to llDie and with alert when other related objects are changing UUID the latter happens when objects are re-rezzed from inventory
To accomplish this object listens to a channelPublic (taken from Owner key) for transmitting and receiving changes of UUIDs, or to channelPrivate (taken from the object key) for receiving commands from parent
llSetPin to put in "nodo" object
This is NOT really needed you can instead put a copy of main script in inner object. I used this technique to avoid changing every time the internal script whenever I update it.
<lsl>// This will convert an id to an integer same id => same key
// used to easily set up a not easy way to deposit scripts with a PIN
integer key2int(key id)
{
string idstring = (string)id; integer ret = 0; integer i = 0; for(; i < llStringLength(idstring); ++i) { ret = ret * 10 + (((integer)("0x8"+llGetSubString(idstring, i, i)) - 2) % 17); } return ret;
}
default {
state_entry() { llSetRemoteScriptAccessPin(key2int(llGetOwner())); } changed(integer change) { llSetRemoteScriptAccessPin(key2int(llGetOwner())); }
}</lsl>
The Main Script
<lsl>
// This source is "OpenSource" // It can be freely used under the following CC license // by: you need to state it was originally written by Salahzar Stenvaag // nc: you cannot use for commercial products // sa: you can extend it but you must redistribute it under the same license // This software is specifically written to be used by GPLv3 licenced software
// Wiki3DBuilder (MindMap) // This had been built following the ideas (but not the code) of similar // object "SpatialMap.org (MindMap 3D) v1.0.18" written by Jonny Bee Cioc and Vision Raymaker // which was under a CC by,nc,sa license itself
// What it is // ========== // You can build rich 3D wiki architectures made up of NODES, connected with particles BEANS // Each NODE can be customized changing its NAME, COLOR, SIZE, FORM and possibly inserting in it // 1 Notecard, 1 Landmark, 1 Object, 1 Script, 1 Texture, URL that can be given // The network is completely open to the participation of a group of people who can change the // newtwork, the position of each node, and freely add or remove portions of the graph
// Being a Wiki there is no limitation on who can do what. If you don't want that the work can be furtherly // changed please set to NOT RUNNING the scripts. To save the mindmap you need to select all the nodes // and manually link them together or take in the inventory as aggregate
// How it works
// Initially you have a NODE, i.e. a cube which must be touched to start the process
// Touching it you have a menu specifying color, name, size, content, move, rez, ...
// * clicking on name you can type the name on the local chat and other menu for resizing, recoloring...
// * clicking rez you rez a new node when choosing this option you must touch one of the faces of the cube:
// new node will be rezzed 50cm on that direction.
// * similarly applies to move clicking on a face will pull the node towards that direction
// * content. Everybody can move objects, textures and so from their inventory to a node (CTRL-Dragging them).
// at each moment only one type of object can be in the node and it can be given to whom is asking for content
// changing the texture will also change the texture of the node
// Internal architecture
// This is done in ONLY one script named "Salahzar Map" and an internal object named "nodo" containing a llSetPin script.
// The llSetPin is just a way for setting the pin to a number depending on the Owner of the object so it is NOT easy to
// hack.
// The "Salahzar Map" is implementing a quite interesting mechanism for dealing with "multi-threading", i.e. multiple
// avatars touching the object at the same time. This is accomplished using an Array of RECORDs representing
// the waiting avatars and what they were waiting for. So listen(), touch(), and other events can query this QUEUE
// and understand what is to be made next.
// This also allows for an interesting handling of MULTI MENU
// A part from this the rest is relatively simple, except for the communication between the various nodes.
// A node communicates with its children for example to propagate the DELETE message
// A node communicates with its parent to emit the particles connector
// A node receives messages from its parent to llDie and with alert when other related objects are changing UUID
// the latter happens when objects are re-rezzed from inventory
// To accomplish this object listens to a
// channelPublic (taken from Owner key) for transmitting and receiving changes of UUIDs,
// or to channelPrivate (taken from the object key) for receiving commands from parent
// TODOLIST:
// implement LINKSET all this can be very tricky // implement UNLINK (remove particles)
// CUSTOMIZABLE PARAMETERS
integer TIMEOUT=30; // menu will be obsolesced after this period of time
integer CLEANTIMERSEC=10; // timer will run once every this period of time for cleaning timers
integer DEBUG=0; // 2 maximum level
// END OF CUSTOMIZABLE PARAMETERS
string url=""; // url associated with this node
// 0) General utility functions
// debug this string only if DEBUG is at least 1 debug(string str) {
if(DEBUG>=1)llOwnerSay(str);
}
// trace the string only if DEBUG is 2 trace(string str) {
if(DEBUG>=2)llOwnerSay(str);
} // will put on the hover string notify(string str) {
llSetText(str,<1,1,1>,1);
}
// 1) RECORD STACK // handling. We keep a "queue" or RECORDS for understanding // multi-avatar touching and remember prompts and menu and let them expire //
integer STRIDE=6; // we hold 6 field in the waiting list list waiting=[]; // this is where we actually keep the queue
// this will be the RECORD with the 6 fields string theType; // it is something like MENUxxxx or PROMPTyyyy key theAv; // which avatar is waiting integer theTime; // when the RECORD has been stacked integer theMenu; // the menu handle so we can clean up it integer theChannel; // which channel string theRest; // some further information on this RECORD (prompt string?)
// count how many RECORDS are being stacked waiting to be served integer howManyRecords() {
integer ret= llGetListLength(waiting)/STRIDE; trace("howManyRecords returning: "+(string)ret); return ret;
} // load the record in theXXX variables to ease handling of it integer loadTheRecord(integer index) {
if(index>=0) { theType=llList2String(waiting,index); theAv=llList2Key(waiting,index+1); theTime=llList2Integer(waiting,index+2); theMenu=llList2Integer(waiting,index+3); theChannel=llList2Integer(waiting,index+4); theRest=llList2String(waiting,index+5); debug("Loading the record: \n type: "+theType+"\n Av: "+(string)theAv
+"\n time: "+(string)theTime+"\n menu: "+(string)theMenu+"\n channel:" +(string)theChannel+"\n rest:"+theRest);
return index; } else { theType=""; theAv=NULL_KEY; theTime=-1; theMenu=-1; theChannel=-1; theRest=""; trace("No record returned"); return -1; }
}
// look in our stack for this avatar return the index AND loading in theXXX variables
integer findWaitingAvatar(key av) {
debug("Looking for avatar: "+(string)av); integer pos=llListFindList(waiting,[av]); integer index; if(~pos) index=pos-1; else index=-1; return loadTheRecord(index);
} // change the time of the event at position index updateTimer(integer index) {
if(index<0 || index>=howManyRecords()) return; trace("Updating timer for index: "+(string)index); integer posTime=index*STRIDE+2; waiting=llListReplaceList(waiting,[ llGetUnixTime() ], posTime, posTime);
}
// remove a record integer deleteRecord(integer index) {
if(index>=0 && index<howManyRecords()) { integer i=index*STRIDE; waiting=llDeleteSubList(waiting,i,i+STRIDE-1); trace("deleteRecord "+(string)index+" array: "+llList2CSV(waiting)); } return TRUE;
}
// PSEUDO record containing the avatar for detecting key and message key detected_key=NULL_KEY; string detected_type="";
// add a MENU stack in the queue addMenu(string type, key av, string title, list options) {
trace("Adding menu "+type+" for "+(string)av); integer channel=-1000000-(integer)llFrand(1000000); integer menuid=llListen(channel,"",av,""); waiting+=[ type, av, llGetUnixTime(), menuid, channel,"" ]; //debug("List in touch: "+llList2CSV(waiting)); llDialog(av,title,options,channel);
} // add a PROMPT stack in the queue addPrompt(string type, key av, string title) {
trace("Adding prompt "+type+" for "+(string)av); integer menuid=llListen(0,"",av,""); waiting+=[ type, av, llGetUnixTime(), menuid, 0, title]; llInstantMessage(av,"Please chat: "+title);
} // add in queue detect option to be able square is TRUE if we only allow boxed movement addDetected(string type, key av, string title) {
trace("Adding detected "+type+" for "+(string)av); detected_key=av; detected_type=type;
} remove_detected() {
trace("remove detected"); detected_key=NULL_KEY; detected_type="";
}
// 2) INVENTORY HANDLING
list currentInventory=[]; // will keep note of current inventory // return a list of strings in ls1 NOT in ls2 // actually implementing set difference : [ a,b,c ] - [a, b] => [c] // useful to understan which elements have been added deleted in the inventory list diffList(list ls1, list ls2) {
//llSay(0,"ls1: "+llList2CSV(ls1)+" ls2:"+llList2CSV(ls2)); list ret=[]; integer i=0; for(i=0;i<llGetListLength(ls1);i++) { list el=llList2List(ls1,i,i); // if not found in ls2 add to residual if(llListFindList(ls2, el)<0) { ret+=el; } } return ret;
}
// find out the first inventory object of the specified type string getFromListOfType(list lst,integer type) {
integer i; for(i=0;i<llGetListLength(lst);i++) { string name=llList2String(lst,i); if(name != "nodo" && name != llGetScriptName() && name != "llSetPin") { if(llGetInventoryType(name)==type) return name; } } return "";
}
// will check if inventory changed and make sure that we have JUST one elemento // for each type inventoryUpdate() {
//llSay(0,"inventory update"); integer i=0; integer j=0; string ret=""; integer num=llGetInventoryNumber(INVENTORY_ALL); //if(num==llGetListLength(currentInventory)) return; list newInventory=[]; for(i=0;i<num;i++) { string name=llGetInventoryName(INVENTORY_ALL,i); integer found=0; newInventory+=[name]; } // now find added inventory list added=diffList(newInventory,currentInventory); list removed=diffList(currentInventory,newInventory); //llSay(0,"==>Added "+llList2CSV(added)); //llSay(0,"==>Removed "+llList2CSV(removed)); // now check for each added if there was a duplicate in currentInventory for(i=0;i<llGetListLength(added);i++) { string name=llList2String(added,i); string already=getFromListOfType(currentInventory,llGetInventoryType(name)); if(already!="") { llRemoveInventory(already); llSay(0,name+" replaced "+already); } else llSay(0,name+" inserted in content"); if(llGetInventoryType(name)==INVENTORY_TEXTURE) llSetTexture(name,ALL_SIDES); }
currentInventory=newInventory; return;
}
// 3) handles particles key target=NULL_KEY; particles() {
trace("making particles to "+(string)target); integer pattern = PSYS_SRC_PATTERN_ANGLE; integer flags= PSYS_PART_EMISSIVE_MASK | PSYS_PART_INTERP_COLOR_MASK //| PSYS_PART_FOLLOW_SRC_MASK | PSYS_PART_FOLLOW_VELOCITY_MASK | PSYS_PART_INTERP_SCALE_MASK | PSYS_PART_TARGET_LINEAR_MASK | PSYS_PART_TARGET_POS_MASK; // set particles llParticleSystem([ PSYS_PART_MAX_AGE,5.0, PSYS_PART_FLAGS, flags, PSYS_PART_START_COLOR, <1,0.25,0.25>, PSYS_PART_END_COLOR, <0.75,0,0>, PSYS_PART_START_SCALE,<0.051,.2,FALSE>, PSYS_PART_END_SCALE,<0.2,.1,FALSE>, PSYS_SRC_PATTERN, pattern, PSYS_SRC_BURST_RATE,.1, //PSYS_SRC_ACCEL, <0,0,0>, PSYS_SRC_BURST_PART_COUNT,8, //PSYS_SRC_BURST_RADIUS,0.0, PSYS_SRC_BURST_SPEED_MIN,0.0, PSYS_SRC_BURST_SPEED_MAX,5.0, PSYS_SRC_TARGET_KEY,target, PSYS_SRC_ANGLE_BEGIN,0.0, PSYS_SRC_ANGLE_END,0.0, PSYS_SRC_OMEGA, <0,0,0>, PSYS_SRC_MAX_AGE, 0.0, //PSYS_SRC_TEXTURE, TEXTURE_BLANK, PSYS_PART_START_ALPHA, 1.0, PSYS_PART_END_ALPHA, 0.5 ]);
}
// 4) This will convert an id to an integer same id => same key
// used to easily set up a not easy way to deposit scripts with a PIN
integer key2int(key id)
{
string idstring = (string)id; integer ret = 0; integer i = 0; for(; i < llStringLength(idstring); ++i) { ret = ret * 10 + (((integer)("0x8"+llGetSubString(idstring, i, i)) - 2) % 17); } return ret;
}
integer isroot=TRUE; // are we the MAIN object? Useful to avoid being deleted list children=[]; // will keep the keys of ALL depending children
// 5) propagate message to all children propagate(string message) {
debug("Received message propagating to children: "+llList2CSV(children)); // received a message from my parent // propagate message to my children integer i; for(i=0;i<llGetListLength(children);i++) { integer channel=key2int(llList2Key(children,i)); debug("Saying on channel "+(string)channel+" "+message); llSay(channel,message); } if(message=="DELETE") { // we must die IF we are not ROOT
if(isroot==0) llDie(); else debug("Cannot delete master cube"); }
}
// ============================== MAIN =========================
integer lstPrivate; integer lstPublic; integer channelPublic; integer channelPrivate;
key parent=NULL_KEY; default {
state_entry() { // setting the communication channel identity private can change when re-rezzing channelPrivate=key2int(llGetKey()); channelPublic=key2int(llGetOwner()); debug("My local channel: "+(string)channelPrivate); debug("My public channel: "+(string)channelPublic); // if we are the main node go directly to ready // we know that because we don't have the llSetPin on main Ball inventoryUpdate(); // setup inventory if(llGetInventoryType("llSetPin")==INVENTORY_NONE) { isroot=TRUE; //llRequestPermissions(llGetOwner(), PERMISSION_CHANGE_LINKS); state ready; } isroot=FALSE; // we are in a child node, notify("Node bootstrapping..."); // else rezzed by parent wait to listen who is the parent // parent will send a message on a special channel computed starting from // the child key // waiting for knowing who is my parent lstPrivate=llListen(channelPrivate,"",NULL_KEY,""); } // this listen only waits until my parent talks to me // this happens when rezzing avatar stops the menu listen(integer channel,string name, key id, string str) { debug("received "+str+" on channel "+(string)channel); // set my parent to whom is talking to me first parent=id; // llListenRemove(lstparent);
// emits the beam towards my parent target=id; particles(); // remove listener before leaving this state llListenRemove(lstPrivate);
state ready; }
}
// =========================== READY =========================
state ready
{
state_entry() { // reset waiting list waiting=[]; children=[]; notify(llGetObjectName()); llSetTimerEvent(CLEANTIMERSEC); // garbage collector // listen on private channel (for messages from parent -- delete) lstPrivate=llListen(channelPrivate,"",NULL_KEY,""); // listen on public channel (for UUID changing) lstPublic=llListen(channelPublic,"",NULL_KEY,""); // allow inventory dropping (for content dropping) llAllowInventoryDrop(TRUE); // If here it means I'm being creating from scratch... Mark my UUID on the description llSetObjectDesc((string)llGetKey()); } on_rez(integer rez) { trace("Object rezzed with new taskUUID"); // this happens ONLY if I come back from the inventory my UUID has changed // then will broadcast my old UUID string what="OUID:"+llGetObjectDesc(); debug("Saying "+what+" in public channel "+(string)channelPublic); llSay(channelPublic,what); // private channel has to be reset! llListenRemove(lstPrivate); channelPrivate=key2int(llGetKey()); trace("Listening to new privateChannel "+(string)channelPrivate); lstPrivate=llListen(channelPrivate,"",NULL_KEY,""); // reset the description llSetObjectDesc((string)llGetKey()); } // If we changed owner or inventory changed(integer change) { //llSay(0,"change: "+(string)change); if(change & CHANGED_OWNER) { debug("Changed owner, resetting"); llResetScript(); } // handle changing of the inventory or somebody dropped if(change & CHANGED_INVENTORY || change & CHANGED_ALLOWED_DROP) { inventoryUpdate(); } } // timer is trying to get all the menu listener clean // if they are exhausted then proceed to cancel them timer() { integer i; for(i=0;i<howManyRecords();i++) { loadTheRecord(i); // into theXXXX fields // check if timeout (60 seconds if( (llGetUnixTime() - theTime ) > TIMEOUT ) { debug("Removing listener for "+llKey2Name(theAv)); // if we were on particular menu should also delete the llListenRemove(theMenu); deleteRecord(i); // if timeout something related to the detected avatar need to remove from list if(detected_key==theAv) remove_detected(); } } //debug("List in timer exit: "+llList2CSV(waiting)); }
// main menu listening for waiting avatars // we answer only to touch_start(integer total_number) { integer i; for(i=0;i<total_number;i++) { key av=llDetectedKey(i); debug("touched by "+llKey2Name(av)); // check if we already have this avatar waiting. Don't allow touches if in a menu or a prompt!!!! integer index=findWaitingAvatar(av); if(index>=0) { debug("Has already a menu waiting"); // we have a menu for this avatar.. Check if we have it in detected_key // in such case we allow touching for detecting if(av==detected_key) { trace("We have a detected_key for this avatar type:"+detected_type); // handle this event if(detected_type=="DETECTMOVE") { debug("Moving the prim"); llSetPos(llGetPos()+llDetectedTouchNormal(i)*0.2); } if(detected_type=="DETECTPOSREZ") { debug("rezzing a new cube"); vector posrez=llDetectedTouchNormal(i); llRezAtRoot("nodo",llGetPos()+posrez,ZERO_VECTOR,ZERO_ROTATION,1); // execution is going on at rez_object } //upgrade time for this event extend it so that timer won't remove it for next 60 secs updateTimer(index); // do NOT consume this event will be ended by ctimeout return; } // touch this when avatar has already a queue, will remove the queue deleteRecord(index); // informing the avatar the menu has been reset llInstantMessage(av,"Removing pending menu/promt"); return; } // avatar hasn't any pending menu so start with main menu // start the initial menu addMenu("MENUMAIN",av,"Main menu:
DELETE: delete this and children, NAME: you can type the new name, COLOR: change the color, SIZE: change the scale, REZ: rez another node (click next on face for where to rez) MOVE: pull the object in a direction (click next on face for where to rez), TYPE: changes the object type, HELP: go to online help, UNLINK: remove the connecting beam",
["DELETE","NAME","COLOR", "SIZE","REZ","MOVE", "TYPE","HELP","UNLINK", "CONTENT" ]); trace("List in touch exit: "+llList2CSV(waiting)); return; } }
// listen must understand if this avatar already has pending menu // and correctly sort out of them listen(integer channel,string name, key id, string message) { debug("receiving a message in channel "+(string)channel+" message "+message); // PRIVATE CHANNEL if(channel==channelPrivate) { debug("Received a message for me from my parent "+message); propagate(message); return; } // PUBLIC CHANNEL if(channel==channelPublic) { // on channel public we ONLY receive OLD UUID in the format // UUID: if(llGetSubString(message,0,4)=="OUID:") { key olduuid=(key)llGetSubString(message,5,-1); debug("Received from "+(string)id+" UUID change from old "+(string)olduuid); // need to understand if it was MY target if(target==olduuid){ target=id; particles(); // change the beam to this new target } // check my children integer pos=llListFindList(children,[olduuid]); if(pos>=0) { debug("Replacing in children olduuid: "+(string)olduuid+" with "+(string)id); children=llListReplaceList(children,[id],pos,pos); }
} return; }
// if not channel public or private then it shoudl be on stack integer index=findWaitingAvatar(id); debug("listen index found on waiting list: "+(string)index); if(index<0) return; // only here if we found on the stack integer elapsed=llGetUnixTime()-theTime; // remove handle AND the record from waiting list llListenRemove(theMenu); deleteRecord(index); debug("Found listen menu type: "+theType+" mesg "+message);
// stopping menus if(theType=="MENUSTOPMOVE") { remove_detected(); llInstantMessage(id,"Stop moving"); return; } if(theType=="MENUSTOPREZ") { remove_detected(); llInstantMessage(id,"Stop rezzing"); return; } // handling menu content allow for setting content if(theType=="MENUCONTENT") { // allows for specifying a URL if(message=="SETURL") { addPrompt("PROMPTURL",id,"Say in chat the url where to go"); return; } // just send help on how ctrl-dragging objects if(message=="SETCONTENT") { // send help llInstantMessage(id,"Please ctrl-drag something from your inventory on me. You can change the texture dragging a texture"); return; } // going to stored URL if(message=="GIVE URL") { if(url!="") { llLoadURL(id,"Go to webpage",url); } return; } // handle all GETXXX buttons string name; string desc; if(message=="GIVE NOTE") { name=getFromListOfType(currentInventory,INVENTORY_NOTECARD); desc="notecard"; } if(message=="GIVE LMARK") { name=getFromListOfType(currentInventory,INVENTORY_LANDMARK); desc="landmark"; } if(message=="GIVE OBJECT") { name=getFromListOfType(currentInventory,INVENTORY_OBJECT); desc="object"; } if(message=="GIVE TEXTURE") { name=getFromListOfType(currentInventory,INVENTORY_TEXTURE); desc="texture"; } if(name=="") { llInstantMessage(id,"No "+desc+" to give you"); return; } // giving actually the object llGiveInventory(id,name); llInstantMessage(id,"You have been given a "+desc+" named "+name); return; } // Handling of the options in MAIN MENU if(theType=="MENUMAIN") { if(message=="HELP") { llLoadURL(id,"Load this page for help","http://opensimita.org/lsl/mindmap/README.html"); return; } if(message=="CONTENT") { integer i; string texture=getFromListOfType(currentInventory,INVENTORY_TEXTURE); string object=getFromListOfType(currentInventory,INVENTORY_OBJECT); string note=getFromListOfType(currentInventory,INVENTORY_NOTECARD); string landmark=getFromListOfType(currentInventory,INVENTORY_LANDMARK); string prompt = "Choose an action:
SETCONTENT: explains how to insert an object SETURL: allows for specifying a url";
list buttons=["SETCONTENT","SETURL" ]; if(url!="") { prompt+="\nGIVE URL: go to the url "+url; buttons+=["GIVE URL"]; } if(note!="") { prompt+="\nGIVE NOTE: get the note "+note; buttons+=["GIVE NOTE"]; } if(object!="") { prompt+="\nGIVE OBJECT: get the object "+object; buttons+=["GIVE OBJECT"]; } if(landmark!="") { prompt+="\nGIVE LMARK: get the landmark "+landmark; buttons+=["GIVE LMARK"]; } if(texture!="") { prompt+="\nGETTEXTURE: get the texture "+texture; buttons+=["GIVE TEXTURE"]; } addMenu("MENUCONTENT",id,prompt,buttons); return; } if(message=="NAME") { addPrompt("PROMPTNAME",id,"Say in chat name of this concept"); return; } if(message=="DELETE") { addPrompt("PROMPTDELETE",id,"Say DELETE in chat to be absolutely sure to delete from here"); return; } if(message=="COLOR") { addMenu("MENUCOLOR",id,"Enter color",["RED","GREEN","BLUE","WHITE","BLACK","VIOLET","TURQUESE","YELLOW"]); return; } if(message=="SIZE") { addMenu("MENUSIZE",id,"Enter size",["HALF", "DOUBLE", "+0.2","-0.2","DEFAULT"]); return; } if(message=="UNLINK") { llParticleSystem([]); target=NULL_KEY; llInstantMessage(id,"Removed particles"); return; } // detected category if(message=="REZ") { addDetected("DETECTPOSREZ",id,"Enter position where rez"); addMenu("MENUSTOPREZ",id,"Click on a face of the concept to rez a new cube in that direction.\nClick 'stop' after chosing",["STOP"]); return; } if(message=="MOVE") { addDetected("DETECTMOVE",id,"Click on a face of the concept and the cube will move 20cm in that direction."); addMenu("MENUSTOPMOVE",id,"Click to stop moving",["STOP"]); return; } if(message=="TYPE") { addMenu("MENUTYPE",id,"Select type of this concept",["SPHERE","CUBE","FLAT","CYLINDER","PYRAMID","POINT","STAR","ENDTYPE"]); return; } } // change the type of this prim if(theType=="MENUTYPE") { if(message=="ENDTYPE") { llInstantMessage(id,"Finished with typing"); return; } list typ=[]; if(message=="FLAT") { vector scale=llGetScale(); llSetScale(<0.01,scale.y,scale.z>); return; } if(message=="POINT") { llSetScale(<0.01,0.01,0.01>); return; } if(message=="STAR") { llInstantMessage(id,"STAR not yet implemented"); return; }
if(message=="SPHERE") typ=[ PRIM_TYPE, PRIM_TYPE_SPHERE, 0, <0,1,0>, 0.0, <0,0,0>,<0,1,0> ]; if(message=="CUBE") typ=[ PRIM_TYPE, PRIM_TYPE_BOX,0, <0.0, 1.0, 0.0>, 0.0, <0.0, 0.0, 0.0>, <1.0, 1.0, 0.0>, <0.0, 0.0, 0.0>]; if(message=="CYLINDER") typ=[ PRIM_TYPE, PRIM_TYPE_CYLINDER, 0, <0.0, 1.0, 0.0>, 0.0, <0.0, 0.0, 0.0>, <1.0, 1.0, 0.0>, <0.0, 0.0, 0.0>]; if(message=="PYRAMID") typ=[ PRIM_TYPE, PRIM_TYPE_BOX,0, <0.0, 1.0, 0.0>, 0.0, <0.0, 0.0, 0.0>, <0.0, 0.0, 0.0>, <0.0, 0.0, 0.0>]; llSetPrimitiveParams(typ); addMenu("MENUTYPE",id,"Select type of this concept",["SPHERE","CUBE","FLATBOX","CYLINDER","PYRAMID","POINT","STAR","ENDTYPE"]); return; }
// change the size of this prim if(theType=="MENUSIZE") { vector current=llGetScale(); if(message=="HALF") current=current/2; if(message=="DOUBLE") current=current*2; if(message=="+0.2") current=current+<0.2,0.2,0.2>; if(message=="-0.2") current=current-<0.2,0.2,0.2>; if(message=="DEFAULT") current=<0.5,0.5,0.5>; llSetScale(current); llInstantMessage(id,"Size changed to '"+(string)current+"'"); return; } // change the color of this prim if(theType=="MENUCOLOR") { if(message=="RED") llSetColor(<1,0,0>,ALL_SIDES); if(message=="GREEN") llSetColor(<0,1,0>,ALL_SIDES); if(message=="BLUE") llSetColor(<0,0,1>,ALL_SIDES); if(message=="WHITE") llSetColor(<1,1,1>,ALL_SIDES); if(message=="BLACK") llSetColor(<0,0,0>,ALL_SIDES); if(message=="VIOLET") llSetColor(<1,0,1>,ALL_SIDES); if(message=="TURQUESE") llSetColor(<0,1,1>,ALL_SIDES); if(message=="YELLOW") llSetColor(<1,1,0>,ALL_SIDES); llInstantMessage(id,"Color changed to '"+message+"'"); return; } // change the name if(theType=="PROMPTNAME") { notify(message); llSetObjectName(message); llInstantMessage(id,"Name changed to '"+message+"'"); return; } if(theType=="PROMPTDELETE") { if(message=="DELETE") { // handle delete message propagate(message); return; } } // change the url if(theType=="PROMPTURL") { url=message; llInstantMessage(id,"URL accepted: "+url); return; } } // when new node is rezzed then we give this script running and tell it // my id so that it can emit particles object_rez(key id) {
// understand the remotepin which is publicchannel integer remotePin=key2int(llGetOwner()); integer childchannel=key2int(id); llRemoteLoadScriptPin(id, llGetScriptName(), remotePin, TRUE, key2int(llGetKey())); debug("Obtaining my key: "+(string)childchannel+(string)llGetKey()); // memorize this channel so to be able to speak to all my children in case of die children+=[id]; llGiveInventory(id,"nodo"); debug("Chatting "+(string)llGetKey()+" on channel "+(string)key2int(id)+ "children: "+llList2CSV(children)); llSay(childchannel,(string)llGetKey()); //llInstantMessage("Node created"); }
} </lsl>
Having a copy from XStreetsl
If you have any difficulties in having these scripts running, please get a copy from XStreetSL: https://www.xstreetsl.com/modules.php?name=Marketplace&file=item&ItemID=1632590