Jump to content

Quistess Alpha

Resident
  • Posts

    3,876
  • Joined

  • Last visited

Everything posted by Quistess Alpha

  1. Would you really want one if it didn't do anything for your hair?
  2. both have implied consent, or can easily obtain consent of the people they're tracking.
  3. a bit off-topic, but being able to see a visitor list and being able to test an ID against the list are different. IMHO the kind of info storage needed to retrieve a specific visitor list necessarily violates PII. 3 distinct people visited on Friday, one of the people visited twice = ok, Love, M Peccable and Tessa visited on Friday, Tessa visited twice = probable PII violation.
  4. Depends on the specific use case. I don't see why the server should ~need any user-names.
  5. if the only reason you're storing a user's key is to not double-count them, you can just obfuscate ther key before storing it on your server, then there's no PII storeed outside SL.
  6. indeed, but all they have to do is hash the key to "fix" the issue.
  7. Sometimes applying arbitrary RLV via the RLV Relay protocol can be fun when done responsibly: integer gChannel_Owner=36; // channel for owner to hud talk. integer gChannel_Other=37; string gTarget = NULL_KEY; list gAttachKeys = []; string gCommand = "GrieferHud"; integer ghListenOther; list gClothingLayers = ["gloves","jacket","pants","shirt","shoes", "skirt","socks","underpants","undershirt","skin", "eyes","hair","shape","alpha","tatoo","physics"]; string wwGetObjectSLURL(key ID) { // could improve by checking if group owned, parsing one big ObjectDetails call list details = llGetObjectDetails(ID,[OBJECT_NAME,OBJECT_POS,OBJECT_OWNER,OBJECT_GROUP]); string globe = "[secondlife:///app/objectim/"; string region = llEscapeURL(llGetRegionName()); vector pos = llList2Vector(details,1); key owner = llList2Key(details,2); key group = llList2Key(details,3); string objOwner = llList2String(["&owner="+(string)owner,"&groupowned=true"],owner==NULL_KEY); string name= llEscapeURL(llList2String(details,0)); string posx = (string)llRound(pos.x); string posy = (string)llRound(pos.y); string posz = (string)llRound(pos.z); return (globe + (string)ID +"?name="+name+objOwner+"&slurl="+ region +"/"+posx+"/"+posy+"/"+posz+" "+name+"]"); } string wwGetAgentSLURL(key ID) { return ("secondlife:///app/agent/"+(string)ID+"/inspect "); } default { state_entry() { llListen(gChannel_Owner,"",llGetOwner(),""); } timer() { llSetTimerEvent(0.0); llListenRemove(ghListenOther); llOwnerSay("No response from target."); } listen(integer Channel,string Name,key ID,string Message) { if(Channel==gChannel_Owner) { if(llGetSubString(Message,0,0)=="@" || llGetSubString(Message,0,0)=="!" ) { llSay(-1812221819,gCommand+","+gTarget+","+Message); llOwnerSay("Relaying commands"); } else if(llGetSubString(Message,0,8)=="SETTARGET") // expects an extra space after. { gTarget = llGetSubString(Message,10,-1); if(llStringLength(gTarget)==36) llOwnerSay("Target Set!"); else llOwnerSay("ERROR!"); } else if(llGetSubString(Message,0,5)=="SETCMD") // expects an extra space after. gCommand = llGetSubString(Message,7,-1); else if(llGetSubString(Message,0,4)=="STRIP") // expects an extra space after. { string argument = llGetSubString(Message,6,-1); if(llStringLength(Message) <= 8)//assume no more than 99 attachments lol. { llSay(-1812221819,"qStrip"+ ","+gTarget+","+"@remattach:"+ llList2String(gAttachKeys,(integer)argument)+ "=force"); }else { llSay(-1812221819,"qStrip"+ ","+gTarget+","+"@remoutfit:"+ argument+ "=force"); } } else if(llGetSubString(Message,0,6)=="GETWORN") { gAttachKeys = llGetAttachedList(gTarget); integer len = llGetListLength(gAttachKeys); llOwnerSay("getting "+(string)len+" attachments."); while (len>0) { llOwnerSay((string)len+ "): "+ llKey2Name(llList2Key(gAttachKeys,--len))); } ghListenOther= llListen(gChannel_Other,"",gTarget,""); llSetTimerEvent(5.0); llSay(-1812221819,"qGetOutfit"+ ","+gTarget+","+"@getoutfit="+(string)gChannel_Other); }else if("GETTARGET"==Message) { llSensor("", "", AGENT, 10.0, PI); }else { llOwnerSay("I didn't understand: "+Message); // could be more careful with this. . . llOwnerSay("Commands are: SETTARGET <uuid>, SETCMD <string>, STRIP <index>, GETWORN, GETTARGET, @<rlv>"); } }else if(Channel==gChannel_Other) { //This is an @getoutfit response. llListenRemove(ghListenOther); llSetTimerEvent(0.0); integer len = llStringLength(Message); string Reply; if( len < 20) // should be 15. { while(~--len) { if(llGetSubString(Message,len,len)=="1") Reply=llList2String(gClothingLayers,len)+", "+Reply; } llOwnerSay(Reply); }else { llOwnerSay("Bug or depreciation: "+(string)len+","+Message); } } } sensor(integer n) { while(~--n) { llOwnerSay("[secondlife:///app/chat/36/SETTARGET%20"+(string)llDetectedKey(n)+" "+llDetectedName(n)+"]"); } } } /*list gAttachPoints = ["none","chest","skull","left shoulder","right shoulder", "left hand","right hand","left foot","right foot","spine", "pelvis","mouth","chin","left ear","right ear", "left eyeball","right eyeball","nose","r upper arm","r forearm", "l upper arm","l forearm","right hip","r upper leg","r lower leg", "left hip","l upper leg","l lower leg","stomach","left pec", "right pec","center 2","top right","top","top left", "center","bottom left","bottom","bottom right","neck", "root", //Bento/extended: "left ring finger","right ring finger","tail base","tail tip","left wing" ];*/ and a few miscellaneous functions (notecard giver, "anonymous" echo on channel 3536, RLV-message snooper ) rolled together.: // [secondlife:///app/chat/363500/GET Quistessa's Notecard] // may have to right click -> run this command integer ghListenRLV; integer gbListenRLV; string wwGetObjectSLURL(key ID) { // could improve by checking if group owned, parsing one big ObjectDetails call list details = llGetObjectDetails(ID,[OBJECT_NAME,OBJECT_POS,OBJECT_OWNER,OBJECT_GROUP]); string globe = "[secondlife:///app/objectim/"; string region = llEscapeURL(llGetRegionName()); vector pos = llList2Vector(details,1); key owner = llList2Key(details,2); key group = llList2Key(details,3); string objOwner = llList2String(["&owner="+(string)owner,"&groupowned=true"],owner==NULL_KEY); string name= llEscapeURL(llList2String(details,0)); string posx = (string)llRound(pos.x); string posy = (string)llRound(pos.y); string posz = (string)llRound(pos.z); return (globe + (string)ID +"?name="+name+objOwner+"&slurl="+ region +"/"+posx+"/"+posy+"/"+posz+" "+name+"]"); } string wwGetAgentSLURL(key ID) { return ("secondlife:///app/agent/"+(string)ID+"/inspect "); } default { state_entry() { llListen(363500,"",NULL_KEY,"GET"); llListen(3536,"",llGetOwner(),""); ghListenRLV = llListen(-1812221819,"","",""); } touch_start(integer n) { if(llDetectedTouchFace(0)==3) { llListenControl(ghListenRLV,gbListenRLV=!gbListenRLV); llOwnerSay("RLV snooper set to: "+(string)gbListenRLV); } } listen(integer CH, string Name,key ID,string text) { if(363500==CH) { llOwnerSay("Giving Notecard to "+ llGetDisplayName(ID)); llGiveInventory(ID,"Quistessa's Notecards"); }else if(3536==CH) { string name = llGetObjectName(); llSetObjectName(""); llSay(0,text); llSetObjectName(name); }else if(CH == -1812221819) { string myName = llGetObjectName(); llSetObjectName("RLV Sensor"); list parsed = llParseString2List(text,[","],[]); integer Len = llGetListLength(parsed); if(Len==4) { // this is a relay to object communication string cmd_name = llList2String(parsed,0); string uuid = llList2String(parsed,1); string cmd = llList2String(parsed,2); string reply = llList2String(parsed,3); llOwnerSay( "Relay to object "+wwGetAgentSLURL(llGetOwnerKey(ID))+" -> " + wwGetObjectSLURL(uuid) + " : "+text); }else if(Len==3) { // this is an object to relay communication string cmd_name = llList2String(parsed,0); string uuid = llList2String(parsed,1); string cmd = llList2String(parsed,2); llOwnerSay("Object to relay "+wwGetObjectSLURL(ID)+" -> " + wwGetAgentSLURL(llGetOwnerKey(uuid)) +" : " + text); }else { llOwnerSay("Parse Error! Length ="+(string)Len); integer Index=-1; while(Index<Len) { llOwnerSay("Error! "+wwGetObjectSLURL(ID)+" : "+text); } } llSetObjectName(myName); } } }
  8. It turns out that LinksetData functions make implementing a RLV relay a good deal easier: Assistant script and config: string gSatOn; key OWNER; default { state_entry() { // listed "DOM"s accept or deny RLV devices on your behalf. llLinksetDataWrite("@_DOM",""); // supports a comma separated list, or empty value. // above "DOM"s can use this safeword to free you. (reset the other script after changing this setting) // To free yourself in case of emergency, click and hold on the HUD for 5 seconds then release. llLinksetDataWrite("@_SAFEWORD","Free Tessa!"); // If you have any "DOM"s listed above, they must approve your safeword. // When @_AloneSafewordAllowed is 1, your click-and-hold safeword will work if none of your "DOM"s are nearby. llLinksetDataWrite("@_AloneSafewordAllowed","1"); OWNER = llGetOwner(); integer CHAN_ALLOW = llAbs((integer)("0x"+(string)OWNER)); llListen(CHAN_ALLOW,"",OWNER,"Allow"); llListen(CHAN_ALLOW,"",OWNER,"Deny"); string DOM = llLinksetDataRead("@_DOM"); if(DOM) { list DOM = llCSV2List(DOM); integer i = llGetListLength(DOM); while(~--i) { string DOM = llList2String(DOM,i); // yes, lsl supports confusing name overloading per-scope! llListen(CHAN_ALLOW,"",DOM,"Allow"); llListen(CHAN_ALLOW,"",DOM,"Deny"); } } llSetTimerEvent(20.0); } timer() { gSatOn=llList2String(llGetObjectDetails(OWNER,[OBJECT_ROOT]),0); } attach(key ID) { if(ID) if(gSatOn!=(string)llGetOwner()) llOwnerSay("@sit:"+gSatOn+"=force"); // ^^ second conditional isn't really neccessary, just avoids a 'no room to sit' error when attempting to sit on oneself. } listen(integer Channel, string Name, key ID, string Text) { llLinksetDataWrite("@_Allow",(string)(Text=="Allow")); } changed(integer c) { if(c&CHANGED_OWNER) llResetScript(); // mildly inefficient, but avoids listen handles. } } Main script: integer MAX_RESTRICTORS = 6; // can handle up to 31, but that's gratuitous. string SPEC_VERSION = "1100"; // Retrieved Feb 2023: https://wiki.secondlife.com/wiki/LSL_Protocol/Restrained_Love_Relay/Specification string IMPL_VERSION = "LSDR:0001"; // this can be whatever we want? string SAFEWORD = "SAFEWORD!"; float PING_DELAY = 20.0; // how long to wait for responses to log-in ping. no restrictions are applied before this delay. //integer FULL_MASK; // 0b111111 for MAX_RESTRICTORS==6. list gRestrictors; integer gMaskRestrictors; integer CHAN_RLV = -1812221819; integer CHAN_ALLOW; // = llAbs((integer)("0x"+"(string)OWNER")); integer gOpenPing = FALSE; // are we listening for a pong response? // Trivial performance enhancement: list PIPE = ["|"]; list EMPTY_LIST = []; key OWNER; // the wearer of this relay. if D/s features are added, call the other party DOM or TRUSTED. integer check_rlv(string rlv,string suffix) { // check whether we like certain RLV commands or not. if(~llSubStringIndex(rlv,",")) return FALSE; // multiple RLV in one rlv_command seems out-of-spec. if( ("=y"==suffix) || ("=rem"==suffix) ) return TRUE; // undoing restrictions seems benign. if( "@failme"==rlv ) return FALSE; // for debugging and example. return TRUE; } integer check_restrictor(key ID,string cmd,list rlv_list) // adds if new and passes security; returns 1+ the restrictor index. { integer i; for(;i<MAX_RESTRICTORS;++i) { key r = llList2Key(gRestrictors,i); if(r==ID) { //llSay(0,"Debug: found old restrictor in index "+(string)i); return i+1; } } // before checking for an open slot, allow some commands without adding as an approved restrictor: if(1==llGetListLength(rlv_list)) { string rlv = llList2String(rlv_list,0); if((integer)llDeleteSubString(rlv,0,llSubStringIndex(rlv,"="))) // =<channel> commands. { llOwnerSay(rlv); llSay(CHAN_RLV,cmd+","+(string)ID+","+rlv+",ok"); // probably a bit verbose; the object should get a response from the viewer as well. return -1; // skip sendking ko response. }else if("!version"==rlv) //also handled by meta_command routine. { llSay(CHAN_RLV,cmd+","+(string)ID+",!version,"+SPEC_VERSION); return -1; }else if("!implversion"==rlv) // also handled by meta_command routine. { llSay(CHAN_RLV,cmd+","+(string)ID+",!implversion,"+IMPL_VERSION); return -1; }else if("!release"==rlv) // avoid giving out an error message when an untrusted object is trying to release. just do nothing. // this meta command is why it wouldn't be elegant to embed meta_command() up here. { return -1; }else if( ("@unsit=force"==rlv) && (ID==llList2Key(llGetObjectDetails(OWNER,[OBJECT_ROOT]),0)) ) // because some objects are nice and ask to kick us off after we safeword, that shouldn't count as 're-grabbing'. { llOwnerSay(rlv); llSay(CHAN_RLV,cmd+","+(string)ID+","+rlv+",ok"); // probably a bit verbose, does the object really care they successfully unsat us? return -1; } } if(gOpenPing==1) return FALSE; // do not add new restrictors in the opening moments after adding the relay/logging in. // check for an open slot: for(i=0;i<MAX_RESTRICTORS;++i) { if(llList2Key(gRestrictors,i)=="") { /* place any security check against the restrictor here, return FALSE if we don't like the restrictor. // for example, if(not a member of land group) return FALSE;. */ // ask owner (or dominant(s)) via dialog and slave script synchronicity: llLinksetDataDelete("@_Allow"); integer pass=1; string DOM = llLinksetDataRead("@_DOM"); if(DOM) { list DOM = llCSV2List(DOM); integer i = llGetListLength(DOM); while(~--i) { string DOM = llList2String(DOM,i); if(llGetAgentSize(DOM)) { if(llVecDist(llGetPos(),llList2Vector(llGetObjectDetails(DOM,[OBJECT_POS]),0))<20.0) { pass=0; // found a dominant, don't ask wearer's permission. llDialog(DOM,llKey2Name(ID)+" owned by secondlife:///app/agent/"+ (string)llGetOwnerKey(ID)+"/inspect would like to interface with secondlife:///app/agent/"+ (string)OWNER+"/inspect 's relay.", ["Allow","Deny"],CHAN_ALLOW); } } } } if(pass) { llDialog(OWNER,llKey2Name(ID)+" owned by secondlife:///app/agent/"+ (string)llGetOwnerKey(ID)+"/inspect would like to interface via the RLV-relay protocol.", ["Allow","Deny"],CHAN_ALLOW); } llResetTime(); while( (llGetTime()<20.0) && (llLinksetDataRead("@_Allow")=="") ){llSleep(0.5);} pass = (integer)llLinksetDataRead("@_Allow"); llLinksetDataDelete("@_Allow"); if(!pass) return FALSE; llOwnerSay("Debug: Adding new restrictor at index "+(string)i+" "+(string)ID+" : "+llKey2Name(ID)); gRestrictors = llListReplaceList(gRestrictors,[ID],i,i); return i+1; } } // we're full up. (could try a ping test here.) ping(); return FALSE; } rem_restrictor(key ID,integer index,string cmd) // if not via a !release command, let cmd = "safeword" or "not-found" { integer mask = 1<<index; gRestrictors = llListReplaceList(gRestrictors,[""],index,index); // we could take this in chunks, but I think this script is low-memory enough in general to handle a large number of restrictions. list restrictions = llLinksetDataFindKeys("^@[^_]",0,0); // find all keys starting with "@" but not "@_" integer i = llGetListLength(restrictions); while(~--i) { string prefix = llList2String(restrictions,i); apply_mask(prefix,mask,FALSE); } llSay(CHAN_RLV,cmd+","+(string)ID+",!release,ok"); // the spec says we have to say this even if there was no release request from the object. } rem_restrictors(list IDs,integer mask,string cmd) // more efficient for boot-up. { // expect calling location to clean up gRestrictors. integer i = llGetListLength(IDs); while(~--i) { string restrictor = llList2String(IDs,i); if(restrictor) // empty slots don't respond to ping, so are set to be removed. we don't need to talk to "" though. llSay(CHAN_RLV,cmd+","+restrictor+",!release,ok"); } list restrictions = llLinksetDataFindKeys("^@[^_]",0,0); // find all keys starting with "@" but not "@_" i = llGetListLength(restrictions); while(~--i) { string prefix = llList2String(restrictions,i); apply_mask(prefix,mask,FALSE); } } parse_message(string msg, key ID) { list msg = llCSV2List(msg); if((key)llList2String(msg,1) != OWNER) return; // message was not for us. string cmd = llList2String(msg,0); //if(cmd=="ping") return; // don't listen to pings from relays (superfluous) list rlv_list = llParseString2List(llList2String(msg,2),PIPE,EMPTY_LIST); integer index_restrictor = check_restrictor(ID,cmd,rlv_list); if(index_restrictor==-1) return; if(index_restrictor) { // check each restriction: integer i = llGetListLength(rlv_list); while(~--i) { string rlv = llList2String(rlv_list,i); integer ind_eq = llSubStringIndex(rlv,"="); string prefix = llDeleteSubString(rlv,ind_eq,-1); string suffix = llGetSubString(rlv,ind_eq,-1); if(check_rlv(rlv,suffix)) { if(llGetSubString(rlv,0,0)=="!") { meta_command(rlv, cmd, ID, index_restrictor-1); }else { llSay(CHAN_RLV,cmd+","+(string)ID+","+rlv+",ok"); llOwnerSay(rlv); add_rlv(prefix,suffix,index_restrictor-1); } }else { //llSay(0,"Debug: "+rlv+" didn't pass check!"); llSay(CHAN_RLV,cmd+","+(string)ID+","+rlv+",ko"); } } }else { // we don't like the restrictor, reply 'ko' to each rlv. llOwnerSay("Warning: "+llList2CSV(rlv_list)+" from a disliked restrictor!"); // mildly innefficient because of variable masking, oh well. integer i = llGetListLength(rlv_list); while(~--i) { string rlv = llList2String(rlv_list,i); llSay(CHAN_RLV,cmd+","+(string)ID+","+rlv+",ko"); } } } meta_command(string rlv, string cmd, key restrictor, integer index_res) { if("!release"==rlv) { rem_restrictor(restrictor,index_res,cmd); }else if("!version"==rlv) { llSay(CHAN_RLV,cmd+","+(string)restrictor+",!version,"+SPEC_VERSION); }else if("!pong"==rlv) { if(gOpenPing) { gMaskRestrictors = gMaskRestrictors | (1<<index_res); // we don't reply to !pong with an ok. //llSay(0,"Debug: Got pong from "+(string)index_res+" "+(string)restrictor); }else { // it didn't respond promptly; tell it to release: llSay(CHAN_RLV,"ping,"+(string)restrictor+"!release,ok"); //llSay(0,"Debug: The pong was too tardy!"); } }else if("!implversion"==rlv) { llSay(CHAN_RLV,cmd+","+(string)restrictor+",!implversion,"+IMPL_VERSION); } } add_rlv(string prefix, string suffix, integer index_restrictor) { if("=y"==suffix || "=rem"==suffix) { apply_mask(prefix, (1<<index_restrictor),FALSE); }else if("=n"==suffix || "=add"==suffix) { apply_mask(prefix, (1<<index_restrictor),TRUE); } // last 2 cases aren't strictly neccessary: else if("=force"==suffix || (integer)llDeleteSubString(suffix,0,0)) // { // do nothing, llOwnerSay was already executed. }else { llSay(0,"Error! RLV with erroneous suffix: '"+prefix+suffix+"'"); } } apply_mask(string rlv, integer mask, integer add) { if(add) { mask = mask | (integer)llLinksetDataRead(rlv); }else // remove { mask = ~mask & (integer)llLinksetDataRead(rlv); } if(mask) { llLinksetDataWrite(rlv,(string)mask); llOwnerSay(rlv+"=add"); }else { llLinksetDataDelete(rlv); llOwnerSay(rlv+"=rem"); } } string mask_to_list(integer n) { string ret; integer i = MAX_RESTRICTORS; while(~--i) { if(n&(1<<i)) { ret+=(string)i; }else { ret+="_"; } } return ret; } ping() { integer i = MAX_RESTRICTORS; while(~--i) { key res = llList2Key(gRestrictors,i); if(res) { llSay(CHAN_RLV,"ping,"+(string)res+",ping,ping"); //llSay(0,"Debug: pinging "+(string)i+" "+(string)res); gOpenPing=TRUE; gMaskRestrictors =0; } } llSetTimerEvent(gOpenPing*PING_DELAY); } default { state_entry() { OWNER= llGetOwner(); CHAN_ALLOW = llAbs((integer)("0x"+(string)OWNER)); string s = llLinksetDataRead("@_SAFEWORD"); if(s) SAFEWORD = s; // mildly innefficient, but it's done rarely: while(llGetListLength(gRestrictors)<MAX_RESTRICTORS) gRestrictors+=""; llListen(CHAN_RLV,"","",""); string DOM = llLinksetDataRead("@_DOM"); if(DOM) { list DOM = llCSV2List(DOM); integer i = llGetListLength(DOM); while(~--i) { llListen(0,"",llList2String(DOM,i),SAFEWORD); } } } touch_start(integer n) { llResetTime(); } changed(integer c) { if(c&CHANGED_TELEPORT) { ping(); } if(c&CHANGED_OWNER) { llResetScript(); //only need to change CHAN_ALLOW, but in an abundance of caution. } } touch_end(integer n) { //if(llDetectedTouchFace(0)!=2) return; if(llGetTime()>3.0) { llOwnerSay("Attempting Safeword."); string DOM = llLinksetDataRead("@_DOM"); integer allowSafeword= (integer)llLinksetDataRead("@_AloneSafewordAllowed"); if(DOM) { list DOM= llCSV2List(DOM); integer i = llGetListLength(DOM); while(~--i) { string DOM = llList2String(DOM,i); if(llVecDist(llGetPos(),llList2Vector(llGetObjectDetails(DOM,[OBJECT_POS]),0))<20.0) { allowSafeword=FALSE; llDialog(DOM,"secondlife:///app/agent/"+(string)OWNER+"/inspect is attempting safeword. To let them, say: '"+SAFEWORD+"'.",[SAFEWORD,"ignore"],0); }else { llInstantMessage(DOM,"secondlife:///app/agent/"+(string)OWNER+"/inspect is attempting safeword."); } } } if(allowSafeword) { llOwnerSay("Safeword Allowed."); rem_restrictors(gRestrictors,-1,"safeword"); llOwnerSay("@detach=y,unsit=force"); }else { llOwnerSay("Safeword Denied."); } }else { // list restrictors and restrictions. integer i = MAX_RESTRICTORS; while(~--i) { llOwnerSay("Restrictor "+(string)i+" is "+llKey2Name(llList2Key(gRestrictors,i))); } list restrictions = llLinksetDataFindKeys("^@[^_]",0,0); // starts with "@" as first character, but "_" is not the second character. i = llGetListLength(restrictions); while(~--i) { string restriction = llList2String(restrictions,i); string by = mask_to_list((integer)llLinksetDataRead(restriction)); llOwnerSay("> "+restriction+" is restricted by "+by+"."); } } } attach(key ID) { if(ID) { OWNER = ID; llOwnerSay("@detach=n,touchme=add"); ping(); } } timer() { llSetTimerEvent(0); gOpenPing=FALSE; list remove = gRestrictors; integer i = MAX_RESTRICTORS; while(~--i) { if((1<<i)&gMaskRestrictors) { remove = llDeleteSubList(remove,i,i); }else { gRestrictors = llListReplaceList(gRestrictors,[""],i,i); } } rem_restrictors(remove,~gMaskRestrictors,"not-found"); } listen(integer Channel, string Name, key ID, string Text) { //llOwnerSay(Text); if(Text==SAFEWORD) { llOwnerSay("Safeword Allowed."); rem_restrictors(gRestrictors,-1,"safeword"); llOwnerSay("@detach=y,unsit=force"); } parse_message(Text,ID); } } Complex and nitpicky, but it could be much worse. I have tested it a little bit and it should work in most situations, but YMMV. It should be noted that "safewording" this relay is a bit idiosyncratic. to safeword, either touch and hold the relay for 5 seconds, or have a listed dominant (see config) say your safeword. after a successful safeword, the HUD will be detachable; detaching it removes any restrictions it may have added.
  9. A little project I've been working on a bit and has come up in forum discussions every now and again, especially in relation to "real life" scaling of object dimensions. Halving your movement speed and moving the camera closer to the ground can lead to a more 'realistic' SL experience: float gResistance = -125.0; // large negative number. // -200 is more-or-less no-movement; -125 is my suggestion for normal human walking speed. integer gNeededPerms; integer gControls; integer gControlMove; integer gIsOn = TRUE; integer g1stPerson = FALSE; key owner; vector gShoulder; vector gHeight; default { state_entry() { gNeededPerms = PERMISSION_TAKE_CONTROLS| PERMISSION_CONTROL_CAMERA | PERMISSION_OVERRIDE_ANIMATIONS; gControls = CONTROL_FWD|CONTROL_BACK|CONTROL_LEFT|CONTROL_RIGHT|CONTROL_ROT_LEFT|CONTROL_ROT_RIGHT|CONTROL_UP|CONTROL_DOWN; gControlMove = CONTROL_FWD|CONTROL_BACK|CONTROL_LEFT|CONTROL_RIGHT; llRequestPermissions(llGetOwner(),gNeededPerms); owner = llGetOwner(); vector s = llGetAgentSize(owner); gHeight.z = s.z*0.5; } attach(key ID) { if(ID) { llRequestPermissions(ID,gNeededPerms); owner = ID; vector s = llGetAgentSize(ID); gHeight.z = s.z*0.5; } } touch_end(integer n) { integer face = llDetectedTouchFace(0); if(0==face) { gIsOn=!gIsOn; llTakeControls(gControls,gIsOn,TRUE); llSetCameraParams([CAMERA_ACTIVE, gIsOn]); llSetColor(<!gIsOn,gIsOn,0>,0); vector s = llGetAgentSize(owner); gHeight.z = s.z*0.5; }else if(1==face) { g1stPerson = !g1stPerson; llOwnerSay("Switching to "+llList2String(["3rd","1st"],g1stPerson)+" person preset."); llSetCameraParams( [ CAMERA_ACTIVE,gIsOn, CAMERA_POSITION_LAG, 0.15, CAMERA_BEHINDNESS_LAG, 0.3, CAMERA_BEHINDNESS_ANGLE, 10.0-(5.0*g1stPerson) ]); } } run_time_permissions(integer perm) { if(perm == gNeededPerms) { llSetAnimationOverride("Walking","D2048-Walk.N"); llTakeControls(gControls,TRUE,TRUE); llSetCameraParams( [ CAMERA_ACTIVE,TRUE, CAMERA_POSITION_LAG, 0.15, CAMERA_BEHINDNESS_LAG, 0.3, CAMERA_BEHINDNESS_ANGLE, 10.0-(5.0*g1stPerson) ]); llSetColor(<0,1,0>,0); }else { llOwnerSay("I cannot function without permissions (which should be auto-granted when attached)."); llDie(); } } control(key ID, integer level,integer edge) { if(!edge) return; // controls are the same as last frame, do nothing. // safely turn off if the wearer does something that would cause 'unexpected behavior': integer le = level&edge; // trivial efficiency gain. *shrugs* if( ( (le&CONTROL_UP) ) || // attempted jump ( (level&CONTROL_DOWN) && (le&gControlMove) ) || // attempted crawl ( le && (llGetAgentInfo(owner)&AGENT_FLYING) ) // flying. //(N.B. recall this event only happens when a control is held down.) // in all of the above cases, testing 'edge' is only neccessary to prevent double-firing the llOwnerSay(). ) { gIsOn=FALSE; llTakeControls(gControls,FALSE,TRUE); llSetCameraParams([CAMERA_ACTIVE,FALSE]); llSetForce(<0,0,0>,TRUE); llSetColor(<1,0,0>,0); llOwnerSay("Unsupported action disables speed and camera control."); return; } vector move = llVecNorm( // can remove the vecNorm for minor efficiency gain if you don't mind going faster diagonally. <-1, 0,0.0>*!(level&CONTROL_FWD) + < 1, 0,0.0>*!(level&CONTROL_BACK)+ < 0,-1,0.0>*!(level&CONTROL_LEFT)+ < 0, 1,0.0>*!(level&CONTROL_RIGHT)); vector turn = <0,-1,0>*!(level&CONTROL_ROT_LEFT)+ <0, 1,0>*!(level&CONTROL_ROT_RIGHT); float dist = 1.75 - (1.0*g1stPerson); if(level&CONTROL_BACK) // changing backwards movement speed messes up avatar facing when using an animation override. { llSetForce(<0,0,0>,TRUE); move=-move; turn=-turn; gShoulder = <0,0,0>; dist = 5.25 +(2.25*g1stPerson); }else { llSetForce((gResistance*move)+<0,0,4.5>,TRUE); // resist forward movemnt to slow down. (+z to help walking over small ledges.) // the +z and this patch to fix the patch shouldn't be ~neccessary if the ambient ledges are short enough. // +z over 9 or so will send you flying skyward. // prevent possible problems caused by 'staircase assist' positive z-force: vector vel = llGetVel(); if(vel.z>0) vel.z=0; llSetVelocity(vel,FALSE); } if(le&CONTROL_ROT_LEFT) { gShoulder = <0,0.40,0>; }else if(le&CONTROL_ROT_RIGHT) { gShoulder = <0,-0.40,0>; } if(move) { llSetCameraParams( [ CAMERA_FOCUS_OFFSET, ((0.75+0.75*g1stPerson)*move)+(0.75*turn)+gHeight+<0,0.0,-0.15>+(gShoulder*g1stPerson), CAMERA_DISTANCE, dist, CAMERA_PITCH, 7.5-(1.5*g1stPerson) ]); }else { llSetCameraParams( [ CAMERA_FOCUS_OFFSET, <4.5,0,0>+(1.25*turn)+<0,0,-0.25>+(gHeight*g1stPerson), CAMERA_DISTANCE, 5.25+(1.25*g1stPerson), CAMERA_PITCH, 12.0-(7.5*g1stPerson) ]); } } } Add it to a HUD and make sure faces 0 and 1 of the HUD are visible. button 0 is an on/off switch, 1 switches between 2 different camera presets. The main issue is that there is no "good" way to change your movement speed in SL. There are a few different methods, but they all have drawbacks. The above applies a backwards force when you walk forwards. This can cause issues when you do various things (like jumping/flying) so it disables everything if you do something likely to cause problems, so it's "safe" as long as you don't walk off a cliff. This is an older version which uses a different method, but runs into problems (walking into the air) in severely physics-lagged regions. It needs 2 separate scripts, but doesn't have any 'unsupported actions'. It's integrated with an AO, so it will take a bit of modification for personal use: Camera Override: integer gNeededPerms; default { state_entry() { gNeededPerms = PERMISSION_CONTROL_CAMERA|PERMISSION_TAKE_CONTROLS; llRequestPermissions(llGetOwner(),gNeededPerms); } attach(key ID) { if(ID) { llRequestPermissions(llGetOwner(),gNeededPerms); } }/* changed(integer c) { llOwnerSay((string)c); }*/ run_time_permissions(integer perms) { if(perms==gNeededPerms) { llTakeControls( CONTROL_FWD|CONTROL_BACK| CONTROL_LEFT|CONTROL_RIGHT| CONTROL_ROT_LEFT|CONTROL_ROT_RIGHT, TRUE,TRUE); llSetCameraParams( [ CAMERA_ACTIVE,TRUE, CAMERA_POSITION_LAG, 0.15, CAMERA_BEHINDNESS_LAG, 0.3 ]); }else { llOwnerSay("I cannot function correctly without permission."); } } control(key Who, integer level, integer edge) { if((level&edge)||(~level&edge)) { // a control has been pressed or released vector move = // removed vecnorm; longer diagonals are fine. <-1, 0,0.0>*!(level&CONTROL_FWD) + < 1, 0,0.0>*!(level&CONTROL_BACK)+ < 0,-1,0.0>*!(level&CONTROL_LEFT)+ < 0, 1,0.0>*!(level&CONTROL_RIGHT); vector turn = <0,-1,0>*!(level&CONTROL_ROT_LEFT)+ <0, 1,0>*!(level&CONTROL_ROT_RIGHT); /*if(llGetAgentInfo(Who)&AGENT_ON_OBJECT) { llClearCameraParams(); }else*/ if(move) { // rotate turn to be relative to forward direction: // (this is overkill because we should never be // strafing and turning simultaneously.) turn = <(move.x*turn.x)-(move.y*turn.y), (move.x*turn.y)+(move.y*turn.x), 0>; //move.x = llFabs(move.x); llSetCameraParams( [ CAMERA_ACTIVE,TRUE, CAMERA_FOCUS_OFFSET, (0.75*move)+(0.75*turn)+<0,0,0.50>, CAMERA_DISTANCE, 1.75, CAMERA_PITCH, 7.5 ]); }else { llSetCameraParams( [ CAMERA_ACTIVE,TRUE, CAMERA_FOCUS_OFFSET, <4.5,0,0>+(1.25*turn)+<0,0,-0.25>, CAMERA_DISTANCE, 5.25, CAMERA_PITCH, 12.0 ]); } } } link_message(integer SendersLink, integer Value, string Text, key ID) { if(Value==0) { llClearCameraParams(); llOwnerSay("Camera off."); }else { llSetCameraParams( [ CAMERA_ACTIVE,TRUE, CAMERA_POSITION_LAG, 0.15, CAMERA_BEHINDNESS_LAG, 0.3 ]); llOwnerSay("Camera On."); } } } Movement and animation Override: // Open Source, no particular license. Use and distribute. // extra features: check if current anim set is taken. integer gnSpeeds = 3; list gSpeeds = [1.25,7.0,15.0]; integer gSpeedIndex = 0; float gTimer = 0.2; // I'm a it too lenient perhaps. if you want, set it to 0.2 and multiply the Change Timer by 5.; integer gChangeTimer = 75; //15 seconds // number of timer ticks before changing stand/sit etc. list gStand= // still while not flying [ "01/BLAOshSt01_3", "02/BLAOlylST02_3", "02/BLAOslpSt02_3", "04/BLAOlylST04_3", "04/BLAONOst04_3", "04/BLAOslpSt04_3", "05/BLAOalySt05_3", "05/BLAObakStand05_3", "05/BLAOflcSt05_3", "09/BLAOrisST09_3", "10/BLAOfuzSt10_3", "10/BLAOnblSt10_3", "11/BLAOcaSt11_3", "11/BLAOeltSt11_3", "11/BLAOGrPst11_3", "11/BLAOSoSt11_3", "11/BLAOtriSt11_3", "11/BLAOwstSt11_3" ]; list gStandAFK = //standing still while AFK. [ "Flirt~Hover1 sexie" ]; list gHover= // still while in air [ "D2067-Hover.N" ]; list gFloat= // still while in water [ "D9002-Swim.H" ]; list gGroundSit = //Still while sitting with 'sit here' [ "D5514-Sit.G (155cm)","D5874-Sit.G (155cm)","D5875-Sit.G (155cm)", "D2170-Crouch (155cm)" ]; list gLedgeSit = // sitting on a prim or underscripted seat. [ "D2040-Sit.N" ]; list gTyping = ["D0089-Stand.N (Teen)", "D0063-Typing", "D2059-Typing", "D1502-Typing"];//"Stand 16"; //standing still while typing. string gWalking = "D2048-Walk.N"; string gRunning = "D6008-Running (155cm)"; string gRunning2 = "D3428-Running (155cm)"; string gCrouching = "D2060-Crouch (155cm)"; string gCrouchWalk = "!-DH- Crawl"; string gFlying = "D3436-Fly.N"; string gFlyingSlow = "D3436-Fly.N"; string gFlyingUp = "dz492-Hover.U";//"D2070-Hover.U"; string gFlyingDown = "27/BLAOSwFlyDown01_4"; //"D6021-Hover.D"; string gSwimming = "D9001-Swim.N"; string gSwimmingSlow = "D9001-Swim.N"; string gSwimmingUp = "D9003-Swim.U"; string gSwimmingDown = "D9004-Swim.D"; string gFalling = "27/BLAOshFall01_4";//"D2072-Falling"; string gLandingSoft = "23/BLAOSwLanding01_4"; //""; // "D2065-Land.N (155cm)"; // small distance // needs replaced! string gLandingHard = ""; // "D2065-Land.N (155cm)"; // large distance // needs replaced! //string gTakeOff = "Fly Up-1"; string gPreJumping = "21/BLAOSwPreJump01_4";//"D2061-Jump.P (155cm)"; string gJumping = "22/BLAOSwJump01_4";//"D2064-Jump.N"; string gLanding = "23/BLAOSwLanding01_4"; //"Stefani-SU"; // "D2065-Land.N (155cm)"; // after jump // needs replaced! string gTurnLeft = "13/BLAOKDturnL01_4"; //"D5997-Turn.L (155cm)"; string gTurnRight = "12/BLAOKDturnR01_4"; //"D5996-Turn.R (155cm)"; string gStriding = "D2040-Sit.N"; // wiki says "When the avatar is stuck on the edge of an object or on top of another avatar." typing_effect_start() { // throw some particles or whatever. return; } typing_effect_stop() { // stop whatever you did above. return; } integer uOverrideIfIs(string anim, string def, string set) { if(set=="") { return TRUE; // pretend we set the animation. } if(llGetAnimationOverride(anim)==def) { llSetAnimationOverride(anim,set); return TRUE; }else { llOwnerSay("Animation state "+anim+" is occupied by another AO."); return FALSE; } } integer gTimerCount; integer gChangeCount; integer gAirWater = 1; // 1 = air, 0 = water. integer gIsFlying; integer gIsTyping = FALSE; // TRUE when typing; integer gOn =TRUE; integer gNeededPerms; default { state_entry() { gNeededPerms = PERMISSION_OVERRIDE_ANIMATIONS| PERMISSION_TRIGGER_ANIMATION| PERMISSION_TAKE_CONTROLS| 0; llRequestPermissions(llGetOwner(),gNeededPerms); llSetColor(<0,1,0>,ALL_SIDES); } touch_start(integer _i) { gOn=++gOn&3; if(gOn&1) { if(llGetPermissions()&PERMISSION_TAKE_CONTROLS) { llTakeControls(CONTROL_FWD/*|CONTROL_BACK|CONTROL_LEFT|CONTROL_RIGHT*/,TRUE,FALSE); }else { llRequestPermissions(llGetOwner(),gNeededPerms); } llSetTimerEvent(gTimer); }else { if(llGetPermissions()&PERMISSION_TAKE_CONTROLS) { llReleaseControls(); } llSetTimerEvent(0.0); } if(gOn&2) { llSetScriptState("!!Cam-Override",TRUE); llSleep(0.1); llMessageLinked(LINK_THIS,TRUE,"",""); }else { llMessageLinked(LINK_THIS,FALSE,"",""); llSleep(0.1); llSetScriptState("!!Cam-Override",FALSE); } llSetColor(<!(gOn&1),(gOn&1),!(gOn&2)>,ALL_SIDES); } attach(key ID) { if(ID) { llRequestPermissions(llGetOwner(),gNeededPerms); } } control(key ID, integer level,integer edge) { if(level&edge&CONTROL_FWD) { if(llGetAndResetTime()<0.2) { ++gSpeedIndex; }else { gSpeedIndex=0; } if(gSpeedIndex>=(gnSpeeds-1)) { gSpeedIndex=(gnSpeeds-1); /*uOverrideIfIs("Running",gRunning,gRunning2);*/ }/*else { uOverrideIfIs("Running",gRunning2,gRunning); }*/ if(!gSpeedIndex) { //llOwnerSay("Force on"); llSetForce(<0,0,4.5>,FALSE); } }else if(~level&CONTROL_FWD) { llResetTime(); //llOwnerSay("Force off"); llSetForce(<0,0,0>,FALSE); } float speed = llList2Float(gSpeeds,gSpeedIndex); vector vel= speed*/*llVecNorm(*/ //superfluous since not using other directions. < 1, 0,0.0>/*!!(level&CONTROL_FWD) + <-1, 0,0.0>*!!(level&CONTROL_BACK)+ < 0, 1,0.0>*!!(level&CONTROL_LEFT)+ < 0,-1,0.0>*!!(level&CONTROL_RIGHT))*/; vector v = llGetVel(); vel.z=v.z; if(v.z>0) v.z=0; // try and prevent random flying walk. //llOwnerSay((string)vel); llSetVelocity(vel,TRUE); } run_time_permissions(integer perm) { if(perm& PERMISSION_TAKE_CONTROLS) { llTakeControls(CONTROL_FWD/*|CONTROL_BACK|CONTROL_LEFT|CONTROL_RIGHT*/,TRUE,FALSE); } if(perm&PERMISSION_OVERRIDE_ANIMATIONS) { llResetAnimationOverride("ALL"); // comment out this line if you want to check for a conflicting AO. integer bad = 0; // number of anims that were not overridden. bad+= !uOverrideIfIs("Standing","stand",llList2String(gStand,0)); bad+= !uOverrideIfIs("Hovering","hover",llList2String(gHover,0)); //float is not a anim state bad+= !uOverrideIfIs("Sitting on Ground","sit_ground_constrained",llList2String(gGroundSit,0)); bad+= !uOverrideIfIs("Sitting","sit",llList2String(gLedgeSit,0)); //typing is not an anim state bad+= !uOverrideIfIs("Walking","walk",gWalking); bad+= !uOverrideIfIs("Running","run",gRunning); bad+= !uOverrideIfIs("Crouching","crouch",gCrouching); bad+= !uOverrideIfIs("CrouchWalking","crouchwalk",gCrouchWalk); bad+= !uOverrideIfIs("Flying","fly",gFlying); bad+= !uOverrideIfIs("FlyingSlow","flyslow",gFlyingSlow); bad+= !uOverrideIfIs("Hovering Up","hover_up",gFlyingUp); bad+= !uOverrideIfIs("Hovering Down","hover_down",gFlyingDown); // swimming not an anim state. bad+= !uOverrideIfIs("Falling Down","falldown",gFalling); bad+= !uOverrideIfIs("Soft Landing","soft_land",gLandingSoft); bad+= !uOverrideIfIs("Standing Up","standup",gLandingHard); //bad+= !uOverrideIfIs("Taking Off","hover_up",gTakeOff); bad+= !uOverrideIfIs("PreJumping","prejump",gPreJumping); bad+= !uOverrideIfIs("Jumping","jump",gJumping); bad+= !uOverrideIfIs("Landing","land",gLanding); bad+= !uOverrideIfIs("Turning Left","turnleft",gTurnLeft); bad+= !uOverrideIfIs("Turning Right","turnright",gTurnRight); bad+= !uOverrideIfIs("Striding","stride",gStriding); if(bad!=0) { llOwnerSay("you seem to be wearing a conflicting AO.\n"); // if you're not in fact wearing a conflicting AO, // you can fix this by adding llResetAnimationOverride(); to state_entry(). }else { llSetTimerEvent(gTimer); //llOwnerSay("Started AO"); } } } timer() { integer mask = llGetAgentInfo(llGetOwner()); integer t; list standlist; if(mask&AGENT_AWAY) { standlist = gStandAFK; llSetAnimationOverride("Standing", llList2String(standlist,0)); //llStopAnimation("fd037134-85d4-f241-72c6-4f42164fedee"); }else if(t=(mask&AGENT_TYPING)) { standlist = gTyping; //llStopAnimation("c541c47f-e0c0-058b-ad1a-d6ae3a4584d9"); //Unnessesary if you have PlayTypingAnim turned off in your viewer's debug settings. }else { standlist = gStand; } if(gIsTyping!=t) { gIsTyping=t; if(t) { typing_effect_start(); llStopAnimation("type"); llSetAnimationOverride("Standing", llList2String(standlist,gChangeCount%llGetListLength(standlist))); }else { typing_effect_stop(); llSetAnimationOverride("Standing", llList2String(standlist,gChangeCount%llGetListLength(standlist))); } } if(++gTimerCount%gChangeTimer==0) { ++gChangeCount; integer index = gChangeCount%llGetListLength(standlist); if(!index) standlist = llListRandomize(standlist,1); string stand = llList2String(standlist,index); llSetAnimationOverride("Standing",stand); //llOwnerSay(stand); if(~mask&AGENT_SITTING) { llSetAnimationOverride("Sitting", llList2String(gLedgeSit,gChangeCount%llGetListLength(gLedgeSit))); llSetAnimationOverride("Sitting on Ground", llList2String(gGroundSit,gChangeCount%llGetListLength(gGroundSit))); } } if(gIsFlying!=(mask&AGENT_FLYING)) { gIsFlying=!gIsFlying; if(gIsFlying) { llReleaseControls(); vector pos = llGetPos(); integer airwater = pos.z > llWater(<0,0,0>); if(gAirWater != airwater) { gAirWater= airwater; if(airwater) { llSetAnimationOverride("Flying",gFlying); llSetAnimationOverride("FlyingSlow",gFlyingSlow); llSetAnimationOverride("Hovering Up",gFlyingUp); llSetAnimationOverride("Hovering Down",gFlyingDown); llSetAnimationOverride("Hovering", llList2String(gHover,gChangeCount%llGetListLength(gHover))); }else { llSetAnimationOverride("Flying",gSwimming); llSetAnimationOverride("FlyingSlow",gSwimmingSlow); llSetAnimationOverride("Hovering Up",gSwimmingUp); llSetAnimationOverride("Hovering Down",gSwimmingDown); llSetAnimationOverride("Hovering", llList2String(gFloat,gChangeCount%llGetListLength(gFloat))); } } }else { llRequestPermissions(llGetOwner(),gNeededPerms); } } } } // N.B. best to name this script "!!tessa's AO" or something else beginning with a ! so that it will appear first in the inventory list. //Possible efficiency improvements: // list-lengths as global vars calculated in state_entry(). // save last known swiming/flying state and don't reapply the override if unchanged *done* // get the water level once per region change? //Possible usability improvements: // notecard config. // ability to switch between different sets. (without moding script) // * not implementing those could be seen as a benefit. fewer anims in inventory means faster loading when editing. It turns out having a single control (touching the HUD anywhere) cycle through 2*2==4 states is more annoying in practice than I would have expected.
  10. someone added a bunch of link-specific versions of sound functions recently, without a lot of fanfare (I don't follow server update stuff, so maybe there was some and I just didn't hear.). Could have been a batch job and they, didn't actually test it?
  11. llLinkSetSoundRadius was added recently; anyone care to test and report it for the same bugs?
  12. If we really are talking about llBreakLink() I guess I'll mention that it's a fairly standard trick to have a separate state for requesting owner-only permissions before moving on to the 'regular operation of the script' state: integer neededPerms = PERMISSION_BREAK_LINKS; default { state_entry() { llRequestPermissions(llGetOwner(),neededPerms); } touch_start(integer n) { llRequestPermissions(llGetOwner(),neededPerms); } run_time_permissions(integer perms) { if(perms==neededPerms) state running; } } state running { touch_start(integer n) { llBreakLink(1); } }
  13. Don't google before directly reading the documentation: https://wiki.secondlife.com/wiki/LSL_Protocol/RestrainedLoveAPI There aren't many, but there are a few specific examples in the documentation that make the usage clear.
  14. you're not supposed to include '#RLV' in the folder path, and A) don't use back slashes in your folder names, B) if your folder names actually have back slashes in them, the slashes need to be doubled in code: llOwnerSay("@attachallover:other/cuffs2=force"); or if your folders really are named \other and \cuffs2: llOwnerSay("@attachallover:\\other/\\cuffs2=force"); P.S. your top level RLV folder is indeed supposed to be UPPER_CASE: '#RLV'
  15. Non-RLV: default { touch_start(integer n) { llRequestPermissions(llGetOwner(),PERMISSION_ATTACH); } run_time_permissions(integer perm) { if(perm&PERMISSION_ATTACH) llDetachFromAvatar(); } } RLV: default { touch_start(integer n) { llOwnerSay("@detach=force"); } }
  16. 40 for mainland, 100 for (full region) private estate IIRC. That's before premium perks are considered.
  17. off-topic, but as I understand it, things got more complicated when LL allowed you to upload profile pics directly to the web-profile instead of using an in-world texture. It's not ideal, but I wouldn't call it useless. Many/most in-world visitor boards use it, and I used it as part of an in-world stamp collecting game to make viewing your stamp collection a bit less painful. 256x192 is plenty big for something that only takes up a portion of the page, and you can re-size dimensions of the image where it's displayed in html.
  18. That's what I demonstrated in my above post. basically, when you request the file via javascript, it's lazy and ignores mime types. When you get the data, you set the 'innerHTML' property of some element to whatever arbitrary text you receive and it updates the page as if that portion of (x)HTML had been there all along: so, pre-javascript: // Main page XHTML: <!DOCTYPE xhtml> <html xmlns=\"http://www.w3.org/1999/xhtml\"> <head> <script type=\"text/javascript\"> //<![CDATA[ See above for function code //]]> </script></head> <body> <div w3-include-html=\"style.css\" /> <div w3-include-html=\"test.txt\" /> <script>includeHTML();</script> </body></html> // style.css: <style>body { font-size: 2.5vw; }</style> // test.txt: Some text post-javascript: // Main page XHTML: <!DOCTYPE xhtml> <html xmlns=\"http://www.w3.org/1999/xhtml\"> <head> <script type=\"text/javascript\"> //<![CDATA[ See above for function code //]]> </script></head> <body> <div><style>body {font-size: 2.5vw; } </style> </div> <div> Some text </div> <script>includeHTML();</script> </body></html> Adding an in-line stylesheet like that actually works surprisingly. My train-of-throught that lead me to the method was supposing the CSS was just part of the 'main page' and other content would be loaded in later.
  19. Also, not too hard to find a solution to include things via javascript. I'm sure there are better methods, but this is one I stumbled on that seemed somewhat reasonable, poked into a simple notecard displaying script: string gNCText; integer gNCLine; string gNCName; key gNCKey; key ghNC; key ghRequestURL; string gsURL; string gPageText = "<!DOCTYPE xhtml> <html xmlns=\"http://www.w3.org/1999/xhtml\"> <head> <script type=\"text/javascript\"> //<![CDATA[ // from https://www.w3schools.com/howto/howto_html_include.asp function includeHTML() { var z, i, elmnt, file, xhttp; /* Loop through a collection of all HTML elements: */ z = document.getElementsByTagName(\"*\"); for (i = 0; i < z.length; i++) { elmnt = z[i]; /*search for elements with a certain atrribute:*/ file = elmnt.getAttribute(\"w3-include-html\"); if (file) { /* Make an HTTP request using the attribute value as the file name: */ xhttp = new XMLHttpRequest(); xhttp.onreadystatechange = function() { if (this.readyState == 4) { if (this.status == 200) {elmnt.innerHTML = this.responseText;} if (this.status == 404) {elmnt.innerHTML = \"Page not found.\";} /* Remove the attribute, and call this function once more: */ elmnt.removeAttribute(\"w3-include-html\"); includeHTML(); } } xhttp.open(\"GET\", file, true); xhttp.send(); /* Exit the function: */ return; } } } //]]> </script></head> <body> <div w3-include-html=\"style.css\" /> <div w3-include-html=\"test.txt\" /> <script>includeHTML();</script> </body></html>"; default { state_entry() { gNCName = llGetInventoryName(INVENTORY_NOTECARD,0); gNCKey = llGetInventoryKey(gNCName); ghNC = llGetNotecardLine(gNCName,gNCLine); llSay(0, "initializing"); } on_rez(integer i) { llResetScript(); } changed(integer c) { if(c&CHANGED_INVENTORY) { key ncKey = llGetInventoryKey(gNCName); if(ncKey!=gNCKey) { gNCText = ""; gNCLine=0; gNCKey=ncKey; ghNC = llGetNotecardLine(gNCName,gNCLine); } } if(c&(CHANGED_REGION_START|CHANGED_REGION)) { llReleaseURL(gsURL); ghRequestURL = llRequestURL(); } } dataserver(key ID, string data) { if(ID==ghNC) { if(data==EOF) { ghRequestURL = llRequestURL(); }else { gNCText+="<p>"+data+"</p>"; ghNC = llGetNotecardLine(gNCName,++gNCLine); } } } http_request(key ID, string Method, string Body) { if(ID==ghRequestURL) { gsURL=Body; llSetLinkMedia(LINK_THIS,2, [ PRIM_MEDIA_HOME_URL, gsURL+"/home", PRIM_MEDIA_CURRENT_URL, gsURL+"/home", PRIM_MEDIA_AUTO_PLAY, TRUE, PRIM_MEDIA_PERMS_CONTROL, PRIM_MEDIA_PERM_NONE, // don't show nav-bar. PRIM_MEDIA_PERMS_INTERACT, PRIM_MEDIA_PERM_NONE ]); }else { string page = llGetHTTPHeader(ID,"x-path-info"); if("/home"==page) { llSetContentType(ID,CONTENT_TYPE_XHTML); llHTTPResponse(ID,200,gPageText); }else if("/test.txt"==page) { llSetContentType(ID,CONTENT_TYPE_TEXT); llHTTPResponse(ID,200,gNCText); }else if("/style.css"==page) { llSetContentType(ID,CONTENT_TYPE_TEXT); llHTTPResponse(ID,200, "<style>body { font-size: 2.5vw; }</style>"); } } } } I should mention, just a proof of concept. Obviously in an actual application, you would store long string variables in LSD, and/or pass messages from other scripts for extra memory.
  20. Actually you can, but they get served as a really bizarre 256x192 resolution. http://secondlife.com/app/image/571d44bb-8e5f-a579-eaec-cdb0bd109b29/1
  21. AFAIK most eyebrows that are good come pre-built into the head skin or packaged with the skin. the Enfer-sombre "runa" skin came with separate tintable eyebrows which I'm using and am rather happy with.
  22. If you don't need collision detection scripts, you could probably solve your problems by linking them and making the 'phantom prim' physics-type: null.
×
×
  • Create New...