/accept-animation-from-agent/accept-animation-from-agent.lsl |
@@ -0,0 +1,325 @@ |
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) Wizardry and Steamworks 2016 - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// |
// This is an automatic animation responder for the Corrade Second Life / |
// OpenSim bot that will accept to trigger animations - such as hugs sent |
// by various HUDs in SecondLife. You can find more details about the bot |
// by following the URL: http://was.fm/secondlife/scripted_agents/corrade |
// |
// The script works together with a "configuration" notecard and an that |
// must be placed in the same primitive as this script. The purpose of |
// this script is to demonstrate accepting animations with Corrade and |
// you are free to use, change, and commercialize it under the GNU/GPLv3 |
// license at: http://www.gnu.org/licenses/gpl.html |
// |
/////////////////////////////////////////////////////////////////////////// |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueGet(string k, string data) { |
if(llStringLength(data) == 0) return ""; |
if(llStringLength(k) == 0) return ""; |
list a = llParseString2List(data, ["&", "="], []); |
integer i = llListFindList(a, [ k ]); |
if(i != -1) return llList2String(a, i+1); |
return ""; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueEncode(list data) { |
list k = llList2ListStrided(data, 0, -1, 2); |
list v = llList2ListStrided(llDeleteSubList(data, 0, 0), 0, -1, 2); |
data = []; |
do { |
data += llList2String(k, 0) + "=" + llList2String(v, 0); |
k = llDeleteSubList(k, 0, 0); |
v = llDeleteSubList(v, 0, 0); |
} while(llGetListLength(k) != 0); |
return llDumpList2String(data, "&"); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// escapes a string in conformance with RFC1738 |
string wasURLEscape(string i) { |
string o = ""; |
do { |
string c = llGetSubString(i, 0, 0); |
i = llDeleteSubString(i, 0, 0); |
if(c == "") jump continue; |
if(c == " ") { |
o += "+"; |
jump continue; |
} |
if(c == "\n") { |
o += "%0D" + llEscapeURL(c); |
jump continue; |
} |
o += llEscapeURL(c); |
@continue; |
} while(i != ""); |
return o; |
} |
|
// corrade data |
string CORRADE = ""; |
string GROUP = ""; |
string PASSWORD = ""; |
|
// for holding the callback URL |
string callback = ""; |
|
// for notecard reading |
integer line = 0; |
|
// key-value data will be read into this list |
list tuples = []; |
|
default { |
state_entry() { |
if(llGetInventoryType("configuration") != INVENTORY_NOTECARD) { |
llOwnerSay("Sorry, could not find an inventory notecard."); |
return; |
} |
// DEBUG |
llOwnerSay("Reading configuration file..."); |
llGetNotecardLine("configuration", line); |
} |
dataserver(key id, string data) { |
if(data == EOF) { |
// invariant, length(tuples) % 2 == 0 |
if(llGetListLength(tuples) % 2 != 0) { |
llOwnerSay("Error in configuration notecard."); |
return; |
} |
CORRADE = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"corrade" |
] |
) |
+1 |
); |
if(CORRADE == "") { |
llOwnerSay("Error in configuration notecard: corrade"); |
return; |
} |
GROUP = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"group" |
] |
) |
+1 |
); |
if(GROUP == "") { |
llOwnerSay("Error in configuration notecard: group"); |
return; |
} |
PASSWORD = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"password" |
] |
) |
+1 |
); |
if(PASSWORD == "") { |
llOwnerSay("Error in configuration notecard: password"); |
return; |
} |
// DEBUG |
llOwnerSay("Read configuration file..."); |
state url; |
} |
if(data == "") jump continue; |
integer i = llSubStringIndex(data, "#"); |
if(i != -1) data = llDeleteSubString(data, i, -1); |
list o = llParseString2List(data, ["="], []); |
// get rid of starting and ending quotes |
string k = llDumpList2String( |
llParseString2List( |
llStringTrim( |
llList2String( |
o, |
0 |
), |
STRING_TRIM), |
["\""], [] |
), "\""); |
string v = llDumpList2String( |
llParseString2List( |
llStringTrim( |
llList2String( |
o, |
1 |
), |
STRING_TRIM), |
["\""], [] |
), "\""); |
if(k == "" || v == "") jump continue; |
tuples += k; |
tuples += v; |
@continue; |
llGetNotecardLine("configuration", ++line); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state url { |
state_entry() { |
// DEBUG |
llOwnerSay("Requesting URL..."); |
llRequestURL(); |
} |
http_request(key id, string method, string body) { |
if(method != URL_REQUEST_GRANTED) return; |
callback = body; |
// DEBUG |
llOwnerSay("Got URL..."); |
state detect; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state detect { |
state_entry() { |
// DEBUG |
llOwnerSay("Detecting if Corrade is online..."); |
llSetTimerEvent(5); |
} |
timer() { |
llRequestAgentData((key)CORRADE, DATA_ONLINE); |
} |
dataserver(key id, string data) { |
if(data != "1") { |
// DEBUG |
llOwnerSay("Corrade is not online, sleeping..."); |
llSetTimerEvent(30); |
return; |
} |
state notify; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state notify { |
state_entry() { |
// DEBUG |
llOwnerSay("Binding to the permission Corrade notification..."); |
llInstantMessage( |
(key)CORRADE, |
wasKeyValueEncode( |
[ |
"command", "notify", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"action", "set", |
"type", "permission", |
"URL", wasURLEscape(callback), |
"callback", wasURLEscape(callback) |
] |
) |
); |
llSetTimerEvent(60); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) != "notify" || |
wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Failed to bind to the permission notification..."); |
state detect; |
} |
// DEBUG |
llOwnerSay("Permission notification installed..."); |
llSetTimerEvent(0); |
state main; |
} |
timer() { |
llSetTimerEvent(0); |
// DEBUG |
llOwnerSay("Timeout binding to permission notification..."); |
state detect; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state main { |
state_entry() { |
// DEBUG |
llOwnerSay("Waiting for animation requests..."); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("type", body) != "permission" || |
wasKeyValueGet("permissions", body) != "TriggerAnimation") |
return; |
|
// DEBUG |
llOwnerSay("Corrade received the permission request to trigger an animation, replying..."); |
|
llInstantMessage((key)CORRADE, |
wasKeyValueEncode( |
[ |
"command", "replytoscriptpermissionrequest", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"item", wasKeyValueGet("item", body), |
"task", wasKeyValueGet("task", body), |
"action", "reply", |
"permissions", "TriggerAnimation", |
"region", wasKeyValueGet("region", body), |
"callback", wasURLEscape(callback) |
] |
) |
); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
/automated-hunt-system/automated-hand-system.lsl |
@@ -0,0 +1,755 @@ |
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// |
// This is an automated hunt system template that illustrates various |
// Corrade commands. You can find out more about the Corrade bot by |
// following the URL: http://was.fm/secondlife/scripted_agents/corrade |
// |
// This script requires the following Corrade permissions: |
// - movement |
// - notifications |
// - interact |
// - inventory |
// - economy |
// It also requires the following Corrade notifications: |
// - permission |
// |
// The template uses the "[WaS-K] Coin Bag" hunt item and Corrade must have |
// the "[WaS-K] Coin Bag" object in its inventory. Other hunt objects are |
// possible by setting "item" in the "configuration" notecard but the item |
// must be sent to Corrade such that it is in the bot's inventory. |
// |
// The "configuration" notecard inside the primitive must be changed to |
// reflect your settings. |
// |
// In case of panic, please see the full instructions on the project page: |
// http://grimore.org/secondlife/scripted_agents/corrade/projects/in_world/automated_hunt_system |
// or ask for help in the [Wizardry and Steamworks]:Support group or contact |
// Kira Komarov in-world directly. |
// |
// This script works together with a "configuration" notecard that must be |
// placed in the same primitive as this script. The purpose of this script |
// to demonstrate an automated hunt system using Corrade and you are free |
// to use, change, and commercialize it under the GNU/GPLv3 license at: |
// http://www.gnu.org/licenses/gpl.html |
// |
/////////////////////////////////////////////////////////////////////////// |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueGet(string k, string data) { |
if(llStringLength(data) == 0) return ""; |
if(llStringLength(k) == 0) return ""; |
list a = llParseString2List(data, ["&", "="], []); |
integer i = llListFindList(a, [ k ]); |
if(i != -1) return llList2String(a, i+1); |
return ""; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueEncode(list data) { |
list k = llList2ListStrided(data, 0, -1, 2); |
list v = llList2ListStrided(llDeleteSubList(data, 0, 0), 0, -1, 2); |
data = []; |
do { |
data += llList2String(k, 0) + "=" + llList2String(v, 0); |
k = llDeleteSubList(k, 0, 0); |
v = llDeleteSubList(v, 0, 0); |
} while(llGetListLength(k) != 0); |
return llDumpList2String(data, "&"); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
integer wasListCountExclude(list input, list exclude) { |
if(llGetListLength(input) == 0) return 0; |
if(llListFindList(exclude, (list)llList2String(input, 0)) == -1) |
return 1 + wasListCountExclude(llDeleteSubList(input, 0, 0), exclude); |
return wasListCountExclude(llDeleteSubList(input, 0, 0), exclude); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// escapes a string in conformance with RFC1738 |
string wasURLEscape(string i) { |
string o = ""; |
do { |
string c = llGetSubString(i, 0, 0); |
i = llDeleteSubString(i, 0, 0); |
if(c == "") jump continue; |
if(c == " ") { |
o += "+"; |
jump continue; |
} |
if(c == "\n") { |
o += "%0D" + llEscapeURL(c); |
jump continue; |
} |
o += llEscapeURL(c); |
@continue; |
} while(i != ""); |
return o; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// unescapes a string in conformance with RFC1738 |
string wasURLUnescape(string i) { |
return llUnescapeURL( |
llDumpList2String( |
llParseString2List( |
llDumpList2String( |
llParseString2List( |
i, |
["+"], |
[] |
), |
" " |
), |
["%0D%0A"], |
[] |
), |
"\n" |
) |
); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasListToCSV(list l) { |
list v = []; |
do { |
string a = llDumpList2String( |
llParseStringKeepNulls( |
llList2String( |
l, |
0 |
), |
["\""], |
[] |
), |
"\"\"" |
); |
if(llParseStringKeepNulls( |
a, |
[" ", ",", "\n", "\""], [] |
) != |
(list) a |
) a = "\"" + a + "\""; |
v += a; |
l = llDeleteSubList(l, 0, 0); |
} while(l != []); |
return llDumpList2String(v, ","); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
list wasCSVToList(string csv) { |
list l = []; |
list s = []; |
string m = ""; |
do { |
string a = llGetSubString(csv, 0, 0); |
csv = llDeleteSubString(csv, 0, 0); |
if(a == ",") { |
if(llList2String(s, -1) != "\"") { |
l += m; |
m = ""; |
jump continue; |
} |
m += a; |
jump continue; |
} |
if(a == "\"" && llGetSubString(csv, 0, 0) == a) { |
m += a; |
csv = llDeleteSubString(csv, 0, 0); |
jump continue; |
} |
if(a == "\"") { |
if(llList2String(s, -1) != a) { |
s += a; |
jump continue; |
} |
s = llDeleteSubList(s, -1, -1); |
jump continue; |
} |
m += a; |
@continue; |
} while(csv != ""); |
// postcondition: length(s) = 0 |
return l + m; |
} |
|
|
// corrade data |
string CORRADE = ""; |
string GROUP = ""; |
string PASSWORD = ""; |
// holds the name of the item to rez |
string ITEM = ""; |
|
// for holding the callback URL |
string callback = ""; |
|
// for notecard reading |
integer line = 0; |
|
// key-value data will be read into this list |
list tuples = []; |
|
// holds the location of hunt items |
list POI = []; |
list poi = []; |
|
default { |
state_entry() { |
llSetText("", <1, 1, 1>, 1.0); |
if(llGetInventoryType("configuration") != INVENTORY_NOTECARD) { |
llOwnerSay("Sorry, could not find a configuration inventory notecard."); |
return; |
} |
// DEBUG |
llOwnerSay("Reading configuration file..."); |
llGetNotecardLine("configuration", line); |
} |
dataserver(key id, string data) { |
if(data == EOF) { |
// invariant, length(tuples) % 2 == 0 |
if(llGetListLength(tuples) % 2 != 0) { |
llOwnerSay("Error in configuration notecard."); |
return; |
} |
CORRADE = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"corrade" |
] |
) |
+1 |
); |
if(CORRADE == "") { |
llOwnerSay("Error in configuration notecard: corrade"); |
return; |
} |
GROUP = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"group" |
] |
) |
+1 |
); |
if(GROUP == "") { |
llOwnerSay("Error in configuration notecard: group"); |
return; |
} |
PASSWORD = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"password" |
] |
) |
+1 |
); |
if(PASSWORD == "") { |
llOwnerSay("Error in configuration notecard: password"); |
return; |
} |
ITEM = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"item" |
] |
) |
+1 |
); |
if(ITEM == "") { |
llOwnerSay("Error in configuration notecard: item"); |
return; |
} |
|
// BEGIN POI |
integer i = llGetListLength(tuples)-1; |
do { |
string n = llList2String(tuples, i); |
if(llSubStringIndex(n, "POI_") != -1) { |
list l = llParseString2List(n, ["_"], []); |
if(llList2String(l, 0) == "POI") { |
integer x = llList2Integer( |
l, |
1 |
)-1; |
// extend the polygon to the number of points |
while(llGetListLength(POI) < x) |
POI += ""; |
// and insert the point at the location |
POI = llListReplaceList( |
POI, |
(list)( |
(vector)( |
"<" + llList2CSV( |
llParseString2List( |
llList2String( |
tuples, |
llListFindList( |
tuples, |
(list)n |
) |
+1 |
), |
["<", ",", ">"], |
[] |
) |
) + ">") |
), |
x, |
x |
); |
} |
} |
} while(--i>-1); |
// now clean up any empty slots |
i = llGetListLength(POI)-1; |
do { |
if(llList2String(POI, i) == "") |
POI = llDeleteSubList(POI, i, i); |
} while(--i > -1); |
// END POI |
|
// DEBUG |
llOwnerSay("Read configuration notecard..."); |
state url; |
} |
if(data == "") jump continue; |
integer i = llSubStringIndex(data, "#"); |
if(i != -1) data = llDeleteSubString(data, i, -1); |
list o = llParseString2List(data, ["="], []); |
// get rid of starting and ending quotes |
string k = llDumpList2String( |
llParseString2List( |
llStringTrim( |
llList2String( |
o, |
0 |
), |
STRING_TRIM), |
["\""], [] |
), "\""); |
string v = llDumpList2String( |
llParseString2List( |
llStringTrim( |
llList2String( |
o, |
1 |
), |
STRING_TRIM), |
["\""], [] |
), "\""); |
if(k == "" || v == "") jump continue; |
tuples += k; |
tuples += v; |
@continue; |
llGetNotecardLine("configuration", ++line); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state url { |
state_entry() { |
// DEBUG |
llOwnerSay("Requesting URL..."); |
llRequestURL(); |
} |
http_request(key id, string method, string body) { |
if(method != URL_REQUEST_GRANTED) return; |
callback = body; |
// DEBUG |
llOwnerSay("Got URL..."); |
state detect; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state detect { |
state_entry() { |
// DEBUG |
llOwnerSay("Detecting if Corrade is online..."); |
llSetTimerEvent(1); |
} |
timer() { |
llRequestAgentData((key)CORRADE, DATA_ONLINE); |
} |
dataserver(key id, string data) { |
if(data != "1") { |
// DEBUG |
llOwnerSay("Corrade is not online, sleeping..."); |
llSetTimerEvent(5); |
return; |
} |
state permission; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
|
state permission { |
state_entry() { |
// DEBUG |
llOwnerSay("Binding to the permission notification..."); |
llInstantMessage(CORRADE, |
wasKeyValueEncode( |
[ |
"command", "notify", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"action", "set", |
"type", "permission", |
"URL", wasURLEscape(callback), |
"callback", wasURLEscape(callback) |
] |
) |
); |
llSetTimerEvent(60); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) != "notify" || |
wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Failed to bind to the permission notification: " + |
wasURLUnescape( |
wasKeyValueGet( |
"error", |
body |
) |
) |
); |
return; |
} |
// DEBUG |
llOwnerSay("Permission notification installed..."); |
state menu; |
} |
timer() { |
// alarm hit, permission notification not installed |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
|
state menu { |
state_entry() { |
llSetText("Touch me for menu!", <0, 1, 0>, 1.0); |
} |
touch_start(integer num) { |
if(llDetectedKey(0) != llGetOwner()) return; |
integer comChannel = (integer)("0x8" + llGetSubString(llGetKey(), 0, 6)); |
llListen(comChannel, "", llGetOwner(), ""); |
llDialog(llGetOwner(), "The menu will allow you to cast and dispel the hunt items.", [ "Cast", "Dispel" ], comChannel); |
} |
listen(integer channel, string name, key id, string message) { |
/* Copy the POI list to recurse over. */ |
poi = POI; |
/* Process the dialog messages. */ |
if(message == "Cast") state rez; |
if(message == "Dispel") state derez; |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
|
/* |
* In order to rez all the items we permute the "poi" list by recursing over states. |
* The "rez_trampoline" provides a trampoline for the "rez" state re-entry. |
*/ |
state rez_trampoline { |
state_entry() { |
llSetTimerEvent(1); |
} |
timer() { |
state rez; |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
|
/* |
* Rez the hunt item from inventory, grant debit permission and trampoline for the |
* next item in the POI list. |
*/ |
state rez { |
state_entry() { |
// If we have rezzed all the objects, then stop rezzing. |
if(llGetListLength(poi) == 0) state menu; |
llSetText("Hunt items left to set-up: " + |
(string) |
llGetListLength( |
poi |
), |
<0, 1, 1>, |
1.0 |
); |
// Permute POIs |
string head = llList2String(poi, 0); |
poi = llDeleteSubList(poi, 0, 0); |
|
// DEBUG |
llOwnerSay("Rezzing @ " + head); |
|
llInstantMessage(CORRADE, |
wasKeyValueEncode( |
[ |
"command", "rez", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"position", wasURLEscape(head), |
"item", wasURLEscape(ITEM), |
"callback", wasURLEscape(callback) |
] |
) |
); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
// Get the result of rezzing the object. |
if(wasKeyValueGet("command", body) == "rez") { |
if(wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Failed to rez the object: " + |
wasURLUnescape( |
wasKeyValueGet( |
"error", |
body |
) |
) |
); |
return; |
} |
llOwnerSay("Item rezzed..."); |
return; |
} |
// Grant debit permissions to the rezzed object. |
if(wasKeyValueGet("type", body) == "permission" && |
wasKeyValueGet("permissions", body) == "Debit") { |
llInstantMessage(CORRADE, |
wasKeyValueEncode( |
[ |
"command", "replytoscriptpermissionrequest", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"task", wasKeyValueGet("task", body), |
"item", wasKeyValueGet("item", body), |
"region", wasKeyValueGet("region", body), |
"action", "reply", |
"permissions", "Debit", |
"callback", wasURLEscape(callback) |
] |
) |
); |
// DEBUG |
llOwnerSay("Replying to permission request..."); |
return; |
} |
// Get the result of granting script permissions. |
if(wasKeyValueGet("command", body) == "replytoscriptpermissionrequest") { |
if(wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Failed to grant permissions to the object: " + |
wasURLUnescape( |
wasKeyValueGet( |
"error", |
body |
) |
) |
); |
return; |
} |
llOwnerSay("Permissions granted..."); |
// Go for the next item in the POI list. |
state rez_trampoline; |
} |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
/* |
* In order to de-rez the hunt items we first teleport Corrade in the vicinity |
* of the POI and then issue a "derez" command to Corrade. |
* Symmetrically to "rez", the "derez_trampoline" state provides a trampoline |
* for the "derez" state re-entry. |
*/ |
state derez_trampoline { |
state_entry() { |
llSetTimerEvent(1); |
} |
timer() { |
state derez; |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
|
state derez { |
state_entry() { |
// If we have derezzed all the objects, then stop rezzing. |
if(llGetListLength(poi) == 0) state menu; |
llSetText("Hunt items left to remove: " + |
(string) |
llGetListLength( |
poi |
), |
<0, 1, 1>, |
1.0 |
); |
// Permute POIs |
string head = llList2String(poi, 0); |
poi = llDeleteSubList(poi, 0, 0); |
// DEBUG |
llOwnerSay("Teleporting to: " + (string)head); |
llInstantMessage((key)CORRADE, |
wasKeyValueEncode( |
[ |
"command", "teleport", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"region", wasURLEscape(llGetRegionName()), |
"position", wasURLEscape(head), |
"entity", "region", |
"fly", "True", |
"callback", wasURLEscape(callback) |
] |
) |
); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
// Get the result of teleporting to the POI. |
if(wasKeyValueGet("command", body) == "teleport") { |
// If the teleport did not succeed and the error was not that the destination |
// was too close, then print the error and stop; otherwise, continue. |
if(wasKeyValueGet("success", body) != "True" && |
wasKeyValueGet("status", body) != "37559") { |
// DEBUG |
llOwnerSay("Failed to teleport: " + |
wasURLUnescape( |
wasKeyValueGet( |
"error", |
body |
) |
) |
); |
return; |
} |
// DEBUG |
llOwnerSay("Teleport succeeded..."); |
// If the teleport succeeded, request to derez the item. |
llInstantMessage((key)CORRADE, |
wasKeyValueEncode( |
[ |
"command", "derez", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"item", wasURLEscape(ITEM), |
"range", 5, |
"callback", wasURLEscape(callback) |
] |
) |
); |
return; |
} |
// Get the result of the derez request. |
if(wasKeyValueGet("command", body) == "derez") { |
// If removing the item because the item was not found, then it was |
// probably consumed during the hunt so carry on to the next destination. |
if(wasKeyValueGet("success", body) != "True" && |
wasKeyValueGet("status", body) != "22693") { |
// DEBUG |
llOwnerSay("Failed to derez: " + |
wasURLUnescape( |
wasKeyValueGet( |
"error", |
body |
) |
) |
); |
return; |
} |
// DEBUG |
llOwnerSay("Derez succeeded..."); |
state derez_trampoline; |
} |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
/avatar-shape-gender-detector/avatar-shape-gender-detector.lsl |
@@ -0,0 +1,473 @@ |
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// |
// This is a script that uses the Corrade Second Life / OpenSim bot which |
// is able to detect the gender of avatar shapes. You can find more details |
// about Corrade at: http://grimore.org/secondlife/scripted_agents/corrade |
// |
// This script works together with a "configuration" notecard that must be |
// placed in the same primitive as this script. The purpose of this script |
// is to demonstrate detecting avatar genders with Corrade and you are free |
// to use, change, and commercialize it provided that you follow the terms |
// of the GNU/GPLv3 license at: http://www.gnu.org/licenses/gpl.html |
// |
/////////////////////////////////////////////////////////////////////////// |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueGet(string k, string data) { |
if(llStringLength(data) == 0) return ""; |
if(llStringLength(k) == 0) return ""; |
list a = llParseString2List(data, ["&", "="], []); |
integer i = llListFindList(a, [ k ]); |
if(i != -1) return llList2String(a, i+1); |
return ""; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueEncode(list data) { |
list k = llList2ListStrided(data, 0, -1, 2); |
list v = llList2ListStrided(llDeleteSubList(data, 0, 0), 0, -1, 2); |
data = []; |
do { |
data += llList2String(k, 0) + "=" + llList2String(v, 0); |
k = llDeleteSubList(k, 0, 0); |
v = llDeleteSubList(v, 0, 0); |
} while(llGetListLength(k) != 0); |
return llDumpList2String(data, "&"); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// escapes a string in conformance with RFC1738 |
string wasURLEscape(string i) { |
string o = ""; |
do { |
string c = llGetSubString(i, 0, 0); |
i = llDeleteSubString(i, 0, 0); |
if(c == "") jump continue; |
if(c == " ") { |
o += "+"; |
jump continue; |
} |
if(c == "\n") { |
o += "%0D" + llEscapeURL(c); |
jump continue; |
} |
o += llEscapeURL(c); |
@continue; |
} while(i != ""); |
return o; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// unescapes a string in conformance with RFC1738 |
string wasURLUnescape(string i) { |
return llUnescapeURL( |
llDumpList2String( |
llParseString2List( |
llDumpList2String( |
llParseString2List( |
i, |
["+"], |
[] |
), |
" " |
), |
["%0D%0A"], |
[] |
), |
"\n" |
) |
); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
list wasCSVToList(string csv) { |
list l = []; |
list s = []; |
string m = ""; |
do { |
string a = llGetSubString(csv, 0, 0); |
csv = llDeleteSubString(csv, 0, 0); |
if(a == ",") { |
if(llList2String(s, -1) != "\"") { |
l += m; |
m = ""; |
jump continue; |
} |
m += a; |
jump continue; |
} |
if(a == "\"" && llGetSubString(csv, 0, 0) == a) { |
m += a; |
csv = llDeleteSubString(csv, 0, 0); |
jump continue; |
} |
if(a == "\"") { |
if(llList2String(s, -1) != a) { |
s += a; |
jump continue; |
} |
s = llDeleteSubList(s, -1, -1); |
jump continue; |
} |
m += a; |
@continue; |
} while(csv != ""); |
// postcondition: length(s) = 0 |
return l + m; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasListToCSV(list l) { |
list v = []; |
do { |
string a = llDumpList2String( |
llParseStringKeepNulls( |
llList2String( |
l, |
0 |
), |
["\""], |
[] |
), |
"\"\"" |
); |
if(llParseStringKeepNulls( |
a, |
[" ", ",", "\n", "\""], [] |
) != |
(list) a |
) a = "\"" + a + "\""; |
v += a; |
l = llDeleteSubList(l, 0, 0); |
} while(l != []); |
return llDumpList2String(v, ","); |
} |
|
// corrade data |
string CORRADE = ""; |
string GROUP = ""; |
string PASSWORD = ""; |
integer INTERVAL = 0; |
integer MEMORY = 0; |
|
// for holding the callback URL |
string callback = ""; |
|
// for notecard reading |
integer line = 0; |
|
// key-value data will be read into this list |
list tuples = []; |
// store names and uuids for gender detect |
list names = []; |
list uuids = []; |
// store the keys of detected agents in |
// order to prevent scanning them again |
list found = []; |
// temporary storage over event handler scope |
string name = ""; |
key uuid = NULL_KEY; |
|
default { |
state_entry() { |
if(llGetInventoryType("configuration") != INVENTORY_NOTECARD) { |
llOwnerSay("Sorry, could not find a configuration inventory notecard."); |
return; |
} |
// DEBUG |
llOwnerSay("Reading configuration file..."); |
llGetNotecardLine("configuration", line); |
} |
dataserver(key id, string data) { |
if(data == EOF) { |
// invariant, length(tuples) % 2 == 0 |
if(llGetListLength(tuples) % 2 != 0) { |
llOwnerSay("Error in configuration notecard."); |
return; |
} |
CORRADE = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"corrade" |
] |
) |
+1); |
if(CORRADE == "") { |
llOwnerSay("Error in configuration notecard: corrade"); |
return; |
} |
GROUP = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"group" |
] |
) |
+1); |
if(GROUP == "") { |
llOwnerSay("Error in configuration notecard: group"); |
return; |
} |
PASSWORD = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"password" |
] |
) |
+1); |
if(PASSWORD == "") { |
llOwnerSay("Error in configuration notecard: password"); |
return; |
} |
INTERVAL = llList2Integer( |
tuples, |
llListFindList( |
tuples, |
[ |
"interval" |
] |
) |
+1); |
if(INTERVAL == 0) { |
llOwnerSay("Error in configuration notecard: interval"); |
return; |
} |
MEMORY = llList2Integer( |
tuples, |
llListFindList( |
tuples, |
[ |
"memory" |
] |
) |
+1); |
if(MEMORY == 0) { |
llOwnerSay("Error in configuration notecard: memory"); |
return; |
} |
// DEBUG |
llOwnerSay("Read configuration notecard..."); |
state url; |
} |
if(data == "") jump continue; |
integer i = llSubStringIndex(data, "#"); |
if(i != -1) data = llDeleteSubString(data, i, -1); |
list o = llParseString2List(data, ["="], []); |
// get rid of starting and ending quotes |
string k = llDumpList2String( |
llParseString2List( |
llStringTrim( |
llList2String( |
o, |
0 |
), |
STRING_TRIM), |
["\""], [] |
), "\""); |
string v = llDumpList2String( |
llParseString2List( |
llStringTrim( |
llList2String( |
o, |
1 |
), |
STRING_TRIM), |
["\""], [] |
), "\""); |
if(k == "" || v == "") jump continue; |
tuples += k; |
tuples += v; |
@continue; |
llGetNotecardLine("configuration", ++line); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state url { |
state_entry() { |
// DEBUG |
llOwnerSay("Requesting URL..."); |
llRequestURL(); |
} |
http_request(key id, string method, string body) { |
if(method != URL_REQUEST_GRANTED) return; |
callback = body; |
// DEBUG |
llOwnerSay("Got URL..."); |
state detect; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state detect { |
state_entry() { |
// DEBUG |
llOwnerSay("Detecting if Corrade is online..."); |
llSetTimerEvent(5); |
} |
timer() { |
llRequestAgentData((key)CORRADE, DATA_ONLINE); |
} |
dataserver(key id, string data) { |
if(data != "1") { |
// DEBUG |
llOwnerSay("Corrade is not online, sleeping..."); |
llSetTimerEvent(30); |
return; |
} |
state scan; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state scan { |
state_entry() { |
// DEBUG |
llOwnerSay("Scanning agents..."); |
list tmp = llGetAgentList(AGENT_LIST_REGION, []); |
do { |
key id = llList2Key(tmp, 0); |
// skip avatars that we have previously detected |
if(llListFindList(found, (list)id) != -1) jump continue; |
uuids += id; |
names += llKey2Name(id); |
@continue; |
tmp = llDeleteSubList(tmp, 0, 0); |
} while(llGetListLength(tmp) != 0); |
// recurse over timer, start. |
llSetTimerEvent(1); |
} |
timer() { |
// Pause the timer. |
llSetTimerEvent(0); |
// if the scanning list is empty, switch to detect |
if(llGetListLength(names) == 0) state detect; |
// pop the first name and UUID off the stack |
name = llList2String(names, 0); |
uuid = llList2Key(uuids, 0); |
names = llDeleteSubList(names, 0, 0); |
uuids = llDeleteSubList(uuids, 0, 0); |
// get the full name and send it to Corrade for scanning |
list full = llParseString2List(name, [" "], []); |
llInstantMessage(CORRADE, |
wasKeyValueEncode( |
[ |
"command", "getavatardata", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"firstname", wasURLEscape( |
llList2String( |
full, |
0 |
) |
), |
"lastname", wasURLEscape( |
llList2String( |
full, |
1 |
) |
), |
"sift", wasURLEscape( |
wasListToCSV( |
[ |
"match", wasURLEscape( |
",Index,31,([^,$]+)" |
) |
] |
) |
), |
"data", "VisualParameters", |
"callback", wasURLEscape(callback) |
] |
) |
); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
|
if(wasURLUnescape( |
wasKeyValueGet("success", body)) != "True") |
return; |
|
// request succeeded, so grab the |
// sex from the visual parameters |
integer sex = (integer) wasURLUnescape( |
wasKeyValueGet( |
"data", |
body |
) |
); |
|
// at this point we know the following: |
// - the name of the scanned avatar stored in "name" |
// - the UUID of the scanned avatar stored in "uuid" |
// - the gender of the avatar shape: |
// - if sex is 0, then the avatar has a female shape |
// - otherwise, the avatar has a male shape |
if(sex != 0) { |
llSay(0, "The avatar " + name + "(" + (string)uuid + ")" + " has a male shape!"); |
jump continue_2; |
} |
llSay(0, "The avatar " + name + "(" + (string)uuid + ")" + " has a female shape!"); |
|
@continue_2; |
|
// this keeps the amount of free memory available to the script above a specified treshold |
if (llGetFreeMemory() < MEMORY) |
found = llDeleteSubList(found, 0, 0); |
|
// to prevent scanning the same avatar again, add the uuid to the found list |
found += uuid; |
|
// Resetart the timer. |
llSetTimerEvent(INTERVAL); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
/batch-change-region-covenant/batch-change-region-covenant.lsl |
@@ -0,0 +1,592 @@ |
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// |
// This is a device that can be used to automatically batch-set the estate |
// covenant for regions using the Corrade scripted agent. You can find out |
// more about Corrade by following the URL: |
// http://grimore.org/secondlife/scripted_agents/corrade |
// |
// The script works in conjunction with a "configuration" notecard, a |
// "regions" notecard and a "covenant" notecard that must all be placed in |
// the same primitive as this script. |
// |
// The purpose of this script is to demonstrate batch-setting the estate |
// covenant with Corrade and you are free to use, change, and commercialize |
// it under the GNU/GPLv3 |
// license at: http://www.gnu.org/licenses/gpl.html |
// |
/////////////////////////////////////////////////////////////////////////// |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasProgress(integer percent, integer length, list symbols) { |
percent /= (integer)((float)100.0/(length)); |
string p = llList2String(symbols,0); |
integer itra = 0; |
do { |
if(itra>percent-1) p += llList2String(symbols,2); |
else p += llList2String(symbols,1); |
} while(++itra<length); |
return p + llList2String(symbols,3); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueGet(string k, string data) { |
if(llStringLength(data) == 0) return ""; |
if(llStringLength(k) == 0) return ""; |
list a = llParseString2List(data, ["&", "="], []); |
integer i = llListFindList(llList2ListStrided(a, 0, -1, 2), [ k ]); |
if(i != -1) return llList2String(a, 2*i+1); |
return ""; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueEncode(list data) { |
list k = llList2ListStrided(data, 0, -1, 2); |
list v = llList2ListStrided(llDeleteSubList(data, 0, 0), 0, -1, 2); |
data = []; |
do { |
data += llList2String(k, 0) + "=" + llList2String(v, 0); |
k = llDeleteSubList(k, 0, 0); |
v = llDeleteSubList(v, 0, 0); |
} while(llGetListLength(k) != 0); |
return llDumpList2String(data, "&"); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// escapes a string in conformance with RFC1738 |
string wasURLEscape(string i) { |
string o = ""; |
do { |
string c = llGetSubString(i, 0, 0); |
i = llDeleteSubString(i, 0, 0); |
if(c == "") jump continue; |
if(c == " ") { |
o += "+"; |
jump continue; |
} |
if(c == "\n") { |
o += "%0D" + llEscapeURL(c); |
jump continue; |
} |
o += llEscapeURL(c); |
@continue; |
} while(i != ""); |
return o; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasListToCSV(list l) { |
list v = []; |
do { |
string a = llDumpList2String( |
llParseStringKeepNulls( |
llList2String( |
l, |
0 |
), |
["\""], |
[] |
), |
"\"\"" |
); |
if(llParseStringKeepNulls(a, [" ", ",", "\n"], []) != (list) a) |
a = "\"" + a + "\""; |
v += a; |
l = llDeleteSubList(l, 0, 0); |
} while(l != []); |
return llDumpList2String(v, ","); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
list wasCSVToList(string csv) { |
list l = []; |
list s = []; |
string m = ""; |
do { |
string a = llGetSubString(csv, 0, 0); |
csv = llDeleteSubString(csv, 0, 0); |
if(a == ",") { |
if(llList2String(s, -1) != "\"") { |
l += m; |
m = ""; |
jump continue; |
} |
m += a; |
jump continue; |
} |
if(a == "\"" && llGetSubString(csv, 0, 0) == a) { |
m += a; |
csv = llDeleteSubString(csv, 0, 0); |
jump continue; |
} |
if(a == "\"") { |
if(llList2String(s, -1) != a) { |
s += a; |
jump continue; |
} |
s = llDeleteSubList(s, -1, -1); |
jump continue; |
} |
m += a; |
@continue; |
} while(csv != ""); |
// invariant: length(s) = 0 |
return l + m; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// unescapes a string in conformance with RFC1738 |
string wasURLUnescape(string i) { |
return llUnescapeURL( |
llDumpList2String( |
llParseString2List( |
llDumpList2String( |
llParseString2List( |
i, |
["+"], |
[] |
), |
" " |
), |
["%0D%0A"], |
[] |
), |
"\n" |
) |
); |
} |
|
// corrade data |
string CORRADE = ""; |
string GROUP = ""; |
string PASSWORD = ""; |
|
// for holding the callback URL |
string callback = ""; |
|
// for notecard reading |
integer line = 0; |
|
// key-value data will be read into this list |
list tuples = []; |
// regions will be stored here |
list regions = []; |
string region = ""; |
integer regionsChanged = 0; |
|
default { |
state_entry() { |
if(llGetInventoryType("configuration") != INVENTORY_NOTECARD) { |
llOwnerSay("Sorry, could not find a configuration inventory notecard."); |
return; |
} |
if(llGetInventoryType("covenant") != INVENTORY_NOTECARD) { |
llOwnerSay("Sorry, could not find a covenant inventory notecard."); |
return; |
} |
// DEBUG |
llOwnerSay("Reading configuration file..."); |
llGetNotecardLine("configuration", line); |
} |
dataserver(key id, string data) { |
if(data == EOF) { |
// invariant, length(tuples) % 2 == 0 |
if(llGetListLength(tuples) % 2 != 0) { |
llOwnerSay("Error in configuration notecard."); |
return; |
} |
CORRADE = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"corrade" |
] |
) |
+1); |
if(CORRADE == "") { |
llOwnerSay("Error in configuration notecard: corrade"); |
return; |
} |
GROUP = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"group" |
] |
) |
+1); |
if(GROUP == "") { |
llOwnerSay("Error in configuration notecard: group"); |
return; |
} |
PASSWORD = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"password" |
] |
) |
+1); |
if(PASSWORD == "") { |
llOwnerSay("Error in configuration notecard: password"); |
return; |
} |
// DEBUG |
llOwnerSay("Read configuration notecard..."); |
state read_regions; |
} |
if(data == "") jump continue; |
integer i = llSubStringIndex(data, "#"); |
if(i != -1) data = llDeleteSubString(data, i, -1); |
list o = llParseString2List(data, ["="], []); |
// get rid of starting and ending quotes |
string k = llDumpList2String( |
llParseString2List( |
llStringTrim( |
llList2String( |
o, |
0 |
), |
STRING_TRIM), |
["\""], [] |
), "\""); |
string v = llDumpList2String( |
llParseString2List( |
llStringTrim( |
llList2String( |
o, |
1 |
), |
STRING_TRIM), |
["\""], [] |
), "\""); |
if(k == "" || v == "") jump continue; |
tuples += k; |
tuples += v; |
@continue; |
llGetNotecardLine("configuration", ++line); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state read_regions { |
state_entry() { |
if(llGetInventoryType("regions") != INVENTORY_NOTECARD) { |
llOwnerSay("Sorry, could not find a regions inventory notecard."); |
return; |
} |
// DEBUG |
llOwnerSay("Reading regions notecard..."); |
line = 0; |
llGetNotecardLine("regions", line); |
} |
dataserver(key id, string data) { |
if(data == EOF) { |
// DEBUG |
llOwnerSay("Read regions notcard..."); |
state url; |
} |
if(data == "") jump continue; |
regions += data; |
@continue; |
llGetNotecardLine("regions", ++line); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state url { |
state_entry() { |
// DEBUG |
llOwnerSay("Requesting URL..."); |
llRequestURL(); |
} |
http_request(key id, string method, string body) { |
if(method != URL_REQUEST_GRANTED) return; |
callback = body; |
// DEBUG |
llOwnerSay("Got URL..."); |
state detect; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state detect { |
state_entry() { |
// DEBUG |
llOwnerSay("Detecting if Corrade is online..."); |
llSetTimerEvent(5); |
} |
timer() { |
llRequestAgentData((key)CORRADE, DATA_ONLINE); |
} |
dataserver(key id, string data) { |
if(data != "1") { |
// DEBUG |
llOwnerSay("Corrade is not online, sleeping..."); |
llSetTimerEvent(30); |
return; |
} |
state teleport; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state teleport { |
state_entry() { |
// Keep checking if Corrade disconnected. |
llSetTimerEvent(5); |
// Shuffle the regions and grab the next region. |
region = llList2String(regions, 0); |
regions = llDeleteSubList(regions, 0, 0); |
regions += region; |
// DEBUG |
llOwnerSay("Teleporting to: " + region); |
llInstantMessage( |
(key)CORRADE, |
wasKeyValueEncode( |
[ |
"command", "teleport", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"region", wasURLEscape(region), |
"entity", "region", |
"fly", "True", |
"position", <128,128,4096>, |
"callback", wasURLEscape(callback) |
] |
) |
); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) != "teleport" || |
wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Failed to teleport to: " + region); |
// Jump to trampoline for re-entry. |
state teleport_trampoline; |
} |
// DEBUG |
llOwnerSay("Teleported successfully to: " + region); |
state get_covenant; |
} |
timer() { |
llRequestAgentData((key)CORRADE, DATA_ONLINE); |
} |
dataserver(key id, string data) { |
if(data != "1") { |
// DEBUG |
llOwnerSay("Corrade is not online, sleeping..."); |
state detect; |
} |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state teleport_trampoline { |
state_entry() { |
// DEBUG |
llOwnerSay("Sleeping..."); |
llSetTimerEvent(5); |
} |
timer() { |
llSetTimerEvent(0); |
state teleport; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state get_covenant { |
state_entry() { |
// Keep checking if Corrade disconnected. |
llSetTimerEvent(5); |
// DEBUG |
llOwnerSay("Getting covenant..."); |
llInstantMessage( |
(key)CORRADE, |
wasKeyValueEncode( |
[ |
"command", "getestatecovenant", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"callback", wasURLEscape(callback) |
] |
) |
); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) != "getestatecovenant" || |
wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Failed to get covenant for region: " + region); |
// Jump to trampoline for teleport. |
state teleport_trampoline; |
} |
// DEBUG |
llOwnerSay("Got covenant for region: " + region); |
if(llList2Key( |
wasCSVToList( |
wasURLUnescape( |
wasKeyValueGet( |
"data", |
body |
) |
) |
), |
0 |
) == llGetInventoryKey("covenant")) { |
// DEBUG |
llOwnerSay("Covenant for region: \"" + region + "\" is set."); |
++regionsChanged; |
llSetText( |
"Corrade @ " + region + "\n" + |
"Progress: " + |
wasProgress( |
100 * regionsChanged/llGetListLength(regions), |
10, |
[ |
"[", "â–ˆ", "â–‘", "]" |
] |
) + "[" + (string)regionsChanged + "/" + (string)llGetListLength(regions) + "]", |
<0, 1, 1>, |
1.0 |
); |
state teleport_trampoline; |
} |
state set_covenant; |
} |
timer() { |
llRequestAgentData((key)CORRADE, DATA_ONLINE); |
} |
dataserver(key id, string data) { |
if(data != "1") { |
// DEBUG |
llOwnerSay("Corrade is not online, sleeping..."); |
state detect; |
} |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state set_covenant { |
state_entry() { |
// Keep checking if Corrade disconnected. |
llSetTimerEvent(5); |
// DEBUG |
llOwnerSay("Setting covenant..."); |
llInstantMessage( |
(key)CORRADE, |
wasKeyValueEncode( |
[ |
"command", "setestatecovenant", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"item", llGetInventoryKey("covenant"), |
"callback", wasURLEscape(callback) |
] |
) |
); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) != "setestatecovenant" || |
wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Failed to set covenant for region: " + region); |
--regionsChanged; |
// Jump to trampoline for teleport. |
state teleport_trampoline; |
} |
// DEBUG |
llOwnerSay("Set covenant for region: " + region); |
state get_covenant; |
} |
timer() { |
llRequestAgentData((key)CORRADE, DATA_ONLINE); |
} |
dataserver(key id, string data) { |
if(data != "1") { |
// DEBUG |
llOwnerSay("Corrade is not online, sleeping..."); |
state detect; |
} |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
|
/circular-patrol-movement/circular-patrol-movement.lsl |
@@ -0,0 +1,374 @@ |
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// |
// This is an automatic teleporter, and patrol script for the Corrade |
// Second Life / OpenSim bot. You can find more details about the bot |
// by following the URL: http://was.fm/secondlife/scripted_agents/corrade |
// |
// The purpose of this script is to demonstrate patroling with Corrade and |
// you are free to use, change, and commercialize it under the GNU/GPLv3 |
// license which can be found at: http://www.gnu.org/licenses/gpl.html |
// |
/////////////////////////////////////////////////////////////////////////// |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueGet(string k, string data) { |
if(llStringLength(data) == 0) return ""; |
if(llStringLength(k) == 0) return ""; |
list a = llParseString2List(data, ["&", "="], []); |
integer i = llListFindList(a, [ k ]); |
if(i != -1) return llList2String(a, i+1); |
return ""; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueEncode(list data) { |
list k = llList2ListStrided(data, 0, -1, 2); |
list v = llList2ListStrided(llDeleteSubList(data, 0, 0), 0, -1, 2); |
data = []; |
do { |
data += llList2String(k, 0) + "=" + llList2String(v, 0); |
k = llDeleteSubList(k, 0, 0); |
v = llDeleteSubList(v, 0, 0); |
} while(llGetListLength(k) != 0); |
return llDumpList2String(data, "&"); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2011 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// http://was.fm/secondlife/wanderer |
vector wasCirclePoint(float radius) { |
float x = llPow(-1, 1 + (integer) llFrand(2)) * llFrand(radius*2); |
float y = llPow(-1, 1 + (integer) llFrand(2)) * llFrand(radius*2); |
if(llPow(x,2) + llPow(y,2) <= llPow(radius,2)) |
return <x, y, 0>; |
return wasCirclePoint(radius); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// escapes a string in conformance with RFC1738 |
string wasURLEscape(string i) { |
string o = ""; |
do { |
string c = llGetSubString(i, 0, 0); |
i = llDeleteSubString(i, 0, 0); |
if(c == "") jump continue; |
if(c == " ") { |
o += "+"; |
jump continue; |
} |
if(c == "\n") { |
o += "%0D" + llEscapeURL(c); |
jump continue; |
} |
o += llEscapeURL(c); |
@continue; |
} while(i != ""); |
return o; |
} |
|
// corrade data |
string CORRADE = ""; |
string GROUP = ""; |
string PASSWORD = ""; |
float RADIUS = 0; |
float WAIT = 0; |
|
// for holding the callback URL |
string callback = ""; |
|
// for notecard reading |
integer line = 0; |
|
// key-value data will be read into this list |
list tuples = []; |
|
default { |
state_entry() { |
if(llGetInventoryType("configuration") != INVENTORY_NOTECARD) { |
llOwnerSay("Sorry, could not find an inventory notecard."); |
return; |
} |
// DEBUG |
llOwnerSay("Reading configuration file..."); |
llGetNotecardLine("configuration", line); |
} |
dataserver(key id, string data) { |
if(data == EOF) { |
// invariant, length(tuples) % 2 == 0 |
if(llGetListLength(tuples) % 2 != 0) { |
llOwnerSay("Error in configuration notecard."); |
return; |
} |
CORRADE = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"corrade" |
] |
) |
+1); |
if(CORRADE == "") { |
llOwnerSay("Error in configuration notecard: corrade"); |
return; |
} |
GROUP = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"group" |
] |
) |
+1); |
if(GROUP == "") { |
llOwnerSay("Error in configuration notecard: group"); |
return; |
} |
PASSWORD = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"password" |
] |
) |
+1); |
if(PASSWORD == "") { |
llOwnerSay("Error in configuration notecard: password"); |
return; |
} |
RADIUS = llList2Float( |
tuples, |
llListFindList( |
tuples, |
[ |
"radius" |
] |
) |
+1); |
if(RADIUS == 0) { |
llOwnerSay("Error in configuration notecard: radius"); |
return; |
} |
WAIT = llList2Float( |
tuples, |
llListFindList( |
tuples, |
[ |
"wait" |
] |
) |
+1); |
if(WAIT == 0) { |
llOwnerSay("Error in configuration notecard: wait"); |
return; |
} |
// DEBUG |
llOwnerSay("Read configuration file..."); |
state url; |
} |
if(data == "") jump continue; |
integer i = llSubStringIndex(data, "#"); |
if(i != -1) data = llDeleteSubString(data, i, -1); |
list o = llParseString2List(data, ["="], []); |
// get rid of starting and ending quotes |
string k = llDumpList2String( |
llParseString2List( |
llStringTrim( |
llList2String( |
o, |
0 |
), |
STRING_TRIM), |
["\""], [] |
), "\""); |
string v = llDumpList2String( |
llParseString2List( |
llStringTrim( |
llList2String( |
o, |
1 |
), |
STRING_TRIM), |
["\""], [] |
), "\""); |
if(k == "" || v == "") jump continue; |
tuples += k; |
tuples += v; |
@continue; |
llGetNotecardLine("configuration", ++line); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state url { |
state_entry() { |
// DEBUG |
llOwnerSay("Requesting URL..."); |
llRequestURL(); |
} |
http_request(key id, string method, string body) { |
if(method != URL_REQUEST_GRANTED) return; |
callback = body; |
// DEBUG |
llOwnerSay("Got URL..."); |
state detect; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state detect { |
state_entry() { |
// DEBUG |
llOwnerSay("Detecting if Corrade is online..."); |
llSetTimerEvent(5); |
} |
timer() { |
llRequestAgentData((key)CORRADE, DATA_ONLINE); |
} |
dataserver(key id, string data) { |
if(data != "1") { |
// DEBUG |
llOwnerSay("Corrade is not online, sleeping..."); |
llSetTimerEvent(30); |
return; |
} |
llSensor("", (key)CORRADE, AGENT, 10, TWO_PI); |
} |
no_sensor() { |
// DEBUG |
llOwnerSay("Teleporting Corrade..."); |
llInstantMessage((key)CORRADE, |
wasKeyValueEncode( |
[ |
"command", "teleport", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"entity", "region", |
"region", wasURLEscape(llGetRegionName()), |
"position", wasURLEscape( |
(string)( |
llGetPos() + wasCirclePoint(RADIUS) |
) |
), |
"callback", callback |
] |
) |
); |
llSensorRepeat("", (key)CORRADE, AGENT, 10, TWO_PI, 60); |
} |
sensor(integer num) { |
llSetTimerEvent(0); |
state wander; |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) != "teleport" || |
wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Teleport failed..."); |
return; |
} |
llSetTimerEvent(0); |
state wander; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state wander { |
state_entry() { |
// DEBUG |
llOwnerSay("Wandering ready..."); |
llSetTimerEvent(1 + llFrand(WAIT)); |
} |
timer() { |
llRequestAgentData((key)CORRADE, DATA_ONLINE); |
} |
dataserver(key id, string data) { |
if(data != "1") { |
// DEBUG |
llOwnerSay("Corrade is not online, sleeping..."); |
llResetScript(); |
return; |
} |
// DEBUG |
//llOwnerSay("Sending stop..."); |
llInstantMessage(CORRADE, |
wasKeyValueEncode( |
[ |
"command", "autopilot", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"action", "stop", |
"callback", wasURLEscape(callback) |
] |
) |
); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) != "autopilot" || |
wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Could not get Corrade to stop, restarting script..."); |
llResetScript(); |
} |
// DEBUG |
//llOwnerSay("Sending next move..."); |
llInstantMessage(CORRADE, |
wasKeyValueEncode( |
[ |
"command", "autopilot", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"position", wasURLEscape( |
(string)( |
llGetPos() + wasCirclePoint(RADIUS) |
) |
), |
"action", "start" |
] |
) |
); |
llSetTimerEvent(1 + llFrand(WAIT)); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
/greet-invite-track-group-members/greet-invite-track-group-members.lsl |
@@ -0,0 +1,522 @@ |
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// |
// A greet and invite script made to work in conjunction with the Corrade |
// the Second Life / OpenSim bot. You can find more details about the bot |
// by following the URL: http://was.fm/secondlife/scripted_agents/corrade |
// |
// The script works in combination with a "configuration" notecard that |
// must be placed in the same primitive as this script. The purpose of this |
// script is to demonstrate greeting and inviting avatars using Corrade |
// and you are free to use, change, and commercialize it under the terms |
// of the GNU/GPLv3 license at: http://www.gnu.org/licenses/gpl.html |
// |
/////////////////////////////////////////////////////////////////////////// |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueGet(string k, string data) { |
if (llStringLength(data) == 0) return ""; |
if (llStringLength(k) == 0) return ""; |
list a = llParseString2List(data, ["&", "="], []); |
integer i = llListFindList(llList2ListStrided(a, 0, -1, 2), [ k ]); |
if (i != -1) return llList2String(a, 2 * i + 1); |
return ""; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueEncode(list data) { |
list k = llList2ListStrided(data, 0, -1, 2); |
list v = llList2ListStrided(llDeleteSubList(data, 0, 0), 0, -1, 2); |
data = []; |
do { |
data += llList2String(k, 0) + "=" + llList2String(v, 0); |
k = llDeleteSubList(k, 0, 0); |
v = llDeleteSubList(v, 0, 0); |
} while (llGetListLength(k) != 0); |
return llDumpList2String(data, "&"); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// escapes a string in conformance with RFC1738 |
string wasURLEscape(string i) { |
string o = ""; |
do { |
string c = llGetSubString(i, 0, 0); |
i = llDeleteSubString(i, 0, 0); |
if(c == "") jump continue; |
if(c == " ") { |
o += "+"; |
jump continue; |
} |
if(c == "\n") { |
o += "%0D" + llEscapeURL(c); |
jump continue; |
} |
o += llEscapeURL(c); |
@continue; |
} while(i != ""); |
return o; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// unescapes a string in conformance with RFC1738 |
string wasURLUnescape(string i) { |
return llUnescapeURL( |
llDumpList2String( |
llParseString2List( |
llDumpList2String( |
llParseString2List( |
i, |
["+"], |
[] |
), |
" " |
), |
["%0D%0A"], |
[] |
), |
"\n" |
) |
); |
} |
|
// Helper function to extract values from tuples |
string getConfigValue(string id) { |
integer i = llListFindList(tuples, (list)id); |
if (i == -1) return ""; |
return llList2String( |
tuples, |
i + 1 |
); |
} |
|
// corrade data |
key CORRADE = ""; |
string GROUP = ""; |
string PASSWORD = ""; |
|
// instance variables |
integer channel; |
list rem = []; |
|
// for holding the callback URL |
string callback = ""; |
|
// for notecard reading |
integer line = 0; |
|
// key-value data will be read into this list |
list tuples = []; |
|
default { |
state_entry() { |
// Set-up commnuication channel. |
channel = (integer)("0x8" + llGetSubString(llGetKey(), 0, 6)); |
// Read configuration. |
if (llGetInventoryType("configuration") != INVENTORY_NOTECARD) { |
llOwnerSay("Sorry, could not find a configuration inventory notecard."); |
return; |
} |
// DEBUG |
llOwnerSay("Reading configuration file..."); |
llGetNotecardLine("configuration", line); |
} |
dataserver(key id, string data) { |
if (data == EOF) { |
// invariant, length(tuples) % 2 == 0 |
if (llGetListLength(tuples) % 2 != 0) { |
llOwnerSay("Error in configuration notecard."); |
return; |
} |
CORRADE = llList2Key( |
tuples, |
llListFindList( |
tuples, |
[ |
"corrade" |
] |
) + 1 |
); |
if (CORRADE == NULL_KEY) { |
llOwnerSay("Error in configuration notecard: corrade"); |
return; |
} |
GROUP = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"group" |
] |
) + 1 |
); |
if (GROUP == "") { |
llOwnerSay("Error in configuration notecard: group"); |
return; |
} |
PASSWORD = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"password" |
] |
) + 1 |
); |
if (PASSWORD == "") { |
llOwnerSay("Error in configuration notecard: password"); |
return; |
} |
// DEBUG |
llOwnerSay("Read configuration notecard..."); |
state url; |
} |
if (data == "") jump continue; |
integer i = llSubStringIndex(data, "#"); |
if (i != -1) data = llDeleteSubString(data, i, -1); |
list o = llParseString2List(data, ["="], []); |
// get rid of starting and ending quotes |
string k = llDumpList2String( |
llParseString2List( |
llStringTrim( |
llList2String( |
o, |
0 |
), |
STRING_TRIM), |
["\""], [] |
), "\""); |
string v = llDumpList2String( |
llParseString2List( |
llStringTrim( |
llList2String( |
o, |
1 |
), |
STRING_TRIM), |
["\""], [] |
), "\""); |
if (k == "" || v == "") jump continue; |
tuples += k; |
tuples += v; |
@continue; |
llGetNotecardLine("configuration", ++line); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if ((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state url { |
state_entry() { |
// DEBUG |
llOwnerSay("Requesting URL..."); |
llRequestURL(); |
} |
http_request(key id, string method, string body) { |
if (method != URL_REQUEST_GRANTED) return; |
callback = body; |
// DEBUG |
llOwnerSay("Got URL..."); |
state online; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if ((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state online { |
state_entry() { |
// DEBUG |
llOwnerSay("Detecting if Corrade is online..."); |
llSetTimerEvent(5); |
} |
timer() { |
llRequestAgentData((key)CORRADE, DATA_ONLINE); |
} |
dataserver(key id, string data) { |
if (data != "1") { |
// DEBUG |
llOwnerSay("Corrade is not online, sleeping..."); |
llSetTimerEvent(30); |
return; |
} |
state notify; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if ((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state notify { |
state_entry() { |
// DEBUG |
llOwnerSay("Binding to the membership notification..."); |
llInstantMessage( |
(key)CORRADE, |
wasKeyValueEncode( |
[ |
"command", "notify", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"action", "set", |
"type", llList2CSV(["membership"]), |
"URL", wasURLEscape(callback), |
"callback", wasURLEscape(callback) |
] |
) |
); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if (wasKeyValueGet("command", body) != "notify" || |
wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Failed to bind to the membership notification..."); |
state detect; |
} |
// DEBUG |
llOwnerSay("Membership notification installed..."); |
state detect; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if ((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state detect { |
state_entry() { |
// DEBUG |
llOwnerSay("Scanning..."); |
llSensorRepeat("", "", AGENT, (float)getConfigValue("range"), TWO_PI, 1); |
llListen(channel, "", "", ""); |
// Poll for Corrade's online status. |
llSetTimerEvent(5); |
} |
timer() { |
llRequestAgentData((key)CORRADE, DATA_ONLINE); |
} |
dataserver(key id, string data) { |
if (data == "1") return; |
// DEBUG |
llOwnerSay("Corrade is not online, sleeping..."); |
// Switch to detect loop and wait there for Corrade to come online. |
state online; |
} |
touch_start(integer num) { |
llDialog( |
llDetectedKey(0), |
getConfigValue("welcome"), |
[ |
getConfigValue("join"), |
getConfigValue("info"), |
getConfigValue("exit"), |
getConfigValue("mark"), |
getConfigValue("gift"), |
getConfigValue("site"), |
getConfigValue("page") |
], |
channel |
); |
} |
listen(integer channel, string name, key id, string message) { |
if (message == getConfigValue("exit")) return; |
if (message == getConfigValue("join")) { |
// DEBUG |
llOwnerSay("Inviting: " + name + " to the group."); |
llInstantMessage(CORRADE, |
wasKeyValueEncode( |
[ |
"command", "invite", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"agent", wasURLEscape(id) |
] |
) |
); |
jump menu; |
} |
if (message == getConfigValue("page")) { |
list a = llParseString2List(getConfigValue("help"), [", ", ","], []); |
if (llGetListLength(a) == 0) { |
llInstantMessage(id, getConfigValue("n/a")); |
jump menu; |
} |
vector p = llGetPos(); |
string slurl = "secondlife://" + wasURLEscape(llGetRegionName()) + "/" + |
(string)((integer)p.x) + "/" + |
(string)((integer)p.y) + "/" + |
(string)((integer)p.z); |
do { |
list n = llParseString2List(llList2String(a, 0), [" "], []); |
if (llGetListLength(n) == 2) { |
llInstantMessage(CORRADE, |
wasKeyValueEncode( |
[ |
"command", "tell", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"entity", "avatar", |
"firstname", wasURLEscape(llList2String(n, 0)), |
"lastname", wasURLEscape(llList2String(n, 1)), |
"message", wasURLEscape( |
name + |
" is asking for help at: " + |
slurl |
) |
] |
) |
); |
} |
a = llDeleteSubList(a, 0, 0); |
} while (llGetListLength(a) != 0); |
llInstantMessage(id, getConfigValue("assist")); |
jump menu; |
} |
if (message == getConfigValue("mark")) { |
if (llGetInventoryNumber(INVENTORY_LANDMARK) == 0) { |
llInstantMessage(id, getConfigValue("n/a")); |
jump menu; |
} |
llGiveInventory(id, llGetInventoryName(INVENTORY_LANDMARK, 0)); |
jump menu; |
} |
if (message == getConfigValue("info")) { |
integer i = llGetInventoryNumber(INVENTORY_NOTECARD) - 1; |
do { |
if (llGetInventoryName(INVENTORY_NOTECARD, i) != "configuration") { |
llGiveInventory(id, llGetInventoryName(INVENTORY_NOTECARD, i)); |
jump menu; |
} |
} while (--i > -1); |
llInstantMessage(id, getConfigValue("n/a")); |
jump menu; |
} |
if (message == getConfigValue("gift")) { |
if (llGetInventoryNumber(INVENTORY_OBJECT) == 0) { |
llInstantMessage(id, getConfigValue("n/a")); |
jump menu; |
} |
llGiveInventory(id, llGetInventoryName(INVENTORY_OBJECT, 0)); |
jump menu; |
} |
if (message == getConfigValue("site")) { |
llLoadURL(id, getConfigValue("loadurl"), getConfigValue("website")); |
jump menu; |
} |
@menu; |
llDialog( |
id, |
getConfigValue("welcome"), |
[ |
getConfigValue("join"), |
getConfigValue("info"), |
getConfigValue("exit"), |
getConfigValue("mark"), |
getConfigValue("gift"), |
getConfigValue("site"), |
getConfigValue("page") |
], |
channel |
); |
} |
no_sensor() { |
rem = []; |
} |
sensor(integer i) { |
--i; |
do { |
key id = llDetectedKey(i); |
if (id == NULL_KEY) jump continue; |
if (llListFindList(rem, (list)id) != -1) jump continue; |
llDialog( |
id, |
getConfigValue("welcome"), |
[ |
getConfigValue("join"), |
getConfigValue("info"), |
getConfigValue("exit"), |
getConfigValue("mark"), |
getConfigValue("gift"), |
getConfigValue("site"), |
getConfigValue("page") |
], |
channel |
); |
rem += id; |
@continue; |
} while (--i > -1); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
list a = llParseString2List(getConfigValue("notify"), [", ", ","], []); |
if (llGetListLength(a) == 0) { |
// DEBUG |
llOwnerSay("Could not read list of avatars to notify about group membership..."); |
return; |
} |
do { |
list n = llParseString2List(llList2String(a, 0), [" "], []); |
if (llGetListLength(n) == 2) { |
llInstantMessage(CORRADE, |
wasKeyValueEncode( |
[ |
"command", "tell", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"entity", "avatar", |
"firstname", wasURLEscape(llList2String(n, 0)), |
"lastname", wasURLEscape(llList2String(n, 1)), |
"message", wasURLEscape( |
wasURLUnescape(wasKeyValueGet("firstname", body)) + |
" " + |
wasURLUnescape(wasKeyValueGet("lastname", body)) + |
" has " + |
wasURLUnescape(wasKeyValueGet("action", body)) + |
" the group." |
) |
] |
) |
); |
} |
a = llDeleteSubList(a, 0, 0); |
} while (llGetListLength(a) != 0); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if ((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
/grid-follow/grid-follow.lsl |
@@ -0,0 +1,380 @@ |
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// |
// This is an automatic grid follower for the Corrade Second Life / OpenSim |
// bot. You can find more details about the bot by following the URL: |
// http://was.fm/secondlife/scripted_agents/corrade |
// |
// The follower script works together with a "configuration" notecard and |
// that must be placed in the same primitive as this script. |
// You are free to use, change, and commercialize it under the GNU/GPLv3 |
// license at: http://www.gnu.org/licenses/gpl.html |
// |
/////////////////////////////////////////////////////////////////////////// |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueGet(string k, string data) { |
if(llStringLength(data) == 0) return ""; |
if(llStringLength(k) == 0) return ""; |
list a = llParseString2List(data, ["&", "="], []); |
integer i = llListFindList(a, [ k ]); |
if(i != -1) return llList2String(a, i+1); |
return ""; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueEncode(list data) { |
list k = llList2ListStrided(data, 0, -1, 2); |
list v = llList2ListStrided(llDeleteSubList(data, 0, 0), 0, -1, 2); |
data = []; |
do { |
data += llList2String(k, 0) + "=" + llList2String(v, 0); |
k = llDeleteSubList(k, 0, 0); |
v = llDeleteSubList(v, 0, 0); |
} while(llGetListLength(k) != 0); |
return llDumpList2String(data, "&"); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2011 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
vector wasCirclePoint(float radius) { |
float x = llPow(-1, 1 + (integer) llFrand(2)) * llFrand(radius*2); |
float y = llPow(-1, 1 + (integer) llFrand(2)) * llFrand(radius*2); |
if(llPow(x,2) + llPow(y,2) <= llPow(radius,2)) |
return <x, y, 0>; |
return wasCirclePoint(radius); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
integer wasIsAvatarInSensorRange(key avatar) { |
return llListFindList( |
llGetAgentList( |
AGENT_LIST_REGION, |
[] |
), |
(list)((key)avatar) |
) != -1 && |
llVecDist( |
llGetPos(), |
llList2Vector( |
llGetObjectDetails( |
avatar, |
[OBJECT_POS] |
), |
0 |
) |
) <= 96; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// escapes a string in conformance with RFC1738 |
string wasURLEscape(string i) { |
string o = ""; |
do { |
string c = llGetSubString(i, 0, 0); |
i = llDeleteSubString(i, 0, 0); |
if(c == "") jump continue; |
if(c == " ") { |
o += "+"; |
jump continue; |
} |
if(c == "\n") { |
o += "%0D" + llEscapeURL(c); |
jump continue; |
} |
o += llEscapeURL(c); |
@continue; |
} while(i != ""); |
return o; |
} |
|
// corrade data |
string CORRADE = ""; |
string GROUP = ""; |
string PASSWORD = ""; |
string RANGE = ""; |
|
// for holding the callback URL |
string callback = ""; |
|
// for notecard reading |
integer line = 0; |
|
// key-value data will be read into this list |
list tuples = []; |
|
default { |
state_entry() { |
// set color for button |
llSetColor(<1,1,0>, ALL_SIDES); |
if(llGetInventoryType("configuration") != INVENTORY_NOTECARD) { |
llOwnerSay("Sorry, could not find an inventory notecard."); |
return; |
} |
// DEBUG |
llOwnerSay("Reading configuration file..."); |
llGetNotecardLine("configuration", line); |
} |
dataserver(key id, string data) { |
if(data == EOF) { |
// invariant, length(tuples) % 2 == 0 |
if(llGetListLength(tuples) % 2 != 0) { |
llOwnerSay("Error in configuration notecard."); |
return; |
} |
CORRADE = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"corrade" |
] |
) |
+1); |
if(CORRADE == "") { |
llOwnerSay("Error in configuration notecard: corrade"); |
return; |
} |
GROUP = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"group" |
] |
) |
+1); |
if(GROUP == "") { |
llOwnerSay("Error in configuration notecard: password"); |
return; |
} |
PASSWORD = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"password" |
] |
) |
+1); |
if(GROUP == "") { |
llOwnerSay("Error in configuration notecard: group"); |
return; |
} |
RANGE = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"range" |
] |
) |
+1); |
if(RANGE == "") { |
llOwnerSay("Error in configuration notecard: range"); |
return; |
} |
// DEBUG |
llOwnerSay("Read configuration file..."); |
state url; |
} |
if(data == "") jump continue; |
integer i = llSubStringIndex(data, "#"); |
if(i != -1) data = llDeleteSubString(data, i, -1); |
list o = llParseString2List(data, ["="], []); |
// get rid of starting and ending quotes |
string k = llDumpList2String( |
llParseString2List( |
llStringTrim( |
llList2String( |
o, |
0 |
), |
STRING_TRIM), |
["\""], [] |
), "\""); |
string v = llDumpList2String( |
llParseString2List( |
llStringTrim( |
llList2String( |
o, |
1 |
), |
STRING_TRIM), |
["\""], [] |
), "\""); |
if(k == "" || v == "") jump continue; |
tuples += k; |
tuples += v; |
@continue; |
llGetNotecardLine("configuration", ++line); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state url { |
state_entry() { |
// DEBUG |
llOwnerSay("Requesting URL..."); |
llRequestURL(); |
} |
http_request(key id, string method, string body) { |
if(method != URL_REQUEST_GRANTED) return; |
callback = body; |
// DEBUG |
llOwnerSay("Got URL..."); |
state off; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state off { |
state_entry() { |
// set color for button |
llSetColor(<1,0,0>, ALL_SIDES); |
} |
touch_start(integer num) { |
state on; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state on { |
state_entry() { |
// set color for button |
llSetColor(<0,1,0>, ALL_SIDES); |
// if Corrade is in-range then just follow |
if(wasIsAvatarInSensorRange(CORRADE)) state follow; |
// DEBUG |
llOwnerSay("Detecting if Corrade is online..."); |
llSetTimerEvent(5); |
} |
timer() { |
llRequestAgentData((key)CORRADE, DATA_ONLINE); |
} |
dataserver(key id, string data) { |
if(data != "1") { |
// DEBUG |
llOwnerSay("Corrade is not online, sleeping..."); |
llSetTimerEvent(30); |
return; |
} |
llSensorRepeat("", (key)CORRADE, AGENT, (integer)RANGE, TWO_PI, 5); |
} |
no_sensor() { |
// DEBUG |
llOwnerSay("Teleporting Corrade..."); |
llInstantMessage((key)CORRADE, |
wasKeyValueEncode( |
[ |
"command", "teleport", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"entity", "region", |
"region", wasURLEscape(llGetRegionName()), |
"position", llGetPos() + wasCirclePoint((integer)RANGE), |
"callback", callback |
] |
) |
); |
} |
sensor(integer num) { |
llSetTimerEvent(0); |
state follow; |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) != "teleport" || |
wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Teleport failed..."); |
return; |
} |
llSetTimerEvent(0); |
state follow; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state follow { |
state_entry() { |
// DEBUG |
llOwnerSay("In follow state..."); |
// check every second whether Corrade is online |
llRequestAgentData(CORRADE, DATA_ONLINE); |
} |
touch_start(integer num) { |
state off; |
} |
dataserver(key id, string data) { |
// if Corrade is not online |
if(data != "1") state on; |
// Corrade is online, so attempt to dectect |
llSensorRepeat("", CORRADE, AGENT, (integer)RANGE, TWO_PI, 1); |
} |
no_sensor() { |
// check if Corrade is in range, and if not, start detecting |
if(!wasIsAvatarInSensorRange(CORRADE)) state on; |
// Corrade is in sensor range, so execute move. |
llInstantMessage(CORRADE, |
wasKeyValueEncode( |
[ |
"command", "autopilot", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
// move in a radius around the current primitive. |
"position", llGetPos() + wasCirclePoint((integer)RANGE), |
"action", "start" |
] |
) |
); |
llSensorRepeat("", CORRADE, AGENT, (integer)RANGE, TWO_PI, 1); |
llRequestAgentData(CORRADE, DATA_ONLINE); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
/group-fortune/fortunes.txt |
@@ -0,0 +1,478 @@ |
Today is the last day of your life so far. |
I would love to, but I have to study for a blood test. |
If you saw a heat wave, would you wave back? |
If a person with multiple personalities threatens suicide, is that considered a hostage situation? |
I want to die like my grandfather in his sleep, not like his screaming passengers. |
Good friends are like bottles of Sweet Wine. That's why I keep mine locked in the cellar. |
If you don't change your direction, you may end up where you were headed. |
When in doubt, ignore it. |
A bird in the hand makes it hard to blow your nose. |
Some days you are the bug, some days you are the windshield. |
I was thinking that women should put pictures of missing husbands on beer cans. |
A hangover is the wrath of grapes. |
I would love to, but I changed the lock on my door and now I can't get out. |
A pessimist's blood type is always b-negative. |
Banning the bra was a big flop. |
A man walked into the doctors, he said, "I've hurt my arm in several places". The doctor said, "Well don't go there anymore". |
How much deeper would oceans be if sponges didn't live there? |
I would love to, but I'm trying to cut down. |
If a man says something in the woods and there are no women there, is he still wrong? |
A closed mouth gathers no feet. |
Computer programmers know how to use their hardware. |
He's not dead, he's electroencephalographically challenged. |
On the other hand, you have different fingers. |
Q: Why did the runner quit the race against Bigfoot? A: He couldn't face de-feet!! |
God didn't create the world in 7 days. He pulled an all-nighter on the 6th. |
Don't be irreplaceable. If you can't be replaced, you can't be promoted. |
The only way to get rid of temptation is to yield to it. |
Each day I try to enjoy something from each of the four food groups: the bonbon group, the salty-snack group, the caffeine group, and the "whatever-the-thing-in-the-tinfoil-in-the-back-of-the- fridge-is" group. |
I would love to, but I'm making a home movie called "The Thing That Grew in My Refrigerator." |
I would love to, but my favorite commercial is on TV. |
Two cannibals are eating dinner. One says to the other, "Gosh, Bill, your wife makes a great meal." |
What is a country song played backwards? Your wife gets back with you, your dog comes back to life, your car starts, you get your job back and life is great. |
It's not hard to meet expenses, they're everywhere. |
I'm thinking of becoming a hitman... Yeah, I heard they make a killing. |
Q: How do crazy people get through the forest? A: They take the psycho path. |
When you're finally holding all the cards, why does everyone else decide to play chess? |
Timing has an awful lot to do with the outcome of a rain dance. |
Lynch's Law: When the going gets tough, everyone leaves. |
She's always late. Her ancestors arrived on the Juneflower. |
I think the Japanese flag is really a pie chart of how afraid they are of Godzilla. |
Earn cash in your spare time -- blackmail friends. |
I would love to, but I have to knit some dust bunnies for a charity bazaar. |
If a motorist cuts you off, just turn the other cheek. Nothing gets the message across like a good mooning. |
Old math teachers never die, they just reduce to lowest term. |
I would love to, but I feel a song coming on. |
Q: What did Tom get when he locked Jerry in the freezer? A: Mice cubes! |
Why do they call it an asteroid when it's outside the hemisphere, but call it a hemorrhoid when it's in your ass? |
Whose cruel idea was it for the word "lisp" to have an "s" in it? |
If it's zero degrees outside today and it's supposed to be twice as cold tomorrow, how cold is it going to be? |
Why is there a light in the fridge and not in the freezer? |
Titanic is just one example of the ice bucket challenge going wrong... |
It may be that your sole purpose in life is simply to serve as a warning to others. |
Death is Nature's way of saying 'slow down'. |
I was thinking about how people seem to read the Bible a whole lot more as they get older, then it dawned on me they were cramming for their finals. |
Q: How does the brain communicate with the nerves? A: With a Cell phone! |
If Fed Ex and UPS were to merge, would they call it Fed UP? (what if FedEx, UPS and Emery Worldwide merged = Fed Up Worldwide)? |
Q: What did the sardine call the submarine? A: A can of people! |
A bus station is where a bus stops. A train station is where a train stops. On my desk, I have a work station...? |
A journey of a thousand miles begins with a cash advance. |
Q: What kind of music do Mummies listen to? A: Wrap. |
Did you ever notice that when you blow in a dog's face, he gets mad at you, but when you take him on a car ride, he can't wait to stick his head out the window into the wind? |
Experience is something you don't get until just after you need it. |
Don't blame the holidays, you were fat in August. |
I married Miss Right. I just didn't know her first name was 'Always!' |
Follow your dream! Unless it's the one where you're at work in your underwear during a fire drill. |
I would love to, but I have to stay home and see if I snore. |
The Gym is like Church. Everybody thinks that by going one hour, one day, they'll erase what they did during the week. |
Practice safe eating: always use condiments. |
I would love to, but I just picked up a book called "Glue in Many Lands" and I'm stuck on it. |
If a cow laughed, would milk come out its nose? |
Life is what happens to you while you are planning to do something else. |
Early bird gets the worm, but the second mouse gets the cheese. |
Q: Why do cows wear bells? A: Because their horns don't work! |
If you are coasting, you're going downhill. |
I would love to, but I'm up to my eardrums in waxy buildup. |
You must've sat in a pile of sugar because you've got a sweet ass! |
A bad day fishing is better than a good day at work. |
Don't worry, it only seems kinky the first time. |
Can blind people see their dreams? Do they dream?? |
Ambition a poor excuse for not having enough sense to be lazy. |
Why do psychics have to ask you for your name? |
Police arrested two kids yesterday, one was drinking battery acid, and the other was eating fireworks. They charged one and let the other one off. |
Dijon vu: the same mustard as before. |
It was all so different before everything changed. |
I fired my masseuse today. She just rubbed me the wrong way. |
Time flies like an arrow. Fruit flies like a banana. |
As you read the scroll, it vanishes... |
I would love to, but my chocolate-appreciation class meets that night. |
Jury -- Twelve people who determine which client has the better lawyer. |
I would love to, but I have to jog my memory. |
If I worked as much as others, I would do as little as they. |
How come abbreviated is such a long word? |
Is a book on voyeurism a peeping tome. |
Televangelists: The Pro Wrestlers of religion. |
Do not walk behind me, for I may not lead. Do not walk ahead of me, for I may not follow. Do not walk beside me, either. Just leave me alone. |
He who hesitates is boss. |
Love is like a roller coaster: when it's good you don't want to get off, and when it isn't... you can't wait to throw up. |
History does not repeat itself, -- historians merely repeat each other. |
I’ve probably wasted a solid year of my life just staring into the fridge. |
A boss with no humor is like a job that is no fun. |
Since light travels faster than sound, isn't that why some people appear bright until you hear them speak? |
A Freudian slip is when you say one thing but mean your mother. |
I would love to, but I have to sit up with a sick ant. |
Support bacteria - they're the only culture some people have. |
Q: Why do ghosts have so much trouble dating? A: Women can see right through them. |
I would love to, but I have to rotate my crops. |
Energizer Bunny arrested, charged with battery. |
Familiarity breeds children. |
Ironically, the only way you could get me to watch 50 shades of gray is if you tied me up and forced me to watch it. |
MOP AND GLOW - Floor wax used by Three Mile Island cleanup team. |
Dancing cheek-to-cheek is really a form of floor play. |
OK, so what's the speed of dark? |
CLEARASOL - Effective sunspot remover. |
Ain't it funny how the colors red, white, and blue represent freedom until they are flashing behind your car. |
I would love to, but I have to go to court for kitty littering. |
Depression is merely anger without enthusiasm. |
I love defenseless animals, especially in a good gravy. |
Computer modelers simulate it first. |
Quoting one is plagiarism. Quoting many is research. |
Computer hackers do it all night long. |
Never miss a good chance to shut up. |
24 hours in a day ... 24 beers in a case ...coincidence? |
Entropy isn't what it used to be. |
After Monday and Tuesday even the calendar says W T F. |
If you ain't makin' waves, you ain't kickin' hard enough! |
What happens if you get scared half to death twice? |
I would love to, but I'm going to count the bristles in my toothbrush. |
What do you call a male ladybug? |
Saying you are dumped but we can still be friends is like saying the dog died but let's take it for a walk anyway. |
Drive defensively -- buy a tank. |
BATCH - A group, kinda like a herd. |
Why is there an expiration date on sour cream? |
Some days you're the dog, some days you're the hydrant. |
Beauty is in the eye of the beer holder... |
If electricity comes from electrons, does morality come from morons? |
Without geometry, life is pointless. |
GAY ABANDON - Homosexual repellent perfume. |
I would love to, but I'm attending a perfume convention as guest sniffer. |
Drink 'till she's cute, but stop before the wedding. |
A bird in the hand is dead. |
If you tied buttered toast to the back of a cat and dropped it from a height, what would happen? |
I'm defending her honor, which is more than she ever did. |
Q: Did you hear about the butcher who accidentally backed into the meat grinder? A: He got a little behind in his work. |
The only substitute for good manners is fast reflexes. |
It is a miracle that curiosity survives formal education. Albert Einstein |
Is there another word for synonym? |
Two women walked into a building. You'd think at least one of them would have seen it. |
Two elephants walk off a cliff... boom, boom! |
Why do the Alphabet song and Twinkle, Twinkle Little Star have the same tune? |
I would love to, but I have to thaw some karate chops for dinner. |
Those who live by the sword get shot by those who don't. |
If you're not part of the solution, be part of the problem! |
Always remember you are unique - just like everyone else. |
Of the choice of two evils, I pick the one I've never tried before. |
Don't squat with your spurs on. |
Don't force it, get a larger hammer. |
When I'm feeling down, I like to whistle. It makes the neighbor's dog run to the end of his chain and gag himself. |
No one is listening until you fart. |
Q: Why are gold fish orange? A: The water makes them rusty! |
Ambition is a poor excuse for not having enough sense to be lazy. |
For Sale: Parachute. Only used once, never opened, small stain. |
If the police arrest a mime, do they tell him he has the right to remain silent? |
Herblock's Law: if it is good, they will stop making it. |
Who is General Failure and why is he reading my hard disk? |
The only difference between a rut and a grave is the depth. |
I would love to, but I'm touring China with a wok band. I'm trying desperately to be less popular. |
If at first you don't succeed, skydiving is not for you. |
"You know, somebody actually complimented me on my driving today. They left a little note on the windscreen. It said, 'Parking Fine.' So that was nice." |
Mediocrity thrives on standardization. |
Why do they sterilize needles for lethal injections? |
Q: What do you call a guy turned on by a witch? A: Scared stiff. |
I think the condoms need to be located in the baby aisle next to the 30 dollar diapers and 20 dollar formula cans. |
Always take time to stop and smell the roses... and sooner or later, you'll inhale a bee. |
A man's home is his castle, in a manor of speaking. |
I almost had a psychic girlfriend but she left me before we met. |
Reality's the only obstacle to happiness. |
With great power comes a great electricity bill. |
If everything seems to be going well, you have obviously overlooked something. |
Shin: a device for finding furniture in the dark. |
I used to have an open mind but my brains kept falling out. |
I went to buy some camouflage trousers the other day but I couldn't find any. |
This land is your land. This land is my land. So stay on your land. |
It's always darkest before dawn. So if you're going to steal the neighbor's newspaper, that's the time to do it. |
I would love to, but I'm having my baby shoes bronzed. |
Q: Why did the Vampire get fired from the Blood Bank? A: He was caught drinking on the job. |
I used to be a lumberjack, but I just couldn't hack it, so they gave me the ax. |
Ever wonder what the speed of lightning would be if it didn't zigzag? |
A President of a democracy is a man who is always ready, willing, and able to lay down your life for his country. |
What the bra say to the hat? You go on ahead, while I give these two a lift. |
When all else fails, read the instructions. |
I would love to, but my yucca plant is feeling yucky. |
If you can't read this, you're illiterate. |
I went for a walk last night and my kids asked me how long I'd be gone. I said, "The whole time." |
Do something unusual today. Accomplish work on the computer. |
A gossip is someone with a great sense of rumor. |
Some grow with responsibility, others just swell. |
Apparently, 1 in 5 people in the world are Chinese. There are 5 people in my family, so it must be one of them. It's either my mum or my Dad, or my older brother Colin, or my younger brother Ho-Cha-Chu. But I think it's Colin. |
Why do midgets laugh when they run? Coz the grass tickles their balls! |
Duct tape is like the Force. It has a light side & a dark side, and it holds the universe together. |
Why does your OB-GYN leave the room when you get undressed if they are going to look up there anyway? |
I would love to, but I've been traded to Cincinnati. |
I would love to, but I'm too young for that stuff. |
Q: What travels all around the world while staying in one place? A: A postage stamp. |
QUASIMOTO - 4 wheeled hard-top moped made in France. |
An apple a day keeps the doctor away... so does having no medical insurance. |
The interest in ironing is decreasing. |
One day YouTube, Twitter and Facebook will merge and be known as YouTwitFace. |
As they say at the Planned Parenthood Clinic, better late than never! |
I used to work in a blanket factory, but it folded. |
If you think nobody cares if you're alive, try missing a couple of car payments. |
Death is life's way of telling you you've been fired. |
If you don't like my driving, don't call anyone. Just take another road. That's why the highway department made so many of them. |
Sex is like air. It's not important unless you aren't getting any. |
Never play strip poker with a nudist, they have nothing to lose. |
I would love to, but I'm going to the Missing Persons Bureau to see if anyone is looking for me. |
If quizzes are quizzical, what are tests? |
What if there were no hypothetical questions? |
Q: What is the best thing about schizophrenia? A: You're never alone! |
I went to the butchers the other day and I bet him 50 quid that he couldn't reach the meat off the top shelf. He said, "No, the steaks are too high." |
Marriage is very much like a violin; after the sweet music is over, the strings are attached. |
Why don't they just make mouse-flavoured cat food? |
Why do toasters always have a setting that burns the toast to a horrible crisp, which no decent human being would eat? |
'Doc I can't stop singing The Green, Green Grass of Home' "That sounds like Tom Jones syndrome." 'Is it common?' "It's not unusual." |
A day without sunshine is like, well, night. |
I drive way too fast to worry about cholesterol. |
Man who scratches ass should not bite fingernails! |
I went to school to become a wit, only got halfway through. |
Q: Why did the nurse go to art school? A: To learn how to draw blood! |
My camera is broken. But, I won't have a negative attitude - I'll take it to the repair shop and see what develops. |
Our ice cream man was found lying on the floor of his van covered with hundreds and thousands. Police say that he topped himself. |
Mail your packages early so that the post office can lose them in time for Christmas! |
Before you criticize someone, you should walk a mile in their shoes. That way, when you criticize them, you're a mile away and you have their shoes. |
Isn't Disney World just a people trap operated by a mouse? |
SYSTEM GOING DOWN AT 4:45 THIS AFTERNOON FOR DISK CRASHING. |
I feel like I'm diagonally parked in a parallel universe. |
It's a small world, but I wouldn't want to paint it. |
I just got skylights put in my place. The people who live above me are furious. |
Phone answering machine message - "... If you want to buy marijuana, press the hash key...". |
Pirates make the best music because they write everything with a hook. |
Funeral homes are forever getting stiffed. |
Just remember... You gotta break some eggs to make a real mess on the neighbor's car! |
I would love to, but my Dress For Obscurity class meets then. |
Computer programmers don't byte, they nybble a bit. |
I would love to, but I'm having all my plants neutered. |
Does the name Pavlov ring a bell? |
The attention span of a computer is as long as its electrical cord. |
A diplomat is someone who can tell you to go to hell in such a way that you will look forward to the trip. |
If you can't be kind, at least have the decency to be vague. |
Documentation is like sex: When it's good, it's fantastic, when it's bad... |
When you find yourself getting irritated with someone, try to remember that all men are brothers... and just give them a noogie or a swirly. |
In University I was going to join the debate team, but someone talked me out of it. |
One nice thing about egotists: they don't talk about other people. |
Q: Who earns a living by driving his customers away? A: A taxi driver. |
So what's the speed of dark? |
My maid is a commercial cleaner... She only cleans during commercials! |
Automobile - A mechanical device that runs up hills and down people. |
I don't know why I broke up with my girl at the gym. Guess we just weren't working out. |
What we could really use is the separation of Bush and state. |
Generally speaking, you aren't learning much when your mouth is moving. |
The first rule of holes: If you are in one, stop digging. |
Q: What do you call a sleeping bull? A: A bulldozer |
Do Lipton employees take coffee breaks? |
I poured Spot remover on my dog. Now he's gone. |
A penny saved is ridiculous. |
The quickest way to double your money is to fold it in half and put it back in your pocket. |
If you are not the lead dog, the scenery never changes. |
I used to do yoga, but their expectations of me were too high. They wanted me to bend over backwards for them. |
How do you tell when you run out of invisible ink? |
Let not the sands of time get in your lunch. |
All that glitters has a high refractive index. |
Nothing is fool-proof to a sufficiently talented fool. |
Last night I had a dream that I ate a ten pound marshmallow. When I woke up, my pillow was gone. |
I would love to, but I'm waiting to see if I'm already a winner. |
When the chips are down, the buffalo is empty. |
Brain -- the apparatus with which we think that we think. |
If it's true that we are here to help others, then what exactly are the OTHERS here for? |
If quitters never win, and winners never quit, what fool came up with, "Quit while you're ahead?" |
Q: What do ceramic tile and men have in common? A: If you lay them right the first time, you can walk on them for life! |
I told a girl her eyebrows were drawn on too high. She looked surprised. |
I really think the Mars Rover is scouting for the next Wal-Mart Superstore site. |
If you are given two contradictory orders, obey them both. |
KODACLONE - duplicating film. |
It's always darkest before dawn. So if you're going to steal your neighbor's newspaper, that's the time to do it. |
I would love to, but my uncle escaped, again. |
Health is merely the slowest possible rate at which one can die. |
Join the Army, meet interesting people, kill them. |
My friend drowned in a bowl of muesli. A strong currant pulled him in. |
Ghosts are hard to impress. They boo everything. |
When everything's coming your way, you're in the wrong lane. |
Despite the cost of living, have you noticed how it remains so popular? |
Anarchy is better that no government at all. |
Life's a bitch, then you die. |
To err is human, to really foul things up requires a computer. |
Condoms should be used on every conceivable occasion. |
Two Eskimos sitting in a kayak were chilly. They lit a fire in the craft, it sank, proving once and for all that you can't have your kayak and heat it. |
Give a man a free hand and he'll run it all over you. |
YTERM - A terminal program for queries. |
Clones are people two. |
Everyone has a photographic memory. Some don't have film. |
If you cannot convince them, confuse them. |
This morning I woke up to the unmistakable scent of pigs in a blanket. That's the price you pay for letting the relatives stay over. |
I would love to, but I never go out on days that end in "Y." |
Marriage is an institution in which a man loses his Bachelor's Degree and the woman gets her Masters. |
Keep your nose to the grindstone and your shoulder to the wheel... it's cheaper than plastic surgery. |
I walked into the bedroom and tripped on the wife's Bra. It was a booby trap. |
A king's castle is his home. |
I would love to, but I have to be on the next train to Bermuda. |
I believe for every drop of rain that falls, a flower grows. And a foundation leaks and a ball game gets rained out and a car rusts and... |
Q: What do you get if you cross Bambi with a ghost? A: Bamboo. |
Any small object when dropped will hide under a larger object. |
It's amazing how fast your mood can change after you step in some water with socks on. |
I would love to, but I have to bleach my hare. |
Back up my hard drive? How do I put it in reverse? |
I would love to, but I promised to help a friend re-fold road maps. |
Does pushing the elevator button more than once make it arrive faster? |
I intend to live forever - so far, so good. |
When in doubt, don't bother. |
The journey of a thousand miles begins with a broken fan belt and a leaky tire. |
So I was getting into my car, and this bloke says to me "Can you give me a lift?" I said "Sure, you look great, the world's your oyster, go for it.' |
I'd insult you, but you're not bright enough to notice. |
Q: What is the difference between a guitar and a tuna fish? A: You can tune a guitar but you can't tuna fish. |
Life is a lot like toilet paper. You're either on a roll.....or you're taking shit from some asshole. |
Do illiterate people get the full effect of Alphabet Soup? |
If all is not lost, where is it? |
Some people are like clouds. When they disappear it’s a brighter day. |
People think Cupid is a symbol for love. Personally, I find an arrow being shot through your heart by a flying baby very horrifying. |
Nostalgia isn't what it used to be. |
What hair colour do they put on the driver's licenses of bald people? |
Buried my grandmother in the wrong plot. That was a grave mistake. |
The little boy asked his dad one evening, "Daddy, how much does it cost to get married?" "I don't know, son," he said. "I'm still paying for it." |
I'm not cheap, but I am on special this week. |
If you can't drink and drive, why do bars have parking lots? |
If the #2 pencil is the most popular, why is it still #2? |
If God wanted me to touch my toes, He would have put them on my knees. |
If you take an Oriental person and spin him around several times, does he become disoriented? |
A backscratcher will always find new itches; a brown-noser will always find new sense. |
If you drink, don't park; accidents cause people. |
Indifference will be the downfall of mankind, but who cares? |
All those who believe in psychokinesis raise my hand. |
Corduroy pillows are making headlines. |
If Jimmy cracks corn and no one cares, why is there a song about him? |
It's hard to make a comeback when you haven't been anywhere. |
Laughing stock: cattle with a sense of humor. |
If you are asked to join a parade, don't march behind the elephants. |
My grandfather died due to shoddy hospital care. I wouldn't have minded, but he was only in there to visit my grandma. |
If you cannot dazzle them with brilliance, baffle them with lies. |
It's not hard to meet expenses. They're everywhere. |
Quantum Mechanics: The dreams stuff is made of. |
Women always call me ugly until they find out how much money I make. Then they call me ugly and poor. |
Q: Why didn't the dog want to play football? A: It was a boxer! |
Q: What do you call a virgin on a waterbed? A: A cherry float. |
Mind Like A Steel Trap Rusty And Illegal In 37 States. |
Marriage is a thing which puts a ring on a woman's finger and two under the man's eyes. |
When I'm not in my right mind, my left mind gets pretty crowded. |
Q: What's the difference between sin and shame? A: It is a sin to put it in, but it's a shame to pull it out. |
Shotgun wedding: A case of wife or death. |
Can a hearse carrying a corpse drive in the carpool lane? |
It's a small world. So you gotta use your elbows a lot. |
It works better if you plug it in. |
I just got lost in thought. It was unfamiliar territory. |
Whoever said money doesn't grow on trees has obviously never sold weed. |
A man needs a mistress just to break the monogamy. |
I would love to, but I have too much guilt. |
Silver's law: If Murphy's law can go wrong it will. |
If corn oil is made from corn, and vegetable oil is made from vegetables, then what is baby oil made from? |
Do not walk behind me, for I may not lead. Do not walk ahead of me, for I may not follow. Do not walk beside me, either, just leave me alone. |
If you choke a smurf, what color does it turn? |
When two egotists meet, it's an I for an I. |
What do you call male ballerinas? |
Marriage is not a word. It is a sentence--a life sentence. |
Q: What do you say if you meet a toad? A: Wart's new! |
There are two theories to arguing with women. Neither one works. |
Computers are not intelligent. They only think they are. |
Corduroy pillows: They're making headlines! |
Seen it all, done it all, can't remember most of it. |
Man goes to the doctor, with a strawberry growing out of his bum. Doc says "I'll give you some cream to put on it." |
Change is inevitable, except from a vending machine. |
Fairy tales: horror stories for children to get them use to reality. |
If you are worried about being crazy, don't be overly concerned. If you were, you would think you were sane. |
I would love to, but I'm uncomfortable when I'm alone or with others. |
What do you call a fish with no eyes ? A fsh. |
Never test the depth of the water with both feet. |
Don't hate yourself in the morning -- sleep till noon. |
Dancing is a perpendicular expression of a horizontal desire. |
Isn't Disney World a people trap operated by a mouse? |
Why do people point to their wrist when asking for the time, but don't point to their crotch when they ask where the bathroom is? |
Pardon my driving, I am reloading. |
Sea captains don't like crew cuts. |
Q: Why aren't there any famous skeletons? A: They're a bunch of no bodies. |
If you are feeling good, don't worry. You'll get over it. |
I would love to, but I'm going to be old someday. |
Marriage certificate is just another word for a work permit. |
Who was the first person to say, "See that chicken there....I'm gonna eat the next thing that comes outta it's butt"? |
A clean tie attracts the soup of the day. |
Into every life some rain must fall. Usually when your car windows are down. |
Who was the first person to look at a cow and say, "I think I'll squeeze these dangly things here, and drink whatever comes out"? |
I'm a sexual health doctor for the lower ranks of the military. I inspect the privates. |
Excuses are like asses everyone's got em and they all stink. |
I tried sniffing Coke once, but the ice cubes got stuck in my nose. |
Q: What's another name for policemen when they're in bed? A: Undercover cops. |
If the professor on Gilligan's Island can make a radio out of coconut, why can't he fix a hole in a boat? |
If Barbie is so popular, why do you have to buy her friends? |
I went to the bank the other day and asked the banker to check my balance, so she pushed me! |
We are born naked, wet, and hungry. Then things get worse. |
SQWERTY - Computer keyboard sized down for use by children. |
I used to have winter fat but now I have spring rolls. |
I would love to, but Oooo, having fun gives me prickly heat. |
If Wile E. Coyote had enough money to buy all that Acme crap, why didn't he just buy dinner? |
Marriage is the mourning after the knot before. |
Why does Goofy stand erect while Pluto remains on all fours? They're both dogs! |
I would love to, but I think you want the OTHER (fill in your name here). |
Courage is your greatest present need. |
The road to to success is always under construction. |
TRAPEZOID - A device for catching zoids. |
If you tell the truth, you don't have to remember anything. |
Going the speed of light is bad for your age. |
I couldn't repair your brakes, so I made your horn louder. |
Return ticket. That takes me back. |
The only time the world beats a path to your door is if you're in the bathroom. |
QUARKBAR - the candy with flavour and charm. |
If you jog backwards, will you gain weight? |
Boycott shampoo! Demand the REAL poo! |
A closed mouth gathers no foot. |
I would love to, but I prefer to remain an enigma. |
I tried to join the Paranoia's Anonymous, but they would't tell me where they were. |
Good judgment comes from bad experience, and a lot of that comes from bad judgment. |
Never lick a gift horse in the mouth. |
A successful diet is the triumph of mind over platter. |
I wish the buck stopped here. I could use a few... |
Two goldfish are in a tank. One turns to the other and says, "Do you know how to drive this thing?" |
A handy telephone tip: Keep a small chalkboard near the phone. That way, when a salesman calls, you can hold the receiver up to it and run your fingernails across it until he hangs up. |
If you are running for a short line, it suddenly becomes a long line. |
If one synchronized swimmer drowns, do the rest drown, too? |
Give a man a fish and he will eat for a day. Teach him how to fish, and he will sit in a boat & drink beer all day. |
If you shake up a can of beer, and spill it on your stove, do you get foam on the range? |
I heard a great joke about amnesia but I forgot it. |
When you dream in color, it's a pigment of your imagination. |
Wear short sleeves! Support your right to bare arms! |
Xerox does it again and again and again and... |
Marriage is not just a having a wife, but also worries inherited forever. |
Energizer Bunny arrested; charged with battery. |
Two fat blokes in a pub, one says to the other "Your round." |
Be moderate where pleasure is concerned, avoid fatigue. |
Screw up your life, you've screwed everything else up. |
Marriage is love. Love is blind. Therefore, marriage is an institution for the blind. |
I would love to, but I'm too old for that stuff. |
It is far more impressive when others discover your good qualities without your help. |
Those who can't write, write help files. |
If you lend someone $20, and never see that person again, it was probably worth it. |
I went to a seafood disco last week... and pulled a muscle. |
My best friend ran away with my wife... I miss him. |
All reports are in. Life is now officially unfair. |
He who laughs last, thinks slowest. |
If you can smile when things go wrong, you must have someone to blame. |
Reading whilst sunbathing makes you well-red. |
A bird in the bush usually has a friend in there with him. |
I would love to, but my bathroom tiles need grouting. |
He who hesitates is sometimes saved. |
I used to think maths was useless but then one day I realized that decimals had a point. |
A bird in the hand is always safer than one overhead. |
If electricity comes from electrons, does that mean that morality comes from morons? |
Eagles may soar, but weasels don't get sucked into jet engines. |
Mental backup in progress - Do Not Disturb! |
Black holes are where God divided by zero. |
Sign in restaurant window: "Eat now - Pay waiter." |
In the field of observation, chance favors only the prepared minds. |
Honk if you love peace and quiet. |
My wife says we should spice up our sex life with some stuff from 50 Shades of Grey. First, she wants me to become a billionaire. |
To be, or not to be, those are the parameters. |
I would love to, but my mother would never let me hear the end of it. |
Q: What do you give a man with an artificial heart? A: Three weeks. |
My wife really likes to make pottery, but to me it's just kiln time. |
Wasting time is an important part of life. |
Old MacDonald had an agricultural real estate tax abatement. |
XMODEM - A spot-marking transfer protocol. |
Help support helpless victims of computer error. |
Many people quit looking for work when they find a job. |
A vampire walks into a bar, and asks for a "Large glass of A-positive blood." The bartender looks him square in the eyes, and says "I'm sorry, but we don't serve your type here!" |
/heads-up-display/dialog-passthrough/dialog-passthrough.lsl |
@@ -0,0 +1,502 @@ |
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) Wizardry and Steamworks 2016 - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// |
// This script is a passthrough or proxy script for dialogs received by |
// Corrade. The script binds to Corrade's dialog notification and then any |
// dialog received by Corrade gets sent to you. When you press a button on |
// the dialog, Corrade will press the button on the dialog it received. |
// |
// For more information on Corrade, please see: |
// http://grimore.org/secondlife/scripted_agents/corrade |
// |
/////////////////////////////////////////////////////////////////////////// |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueGet(string k, string data) { |
if(llStringLength(data) == 0) return ""; |
if(llStringLength(k) == 0) return ""; |
list a = llParseString2List(data, ["&", "="], []); |
integer i = llListFindList(llList2ListStrided(a, 0, -1, 2), [ k ]); |
if(i != -1) return llList2String(a, 2*i+1); |
return ""; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueEncode(list data) { |
list k = llList2ListStrided(data, 0, -1, 2); |
list v = llList2ListStrided(llDeleteSubList(data, 0, 0), 0, -1, 2); |
data = []; |
do { |
data += llList2String(k, 0) + "=" + llList2String(v, 0); |
k = llDeleteSubList(k, 0, 0); |
v = llDeleteSubList(v, 0, 0); |
} while(llGetListLength(k) != 0); |
return llDumpList2String(data, "&"); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// escapes a string in conformance with RFC1738 |
string wasURLEscape(string i) { |
string o = ""; |
do { |
string c = llGetSubString(i, 0, 0); |
i = llDeleteSubString(i, 0, 0); |
if(c == "") jump continue; |
if(c == " ") { |
o += "+"; |
jump continue; |
} |
if(c == "\n") { |
o += "%0D" + llEscapeURL(c); |
jump continue; |
} |
o += llEscapeURL(c); |
@continue; |
} while(i != ""); |
return o; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
list wasCSVToList(string csv) { |
list l = []; |
list s = []; |
string m = ""; |
do { |
string a = llGetSubString(csv, 0, 0); |
csv = llDeleteSubString(csv, 0, 0); |
if(a == ",") { |
if(llList2String(s, -1) != "\"") { |
l += m; |
m = ""; |
jump continue; |
} |
m += a; |
jump continue; |
} |
if(a == "\"" && llGetSubString(csv, 0, 0) == a) { |
m += a; |
csv = llDeleteSubString(csv, 0, 0); |
jump continue; |
} |
if(a == "\"") { |
if(llList2String(s, -1) != a) { |
s += a; |
jump continue; |
} |
s = llDeleteSubList(s, -1, -1); |
jump continue; |
} |
m += a; |
@continue; |
} while(csv != ""); |
// postcondition: length(s) = 0 |
return l + m; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// unescapes a string in conformance with RFC1738 |
string wasURLUnescape(string i) { |
return llUnescapeURL( |
llDumpList2String( |
llParseString2List( |
llDumpList2String( |
llParseString2List( |
i, |
["+"], |
[] |
), |
" " |
), |
["%0D%0A"], |
[] |
), |
"\n" |
) |
); |
} |
|
// callback URL |
string callback = ""; |
// configuration data |
string configuration = ""; |
// listen handle |
integer listenHandle = 0; |
|
// store dialog details |
key dialog_item = NULL_KEY; |
key dialog_id = NULL_KEY; |
integer dialog_channel = 0; |
list dialog_buttons = []; |
string dialog_message = ""; |
|
default { |
state_entry() { |
llSetTimerEvent(1); |
} |
link_message(integer sender, integer num, string message, key id) { |
if(sender != 1 || id != "configuration") return; |
configuration = message; |
state off; |
} |
timer() { |
llMessageLinked(LINK_ROOT, 0, "configuration", NULL_KEY); |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
|
state off { |
state_entry() { |
llReleaseControls(); |
llSetColor(<.5,0,0>, ALL_SIDES); |
} |
touch_end(integer num) { |
state on; |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
} |
|
state on { |
state_entry() { |
llSetColor(<0,.5,0>, ALL_SIDES); |
state url; |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
} |
|
state url { |
state_entry() { |
// DEBUG |
llOwnerSay("Requesting URL..."); |
llRequestURL(); |
} |
touch_end(integer num) { |
llSetColor(<.5,0,0>, ALL_SIDES); |
llResetScript(); |
} |
http_request(key id, string method, string body) { |
if(method != URL_REQUEST_GRANTED) return; |
callback = body; |
// DEBUG |
llOwnerSay("Got URL..."); |
state dialog; |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
} |
|
state dialog { |
state_entry() { |
// DEBUG |
llOwnerSay("Binding to the dialog notification..."); |
llInstantMessage( |
(key)wasKeyValueGet( |
"corrade", |
configuration |
), |
wasKeyValueEncode( |
[ |
"command", "notify", |
"group", wasURLEscape( |
wasKeyValueGet( |
"group", |
configuration |
) |
), |
"password", wasURLEscape( |
wasKeyValueGet( |
"password", |
configuration |
) |
), |
"action", "set", |
"type", "dialog", |
"URL", wasURLEscape(callback), |
"callback", wasURLEscape(callback) |
] |
) |
); |
llSetTimerEvent(60); |
} |
touch_end(integer num) { |
llSetColor(<.5,0,0>, ALL_SIDES); |
llResetScript(); |
} |
timer() { |
// DEBUG |
llOwnerSay("Timeout binding to the dialog notification..."); |
llResetScript(); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) != "notify" || |
wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Failed to bind to the dialog notification..." + wasKeyValueGet("error", body)); |
llResetScript(); |
} |
// DEBUG |
llOwnerSay("Dialog notification installed..."); |
state main; |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
|
state main { |
state_entry() { |
// DEBUG |
llOwnerSay("Waiting for dialog..."); |
llSetTimerEvent(1); |
} |
touch_end(integer num) { |
state uninstall; |
} |
timer() { |
llRequestAgentData( |
(key)wasKeyValueGet( |
"corrade", |
configuration |
), DATA_ONLINE); |
} |
dataserver(key id, string data) { |
if(data != "1") llResetScript(); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
|
// get the dialog id |
dialog_id = (key)wasURLUnescape( |
wasKeyValueGet( |
"id", |
body |
) |
); |
|
// get the dialog message |
dialog_message = wasURLUnescape( |
wasKeyValueGet( |
"message", |
body |
) |
); |
|
// get the UUID of the object sending the dialog |
dialog_item = (key)wasURLUnescape( |
wasKeyValueGet( |
"item", |
body |
) |
); |
|
// get the channel that the dialog was sent on |
dialog_channel = (integer)wasURLUnescape( |
wasKeyValueGet( |
"channel", |
body |
) |
); |
|
// get the buttons that the dialog has |
dialog_buttons = llList2ListStrided( |
llDeleteSubList( |
wasCSVToList( |
wasURLUnescape( |
wasKeyValueGet( |
"button", |
body |
) |
) |
), |
0, |
1 |
), |
0, |
-1, |
2 |
); |
|
// DEBUG |
llOwnerSay("Got buttons: " + llDumpList2String(dialog_buttons, "|")); |
listenHandle = llListen(-14, "", llGetOwner(), ""); |
llDialog(llGetOwner(), "[CORRADE]: " + dialog_message, dialog_buttons, -14); |
} |
listen(integer channel, string name, key id, string message) { |
llListenRemove(listenHandle); |
integer index = llListFindList(dialog_buttons, (list)message); |
if(index == -1) { |
// DEBUG |
llOwnerSay("Button index not found..."); |
return; |
} |
|
llInstantMessage( |
wasKeyValueGet( |
"corrade", |
configuration |
), |
wasKeyValueEncode( |
[ |
"command", "replytoscriptdialog", |
"group", wasURLEscape( |
wasKeyValueGet( |
"group", |
configuration |
) |
), |
"password", wasURLEscape( |
wasKeyValueGet( |
"password", |
configuration |
) |
), |
"action", "reply", |
"dialog", dialog_id, |
"button", wasURLEscape(message), |
"index", index |
] |
) |
); |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
|
state uninstall { |
state_entry() { |
// DEBUG |
llOwnerSay("Uninstalling dialog notification..."); |
llInstantMessage( |
(key)wasKeyValueGet( |
"corrade", |
configuration |
), |
wasKeyValueEncode( |
[ |
"command", "notify", |
"group", wasURLEscape( |
wasKeyValueGet( |
"group", |
configuration |
) |
), |
"password", wasURLEscape( |
wasKeyValueGet( |
"password", |
configuration |
) |
), |
"action", "remove", |
"type", "dialog", |
"URL", wasURLEscape(callback), |
"callback", wasURLEscape(callback) |
] |
) |
); |
llSetTimerEvent(60); |
} |
timer() { |
// DEBUG |
llOwnerSay("Timeout uninstalling the dialog notification..."); |
llResetScript(); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) != "notify" || |
wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Failed to uninstall the dialog notification..." + wasKeyValueGet("error", body)); |
llResetScript(); |
} |
// DEBUG |
llOwnerSay("Dialog notification uninstalled..."); |
llResetScript(); |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
} |
/heads-up-display/grid-follow/grid-follow.lsl |
@@ -0,0 +1,702 @@ |
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// |
// This is an automatic grid follower for the Corrade Second Life / OpenSim |
// bot. It uses two different engines: the simulator's built-in autopilot |
// and a fly engine that will make Corrade fly to your location. |
// You can find more details about the bot by following the URL: |
// http://grimore.org/secondlife/scripted_agents/corrade |
// |
// The follower script works together with a "configuration" notecard and |
// that must be placed in the same primitive as this script. |
// You are free to use, change, and commercialize it under the GNU/GPLv3 |
// license at: http://www.gnu.org/licenses/gpl.html |
// |
/////////////////////////////////////////////////////////////////////////// |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueGet(string k, string data) { |
if(llStringLength(data) == 0) return ""; |
if(llStringLength(k) == 0) return ""; |
list a = llParseString2List(data, ["&", "="], []); |
integer i = llListFindList(a, [ k ]); |
if(i != -1) return llList2String(a, i+1); |
return ""; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueEncode(list data) { |
list k = llList2ListStrided(data, 0, -1, 2); |
list v = llList2ListStrided(llDeleteSubList(data, 0, 0), 0, -1, 2); |
data = []; |
do { |
data += llList2String(k, 0) + "=" + llList2String(v, 0); |
k = llDeleteSubList(k, 0, 0); |
v = llDeleteSubList(v, 0, 0); |
} while(llGetListLength(k) != 0); |
return llDumpList2String(data, "&"); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2011 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
vector wasCirclePoint(float radius) { |
float x = llPow(-1, 1 + (integer) llFrand(2)) * llFrand(radius*2); |
float y = llPow(-1, 1 + (integer) llFrand(2)) * llFrand(radius*2); |
if(llPow(x,2) + llPow(y,2) <= llPow(radius,2)) |
return <x, y, 0>; |
return wasCirclePoint(radius); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
integer wasIsAvatarInSensorRange(key avatar) { |
return llListFindList( |
llGetAgentList( |
AGENT_LIST_REGION, |
[] |
), |
(list)((key)avatar) |
) != -1 && |
llVecDist( |
llGetPos(), |
llList2Vector( |
llGetObjectDetails( |
avatar, |
[OBJECT_POS] |
), |
0 |
) |
) <= 96; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// escapes a string in conformance with RFC1738 |
string wasURLEscape(string i) { |
string o = ""; |
do { |
string c = llGetSubString(i, 0, 0); |
i = llDeleteSubString(i, 0, 0); |
if(c == "") jump continue; |
if(c == " ") { |
o += "+"; |
jump continue; |
} |
if(c == "\n") { |
o += "%0D" + llEscapeURL(c); |
jump continue; |
} |
o += llEscapeURL(c); |
@continue; |
} while(i != ""); |
return o; |
} |
|
// for holding the callback URL |
string callback = ""; |
|
// key-value data will be read into this list |
string configuration = ""; |
|
default { |
state_entry() { |
llSetTimerEvent(1); |
} |
link_message(integer sender, integer num, string message, key id) { |
if(sender != 1 || id != "configuration") return; |
configuration = message; |
state off; |
} |
timer() { |
llMessageLinked(LINK_ROOT, 0, "configuration", NULL_KEY); |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if(change & CHANGED_INVENTORY || (change & CHANGED_OWNER)) |
llResetScript(); |
if( |
(change & CHANGED_REGION_START) || |
(change & CHANGED_REGION)) { |
state on; |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
|
state off { |
state_entry() { |
llSetColor(<.5,0,0>, ALL_SIDES); |
} |
touch_end(integer num) { |
state on; |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if(change & CHANGED_INVENTORY || (change & CHANGED_OWNER)) |
llResetScript(); |
} |
} |
|
state on { |
state_entry() { |
llSetColor(<0,.5,0>, ALL_SIDES); |
state url; |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if(change & CHANGED_INVENTORY || (change & CHANGED_OWNER)) |
llResetScript(); |
} |
} |
|
state url { |
state_entry() { |
// DEBUG |
llOwnerSay("Requesting URL..."); |
llRequestURL(); |
} |
touch_end(integer num) { |
llSetColor(<.5,0,0>, ALL_SIDES); |
llResetScript(); |
} |
http_request(key id, string method, string body) { |
if(method != URL_REQUEST_GRANTED) return; |
callback = body; |
// DEBUG |
llOwnerSay("Got URL..."); |
state detect; |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if(change & CHANGED_INVENTORY || (change & CHANGED_OWNER)) |
llResetScript(); |
if( |
(change & CHANGED_REGION_START) || |
(change & CHANGED_REGION)) { |
state on; |
} |
} |
} |
|
state detect { |
state_entry() { |
// set color for button |
llSetColor(<0,.5,0>, ALL_SIDES); |
// if Corrade is in-range then just follow |
if(wasIsAvatarInSensorRange( |
(key)wasKeyValueGet( |
"corrade", |
configuration |
) |
) |
) state select; |
// DEBUG |
llOwnerSay("Detecting if Corrade is online..."); |
llRequestAgentData( |
(key)wasKeyValueGet( |
"corrade", |
configuration |
), |
DATA_ONLINE |
); |
} |
touch_end(integer num) { |
llSetColor(<.5,0,0>, ALL_SIDES); |
llResetScript(); |
} |
dataserver(key id, string data) { |
if(data != "1") { |
// DEBUG |
llOwnerSay("Corrade is not online, sleeping..."); |
llResetScript(); |
return; |
} |
llSensorRepeat("", |
(key)wasKeyValueGet( |
"corrade", |
configuration |
), |
AGENT, |
(integer)wasURLEscape( |
wasKeyValueGet( |
"range", |
configuration |
) |
), |
TWO_PI, |
5 |
); |
} |
no_sensor() { |
// DEBUG |
llOwnerSay("Teleporting Corrade..."); |
llInstantMessage( |
(key)wasKeyValueGet( |
"corrade", |
configuration |
), |
wasKeyValueEncode( |
[ |
"command", "teleport", |
"group", wasURLEscape( |
wasKeyValueGet( |
"group", |
configuration |
) |
), |
"password", wasURLEscape( |
wasKeyValueGet( |
"password", |
configuration |
) |
), |
"region", wasURLEscape(llGetRegionName()), |
"position", llGetPos() + wasCirclePoint( |
(integer)wasURLEscape( |
wasKeyValueGet( |
"range", |
configuration |
) |
) |
), |
"deanimate", "True", |
"callback", callback |
] |
) |
); |
} |
sensor(integer num) { |
state select; |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) != "teleport" || |
wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Teleport failed..."); |
return; |
} |
state select; |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if(change & CHANGED_INVENTORY || (change & CHANGED_OWNER)) |
llResetScript(); |
if( |
(change & CHANGED_REGION_START) || |
(change & CHANGED_REGION)) { |
state on; |
} |
} |
} |
|
state select { |
state_entry() { |
// DEBUG |
llOwnerSay("Selecting follow engine..."); |
llSetTimerEvent(1); |
} |
touch_end(integer num) { |
llSetColor(<.5,0,0>, ALL_SIDES); |
llResetScript(); |
} |
timer() { |
vector cPos = llList2Vector( |
llGetObjectDetails( |
(key)wasKeyValueGet( |
"corrade", |
configuration |
), |
[OBJECT_POS] |
), |
0 |
); |
vector mPos = llGetPos(); |
// If Corrade is in range then stop. |
if(llVecDist(mPos, cPos) < 5) return; |
// If we are flying or the distance between us and Corrade |
// is larger than a given distance then make Corrade fly. |
if( |
( |
llGetAgentInfo(llGetOwner()) & AGENT_FLYING |
) || |
llFabs(mPos.z - cPos.z) > 5) { |
state stop_pilot; |
} |
// Otherwise, stop flight and walk. |
if(llGetAgentInfo( |
(key)wasKeyValueGet( |
"corrade", |
configuration |
) |
) & AGENT_FLYING) state stop_flight; |
// If Corrade is not flying then walk. |
state walk; |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if(change & CHANGED_INVENTORY || (change & CHANGED_OWNER)) |
llResetScript(); |
if( |
(change & CHANGED_REGION_START) || |
(change & CHANGED_REGION)) { |
state on; |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
|
state stop_pilot { |
state_entry() { |
// DEBUG |
llOwnerSay("Stopping autopilot for flight..."); |
llRegionSayTo( |
(key)wasKeyValueGet( |
"corrade", |
configuration |
), |
0, |
wasKeyValueEncode( |
[ |
"command", "autopilot", |
"group", wasURLEscape( |
wasKeyValueGet( |
"group", |
configuration |
) |
), |
"password", wasURLEscape( |
wasKeyValueGet( |
"password", |
configuration |
) |
), |
// move in a radius around the current primitive. |
"position", llGetPos() + wasCirclePoint( |
(integer)wasURLEscape( |
wasKeyValueGet( |
"range", |
configuration |
) |
) |
), |
"action", "stop", |
"callback", wasURLEscape(callback) |
] |
) |
); |
llSetTimerEvent(60); |
} |
timer() { |
state on; |
} |
touch_end(integer num) { |
llSetColor(<.5,0,0>, ALL_SIDES); |
llResetScript(); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) != "autopilot" || |
wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Failed to stop autopilot..."); |
state on; |
} |
state fly; |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if(change & CHANGED_INVENTORY || (change & CHANGED_OWNER)) |
llResetScript(); |
if( |
(change & CHANGED_REGION_START) || |
(change & CHANGED_REGION)) { |
state on; |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
|
state fly { |
state_entry() { |
// DEBUG |
llOwnerSay("Flying to agent..."); |
llRegionSayTo( |
(key)wasKeyValueGet( |
"corrade", |
configuration |
), |
0, |
wasKeyValueEncode( |
[ |
"command", "flyto", |
"group", wasURLEscape( |
wasKeyValueGet( |
"group", |
configuration |
) |
), |
"password", wasURLEscape( |
wasKeyValueGet( |
"password", |
configuration |
) |
), |
// move in a radius around the current primitive. |
"position", llGetPos() + wasCirclePoint( |
(integer)wasURLEscape( |
wasKeyValueGet( |
"range", |
configuration |
) |
) |
), |
"fly", "True", |
"vicinity", wasCirclePoint( |
(integer)wasURLEscape( |
wasKeyValueGet( |
"range", |
configuration |
) |
) |
), |
"duration", "2500", |
"callback", wasURLEscape(callback) |
] |
) |
); |
llSetTimerEvent(60); |
} |
timer() { |
state on; |
} |
touch_end(integer num) { |
llSetColor(<.5,0,0>, ALL_SIDES); |
llResetScript(); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) != "flyto" || |
wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Flight failed..."); |
state on; |
} |
state select; |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if(change & CHANGED_INVENTORY || (change & CHANGED_OWNER)) |
llResetScript(); |
if( |
(change & CHANGED_REGION_START) || |
(change & CHANGED_REGION)) { |
state on; |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
|
state stop_flight { |
state_entry() { |
// DEBUG |
llOwnerSay("Landing agent for walking..."); |
llRegionSayTo( |
wasKeyValueGet( |
"corrade", |
configuration |
), |
0, |
wasKeyValueEncode( |
[ |
"command", "fly", |
"group", wasURLEscape( |
wasKeyValueGet( |
"group", |
configuration |
) |
), |
"password", wasURLEscape( |
wasKeyValueGet( |
"password", |
configuration |
) |
), |
"action", "stop", |
"callback", wasURLEscape(callback) |
] |
) |
); |
llSetTimerEvent(60); |
} |
timer() { |
state on; |
} |
touch_end(integer num) { |
llSetColor(<.5,0,0>, ALL_SIDES); |
llResetScript(); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) != "fly" || |
wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Landing failed..."); |
state on; |
} |
llOwnerSay("Landed..."); |
state walk; |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if(change & CHANGED_INVENTORY || (change & CHANGED_OWNER)) |
llResetScript(); |
if( |
(change & CHANGED_REGION_START) || |
(change & CHANGED_REGION)) { |
state on; |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
|
state walk { |
state_entry() { |
// DEBUG |
llOwnerSay("Walking to agent..."); |
llRegionSayTo( |
(key)wasKeyValueGet( |
"corrade", |
configuration |
), |
0, |
wasKeyValueEncode( |
[ |
"command", "autopilot", |
"group", wasURLEscape( |
wasKeyValueGet( |
"group", |
configuration |
) |
), |
"password", wasURLEscape( |
wasKeyValueGet( |
"password", |
configuration |
) |
), |
// move in a radius around the current primitive. |
"position", llGetPos() + wasCirclePoint( |
(integer)wasURLEscape( |
wasKeyValueGet( |
"range", |
configuration |
) |
) |
), |
"action", "start", |
"callback", wasURLEscape(callback) |
] |
) |
); |
llSetTimerEvent(60); |
} |
timer() { |
state on; |
} |
touch_end(integer num) { |
llSetColor(<.5,0,0>, ALL_SIDES); |
llResetScript(); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) != "autopilot" || |
wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Failed to start autopilot..."); |
state on; |
} |
state select; |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if(change & CHANGED_INVENTORY || (change & CHANGED_OWNER)) |
llResetScript(); |
if( |
(change & CHANGED_REGION_START) || |
(change & CHANGED_REGION)) { |
state on; |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
/heads-up-display/heads-up-display.lsl |
@@ -0,0 +1,320 @@ |
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) Wizardry and Steamworks 2016 - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// |
// A template HUD script for Corrade that reads a configuration notecard |
// and then feeds that configuration to various other components of the HUD |
// that request the configuration. Additionally, this HUD is programmed to |
// "fold" and "unfold" depending on whether Corrade is online or not. For |
// more information on the Corrade scripted agent please see the URL: |
// http://was.fm/secondlife/scripted_agents/corrade |
// |
/////////////////////////////////////////////////////////////////////////// |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueGet(string k, string data) { |
if(llStringLength(data) == 0) return ""; |
if(llStringLength(k) == 0) return ""; |
list a = llParseString2List(data, ["&", "="], []); |
integer i = llListFindList(llList2ListStrided(a, 0, -1, 2), [ k ]); |
if(i != -1) return llList2String(a, 2*i+1); |
return ""; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueEncode(list data) { |
list k = llList2ListStrided(data, 0, -1, 2); |
list v = llList2ListStrided(llDeleteSubList(data, 0, 0), 0, -1, 2); |
data = []; |
do { |
data += llList2String(k, 0) + "=" + llList2String(v, 0); |
k = llDeleteSubList(k, 0, 0); |
v = llDeleteSubList(v, 0, 0); |
} while(llGetListLength(k) != 0); |
return llDumpList2String(data, "&"); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// escapes a string in conformance with RFC1738 |
string wasURLEscape(string i) { |
string o = ""; |
do { |
string c = llGetSubString(i, 0, 0); |
i = llDeleteSubString(i, 0, 0); |
if(c == "") jump continue; |
if(c == " ") { |
o += "+"; |
jump continue; |
} |
if(c == "\n") { |
o += "%0D" + llEscapeURL(c); |
jump continue; |
} |
o += llEscapeURL(c); |
@continue; |
} while(i != ""); |
return o; |
} |
|
// for notecard reading |
integer line = 0; |
|
// key-value data will be read into this list |
list tuples = []; |
// Corrade's online status. |
integer online = FALSE; |
integer open = FALSE; |
string URL = ""; |
|
default { |
state_entry() { |
if(llGetInventoryType("configuration") != INVENTORY_NOTECARD) { |
llOwnerSay("Sorry, could not find a configuration inventory notecard."); |
return; |
} |
// DEBUG |
llOwnerSay("Reading configuration file..."); |
llGetNotecardLine("configuration", line); |
} |
dataserver(key id, string data) { |
if(data == EOF) { |
// invariant, length(tuples) % 2 == 0 |
if(llGetListLength(tuples) % 2 != 0) { |
llOwnerSay("Error in configuration notecard."); |
return; |
} |
key CORRADE = llList2Key( |
tuples, |
llListFindList( |
tuples, |
[ |
"corrade" |
] |
) |
+1); |
if(CORRADE == NULL_KEY) { |
llOwnerSay("Error in configuration notecard: corrade"); |
return; |
} |
string GROUP = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"group" |
] |
) |
+1); |
if(GROUP == "") { |
llOwnerSay("Error in configuration notecard: group"); |
return; |
} |
string PASSWORD = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"password" |
] |
) |
+1); |
if(PASSWORD == "") { |
llOwnerSay("Error in configuration notecard: password"); |
return; |
} |
string VERSION = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"version" |
] |
) |
+1); |
if(VERSION == "") { |
llOwnerSay("Error in configuration notecard: version"); |
return; |
} |
// DEBUG |
llOwnerSay("Read configuration notecard..."); |
state configuration; |
} |
if(data == "") jump continue; |
integer i = llSubStringIndex(data, "#"); |
if(i != -1) data = llDeleteSubString(data, i, -1); |
list o = llParseString2List(data, ["="], []); |
// get rid of starting and ending quotes |
string k = llDumpList2String( |
llParseString2List( |
llStringTrim( |
llList2String( |
o, |
0 |
), |
STRING_TRIM), |
["\""], [] |
), "\""); |
string v = llDumpList2String( |
llParseString2List( |
llStringTrim( |
llList2String( |
o, |
1 |
), |
STRING_TRIM), |
["\""], [] |
), "\""); |
if(k == "" || v == "") jump continue; |
tuples += k; |
tuples += v; |
@continue; |
llGetNotecardLine("configuration", ++line); |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state configuration { |
state_entry() { |
// DEBUG |
llOwnerSay("Configuration ready..."); |
llMessageLinked(LINK_SET, 0, "retract", NULL_KEY); |
llSetTimerEvent(1); |
} |
timer() { |
llRequestAgentData( |
(key)wasKeyValueGet( |
"corrade", |
wasKeyValueEncode(tuples) |
), |
DATA_ONLINE |
); |
llSetTimerEvent(5); |
} |
dataserver(key id, string data) { |
if(data != "1") { |
if(online == TRUE) { |
// DEBUG |
llOwnerSay("Corrade is not online, shutting down..."); |
llMessageLinked(LINK_SET, 0, "retract", NULL_KEY); |
online = FALSE; |
} |
return; |
} |
online = TRUE; |
} |
touch_start(integer total_number) { |
if(!open && online) { |
// DEBUG |
llOwnerSay("Getting URL..."); |
llRequestURL(); |
return; |
} |
llMessageLinked(LINK_SET, 0, "retract", NULL_KEY); |
open = FALSE; |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
llReleaseURL(URL); |
string configuration = wasKeyValueEncode(tuples); |
if(method == URL_REQUEST_GRANTED) { |
llOwnerSay("Checking version..."); |
|
URL = body; |
llInstantMessage( |
wasKeyValueGet( |
"corrade", |
configuration |
), |
wasKeyValueEncode( |
[ |
"command", "version", |
"group", wasURLEscape( |
wasKeyValueGet( |
"group", |
configuration |
) |
), |
"password", wasURLEscape( |
wasKeyValueGet( |
"password", |
configuration |
) |
), |
"callback", wasURLEscape(URL) |
] |
) |
); |
return; |
} |
if(wasKeyValueGet("command", body) != "version" || |
wasKeyValueGet("success", body) != "True") { |
llOwnerSay("Version check failed..."); |
return; |
} |
list v = llParseString2List( |
wasKeyValueGet( |
"data", |
body |
), |
["."], |
[] |
); |
integer receivedVersion = (integer)(llList2String(v, 0) + llList2String(v, 1)); |
v = llParseString2List( |
wasKeyValueGet( |
"version", |
configuration |
), |
["."], |
[] |
); |
integer notecardVersion = (integer)(llList2String(v, 0) + llList2String(v, 1)); |
if(receivedVersion < notecardVersion) { |
llOwnerSay("HUD version is incompatible! You need a Corrade of at least version: " + |
wasKeyValueGet( |
"version", |
configuration |
) + |
" for this HUD." |
); |
llMessageLinked(LINK_SET, 0, "retract", NULL_KEY); |
open = FALSE; |
return; |
} |
llOwnerSay("Version is compatible. Deploying HUD..."); |
llMessageLinked(LINK_SET, 0, "deploy", NULL_KEY); |
open = TRUE; |
return; |
} |
link_message(integer sender, integer num, string message, key id) { |
if(message != "configuration") return; |
llMessageLinked(sender, 0, wasKeyValueEncode(tuples), "configuration"); |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
/heads-up-display/message-relay/message-relay.lsl |
@@ -0,0 +1,488 @@ |
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) Wizardry and Steamworks 2016 - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// |
// This script is a message relay for Corrade that listens for local, group |
// and instant messages. The script binds to Corrade's local, message and |
// group notification and then any message received by Corrade gets sent |
// to you. |
// |
// For more information on Corrade, please see: |
// http://grimore.org/secondlife/scripted_agents/corrade |
// |
/////////////////////////////////////////////////////////////////////////// |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueGet(string k, string data) { |
if(llStringLength(data) == 0) return ""; |
if(llStringLength(k) == 0) return ""; |
list a = llParseString2List(data, ["&", "="], []); |
integer i = llListFindList(llList2ListStrided(a, 0, -1, 2), [ k ]); |
if(i != -1) return llList2String(a, 2*i+1); |
return ""; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueEncode(list data) { |
list k = llList2ListStrided(data, 0, -1, 2); |
list v = llList2ListStrided(llDeleteSubList(data, 0, 0), 0, -1, 2); |
data = []; |
do { |
data += llList2String(k, 0) + "=" + llList2String(v, 0); |
k = llDeleteSubList(k, 0, 0); |
v = llDeleteSubList(v, 0, 0); |
} while(llGetListLength(k) != 0); |
return llDumpList2String(data, "&"); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// escapes a string in conformance with RFC1738 |
string wasURLEscape(string i) { |
string o = ""; |
do { |
string c = llGetSubString(i, 0, 0); |
i = llDeleteSubString(i, 0, 0); |
if(c == "") jump continue; |
if(c == " ") { |
o += "+"; |
jump continue; |
} |
if(c == "\n") { |
o += "%0D" + llEscapeURL(c); |
jump continue; |
} |
o += llEscapeURL(c); |
@continue; |
} while(i != ""); |
return o; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
list wasCSVToList(string csv) { |
list l = []; |
list s = []; |
string m = ""; |
do { |
string a = llGetSubString(csv, 0, 0); |
csv = llDeleteSubString(csv, 0, 0); |
if(a == ",") { |
if(llList2String(s, -1) != "\"") { |
l += m; |
m = ""; |
jump continue; |
} |
m += a; |
jump continue; |
} |
if(a == "\"" && llGetSubString(csv, 0, 0) == a) { |
m += a; |
csv = llDeleteSubString(csv, 0, 0); |
jump continue; |
} |
if(a == "\"") { |
if(llList2String(s, -1) != a) { |
s += a; |
jump continue; |
} |
s = llDeleteSubList(s, -1, -1); |
jump continue; |
} |
m += a; |
@continue; |
} while(csv != ""); |
// postcondition: length(s) = 0 |
return l + m; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasListToCSV(list l) { |
list v = []; |
do { |
string a = llDumpList2String( |
llParseStringKeepNulls( |
llList2String( |
l, |
0 |
), |
["\""], |
[] |
), |
"\"\"" |
); |
if(llParseStringKeepNulls( |
a, |
[" ", ",", "\n", "\""], [] |
) != |
(list) a |
) a = "\"" + a + "\""; |
v += a; |
l = llDeleteSubList(l, 0, 0); |
} while(l != []); |
return llDumpList2String(v, ","); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// unescapes a string in conformance with RFC1738 |
string wasURLUnescape(string i) { |
return llUnescapeURL( |
llDumpList2String( |
llParseString2List( |
llDumpList2String( |
llParseString2List( |
i, |
["+"], |
[] |
), |
" " |
), |
["%0D%0A"], |
[] |
), |
"\n" |
) |
); |
} |
|
// callback URL |
string callback = ""; |
// configuration data |
string configuration = ""; |
|
default { |
state_entry() { |
llSetTimerEvent(1); |
} |
link_message(integer sender, integer num, string message, key id) { |
if(sender != 1 || id != "configuration") return; |
configuration = message; |
state off; |
} |
timer() { |
llMessageLinked(LINK_ROOT, 0, "configuration", NULL_KEY); |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
|
state off { |
state_entry() { |
llReleaseControls(); |
llSetColor(<.5,0,0>, ALL_SIDES); |
} |
touch_end(integer num) { |
state on; |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
} |
|
state on { |
state_entry() { |
llSetColor(<0,.5,0>, ALL_SIDES); |
state url; |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
} |
|
state url { |
state_entry() { |
// DEBUG |
llOwnerSay("Requesting URL..."); |
llRequestURL(); |
} |
touch_end(integer num) { |
llSetColor(<.5,0,0>, ALL_SIDES); |
llResetScript(); |
} |
http_request(key id, string method, string body) { |
if(method != URL_REQUEST_GRANTED) return; |
callback = body; |
// DEBUG |
llOwnerSay("Got URL..."); |
state message; |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
} |
|
state message { |
state_entry() { |
// DEBUG |
llOwnerSay("Binding to the local, instant and group message notification..."); |
llInstantMessage( |
(key)wasKeyValueGet( |
"corrade", |
configuration |
), |
wasKeyValueEncode( |
[ |
"command", "notify", |
"group", wasURLEscape( |
wasKeyValueGet( |
"group", |
configuration |
) |
), |
"password", wasURLEscape( |
wasKeyValueGet( |
"password", |
configuration |
) |
), |
"action", "add", |
"type", wasListToCSV( |
[ |
"local", |
"message", |
"group" |
] |
), |
"URL", wasURLEscape(callback), |
"callback", wasURLEscape(callback) |
] |
) |
); |
llSetTimerEvent(60); |
} |
touch_end(integer num) { |
llSetColor(<.5,0,0>, ALL_SIDES); |
llResetScript(); |
} |
timer() { |
// DEBUG |
llOwnerSay("Timeout binding to the local, instant and group message notification..."); |
llResetScript(); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) != "notify" || |
wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Failed to bind to the local, instant and group message notification..." + wasKeyValueGet("error", body)); |
llResetScript(); |
} |
// DEBUG |
llOwnerSay("Local, instant and group message notification installed..."); |
state main; |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
|
state main { |
state_entry() { |
// DEBUG |
llOwnerSay("Waiting for local, instant or group messages..."); |
llSetTimerEvent(1); |
} |
touch_end(integer num) { |
state uninstall; |
} |
timer() { |
llRequestAgentData( |
(key)wasKeyValueGet( |
"corrade", |
configuration |
), DATA_ONLINE); |
} |
dataserver(key id, string data) { |
if(data != "1") llResetScript(); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
|
// get the message |
string message = wasURLUnescape( |
wasKeyValueGet( |
"message", |
body |
) |
); |
|
// bail if message not audible |
if(message == "") return; |
|
// get the message type |
string type = wasURLUnescape( |
wasKeyValueGet( |
"type", |
body |
) |
); |
|
// get the first name |
string firstname = wasURLUnescape( |
wasKeyValueGet( |
"firstname", |
body |
) |
); |
|
// get the last name |
string lastname = wasURLUnescape( |
wasKeyValueGet( |
"lastname", |
body |
) |
); |
|
// get the group |
string group = wasURLUnescape( |
wasKeyValueGet( |
"group", |
body |
) |
); |
|
if(group != "") { |
llOwnerSay(firstname + " " + lastname + " [" + type + "] (" + group + ") : " + message); |
return; |
} |
llOwnerSay(firstname + " " + lastname + " [" + type + "]: " + message); |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
|
state uninstall { |
state_entry() { |
// DEBUG |
llOwnerSay("Uninstalling local, group and instant message chat notification..."); |
llInstantMessage( |
(key)wasKeyValueGet( |
"corrade", |
configuration |
), |
wasKeyValueEncode( |
[ |
"command", "notify", |
"group", wasURLEscape( |
wasKeyValueGet( |
"group", |
configuration |
) |
), |
"password", wasURLEscape( |
wasKeyValueGet( |
"password", |
configuration |
) |
), |
"action", "remove", |
"type", wasListToCSV([ |
"group", |
"local", |
"message" |
]), |
"URL", wasURLEscape(callback), |
"callback", wasURLEscape(callback) |
] |
) |
); |
llSetTimerEvent(60); |
} |
timer() { |
// DEBUG |
llOwnerSay("Timeout uninstalling the local and instant message chat notification..."); |
llResetScript(); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) != "notify" || |
wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Failed to uninstall the local chat and instant message notification..." + wasKeyValueGet("error", body)); |
llResetScript(); |
} |
// DEBUG |
llOwnerSay("Local chat and instant message notifications uninstalled..."); |
llResetScript(); |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
} |
/heads-up-display/patrol/patrol.lsl |
@@ -0,0 +1,442 @@ |
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// |
// This is an automatic teleporter, and patrol script for the Corrade |
// Second Life / OpenSim bot. You can find more details about the bot |
// by following the URL: http://was.fm/secondlife/scripted_agents/corrade |
// |
// The purpose of this script is to demonstrate patroling with Corrade and |
// you are free to use, change, and commercialize it under the GNU/GPLv3 |
// license which can be found at: http://www.gnu.org/licenses/gpl.html |
// |
/////////////////////////////////////////////////////////////////////////// |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueGet(string k, string data) { |
if(llStringLength(data) == 0) return ""; |
if(llStringLength(k) == 0) return ""; |
list a = llParseString2List(data, ["&", "="], []); |
integer i = llListFindList(a, [ k ]); |
if(i != -1) return llList2String(a, i+1); |
return ""; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueEncode(list data) { |
list k = llList2ListStrided(data, 0, -1, 2); |
list v = llList2ListStrided(llDeleteSubList(data, 0, 0), 0, -1, 2); |
data = []; |
do { |
data += llList2String(k, 0) + "=" + llList2String(v, 0); |
k = llDeleteSubList(k, 0, 0); |
v = llDeleteSubList(v, 0, 0); |
} while(llGetListLength(k) != 0); |
return llDumpList2String(data, "&"); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2011 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// http://was.fm/secondlife/wanderer |
vector wasCirclePoint(float radius) { |
float x = llPow(-1, 1 + (integer) llFrand(2)) * llFrand(radius*2); |
float y = llPow(-1, 1 + (integer) llFrand(2)) * llFrand(radius*2); |
if(llPow(x,2) + llPow(y,2) <= llPow(radius,2)) |
return <x, y, 0>; |
return wasCirclePoint(radius); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// escapes a string in conformance with RFC1738 |
string wasURLEscape(string i) { |
string o = ""; |
do { |
string c = llGetSubString(i, 0, 0); |
i = llDeleteSubString(i, 0, 0); |
if(c == "") jump continue; |
if(c == " ") { |
o += "+"; |
jump continue; |
} |
if(c == "\n") { |
o += "%0D" + llEscapeURL(c); |
jump continue; |
} |
o += llEscapeURL(c); |
@continue; |
} while(i != ""); |
return o; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
list wasCSVToList(string csv) { |
list l = []; |
list s = []; |
string m = ""; |
do { |
string a = llGetSubString(csv, 0, 0); |
csv = llDeleteSubString(csv, 0, 0); |
if(a == ",") { |
if(llList2String(s, -1) != "\"") { |
l += m; |
m = ""; |
jump continue; |
} |
m += a; |
jump continue; |
} |
if(a == "\"" && llGetSubString(csv, 0, 0) == a) { |
m += a; |
csv = llDeleteSubString(csv, 0, 0); |
jump continue; |
} |
if(a == "\"") { |
if(llList2String(s, -1) != a) { |
s += a; |
jump continue; |
} |
s = llDeleteSubList(s, -1, -1); |
jump continue; |
} |
m += a; |
@continue; |
} while(csv != ""); |
// postcondition: length(s) = 0 |
return l + m; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// unescapes a string in conformance with RFC1738 |
string wasURLUnescape(string i) { |
return llUnescapeURL( |
llDumpList2String( |
llParseString2List( |
llDumpList2String( |
llParseString2List( |
i, |
["+"], |
[] |
), |
" " |
), |
["%0D%0A"], |
[] |
), |
"\n" |
) |
); |
} |
|
// configuration data |
string configuration = ""; |
// callback URL |
string callback = ""; |
// local Corrade position |
vector position = ZERO_VECTOR; |
|
default { |
state_entry() { |
llSetTimerEvent(1); |
} |
link_message(integer sender, integer num, string message, key id) { |
if(sender != 1 || id != "configuration") return; |
configuration = message; |
state off; |
} |
timer() { |
llMessageLinked(LINK_ROOT, 0, "configuration", NULL_KEY); |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
|
state off { |
state_entry() { |
llSetColor(<.5,0,0>, ALL_SIDES); |
} |
touch_end(integer num) { |
state on; |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
} |
|
state on { |
state_entry() { |
llSetColor(<0,.5,0>, ALL_SIDES); |
state url; |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
} |
|
state url { |
state_entry() { |
// DEBUG |
llOwnerSay("Requesting URL..."); |
llRequestURL(); |
} |
touch_end(integer num) { |
llSetColor(<.5,0,0>, ALL_SIDES); |
llResetScript(); |
} |
http_request(key id, string method, string body) { |
if(method != URL_REQUEST_GRANTED) return; |
callback = body; |
// DEBUG |
llOwnerSay("Got URL..."); |
state find; |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
} |
|
state find { |
state_entry() { |
// DEBUG |
llOwnerSay("Getting local position..."); |
llInstantMessage( |
wasKeyValueGet( |
"corrade", |
configuration |
), |
wasKeyValueEncode( |
[ |
"command", "getselfdata", |
"group", wasURLEscape( |
wasKeyValueGet( |
"group", |
configuration |
) |
), |
"password", wasURLEscape( |
wasKeyValueGet( |
"password", |
configuration |
) |
), |
"data", "SimPosition", |
"callback", wasURLEscape(callback) |
] |
) |
); |
} |
touch_end(integer num) { |
llSetColor(<.5,0,0>, ALL_SIDES); |
llResetScript(); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) != "getselfdata" || |
wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Unable to query self data..."); |
return; |
} |
position = (vector)llList2String( |
wasCSVToList( |
wasKeyValueGet( |
"data", |
wasURLUnescape(body) |
) |
), |
1 |
); |
state wander; |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
|
state wander { |
state_entry() { |
// DEBUG |
llOwnerSay("Wandering ready..."); |
llSetTimerEvent(1 + |
llFrand( |
(float)wasKeyValueGet( |
"corrade", |
configuration |
) |
) |
); |
} |
touch_end(integer num) { |
llSetColor(<.5,0,0>, ALL_SIDES); |
llResetScript(); |
} |
timer() { |
llRequestAgentData( |
(key)wasKeyValueGet( |
"corrade", |
configuration |
), |
DATA_ONLINE |
); |
} |
dataserver(key id, string data) { |
if(data != "1") { |
// DEBUG |
llOwnerSay("Corrade is not online, sleeping..."); |
llResetScript(); |
return; |
} |
// DEBUG |
//llOwnerSay("Sending stop..."); |
llInstantMessage( |
wasKeyValueGet( |
"corrade", |
configuration |
), |
wasKeyValueEncode( |
[ |
"command", "autopilot", |
"group", wasURLEscape( |
wasKeyValueGet( |
"group", |
configuration |
) |
), |
"password", wasURLEscape( |
wasKeyValueGet( |
"password", |
configuration |
) |
), |
"action", "stop", |
"callback", wasURLEscape(callback) |
] |
) |
); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) != "autopilot" || |
wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Could not get Corrade to stop, restarting script..."); |
llResetScript(); |
} |
// DEBUG |
//llOwnerSay("Sending next move..."); |
llInstantMessage( |
wasKeyValueGet( |
"corrade", |
configuration |
), |
wasKeyValueEncode( |
[ |
"command", "autopilot", |
"group", wasURLEscape( |
wasKeyValueGet( |
"group", |
configuration |
) |
), |
"password", wasURLEscape( |
wasKeyValueGet( |
"password", |
configuration |
) |
), |
"position", wasURLEscape( |
(string)( |
position + wasCirclePoint( |
(float)wasURLEscape( |
wasKeyValueGet( |
"radius", |
configuration |
) |
) |
) |
) |
), |
"action", "start" |
] |
) |
); |
llSetTimerEvent(1 + |
llFrand( |
(float)wasURLEscape( |
wasKeyValueGet( |
"wait", |
configuration |
) |
) |
) |
); |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
} |
/heads-up-display/permission-passthrough/permission-passthrough.lsl |
@@ -0,0 +1,530 @@ |
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) Wizardry and Steamworks 2016 - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// |
// This script is a passthrough or proxy script for script permissions that |
// are received by Corrade. The script binds to Corrade's permission |
// notification and then any permission request received by Corrade gets |
// sent to you. When you either accept or decline, Corrade will either |
// grant or decline the permission request. |
// |
// For more information on Corrade, please see: |
// http://grimore.org/secondlife/scripted_agents/corrade |
// |
/////////////////////////////////////////////////////////////////////////// |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueGet(string k, string data) { |
if(llStringLength(data) == 0) return ""; |
if(llStringLength(k) == 0) return ""; |
list a = llParseString2List(data, ["&", "="], []); |
integer i = llListFindList(llList2ListStrided(a, 0, -1, 2), [ k ]); |
if(i != -1) return llList2String(a, 2*i+1); |
return ""; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueEncode(list data) { |
list k = llList2ListStrided(data, 0, -1, 2); |
list v = llList2ListStrided(llDeleteSubList(data, 0, 0), 0, -1, 2); |
data = []; |
do { |
data += llList2String(k, 0) + "=" + llList2String(v, 0); |
k = llDeleteSubList(k, 0, 0); |
v = llDeleteSubList(v, 0, 0); |
} while(llGetListLength(k) != 0); |
return llDumpList2String(data, "&"); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// escapes a string in conformance with RFC1738 |
string wasURLEscape(string i) { |
string o = ""; |
do { |
string c = llGetSubString(i, 0, 0); |
i = llDeleteSubString(i, 0, 0); |
if(c == "") jump continue; |
if(c == " ") { |
o += "+"; |
jump continue; |
} |
if(c == "\n") { |
o += "%0D" + llEscapeURL(c); |
jump continue; |
} |
o += llEscapeURL(c); |
@continue; |
} while(i != ""); |
return o; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
list wasCSVToList(string csv) { |
list l = []; |
list s = []; |
string m = ""; |
do { |
string a = llGetSubString(csv, 0, 0); |
csv = llDeleteSubString(csv, 0, 0); |
if(a == ",") { |
if(llList2String(s, -1) != "\"") { |
l += m; |
m = ""; |
jump continue; |
} |
m += a; |
jump continue; |
} |
if(a == "\"" && llGetSubString(csv, 0, 0) == a) { |
m += a; |
csv = llDeleteSubString(csv, 0, 0); |
jump continue; |
} |
if(a == "\"") { |
if(llList2String(s, -1) != a) { |
s += a; |
jump continue; |
} |
s = llDeleteSubList(s, -1, -1); |
jump continue; |
} |
m += a; |
@continue; |
} while(csv != ""); |
// postcondition: length(s) = 0 |
return l + m; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasListToCSV(list l) { |
list v = []; |
do { |
string a = llDumpList2String( |
llParseStringKeepNulls( |
llList2String( |
l, |
0 |
), |
["\""], |
[] |
), |
"\"\"" |
); |
if(llParseStringKeepNulls( |
a, |
[" ", ",", "\n", "\""], [] |
) != |
(list) a |
) a = "\"" + a + "\""; |
v += a; |
l = llDeleteSubList(l, 0, 0); |
} while(l != []); |
return llDumpList2String(v, ","); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// unescapes a string in conformance with RFC1738 |
string wasURLUnescape(string i) { |
return llUnescapeURL( |
llDumpList2String( |
llParseString2List( |
llDumpList2String( |
llParseString2List( |
i, |
["+"], |
[] |
), |
" " |
), |
["%0D%0A"], |
[] |
), |
"\n" |
) |
); |
} |
|
// callback URL |
string callback = ""; |
// configuration data |
string configuration = ""; |
// listen handle |
integer listenHandle = 0; |
|
// store dialog details |
string permission_region = ""; |
list permission_requested = []; |
key permission_item = NULL_KEY; |
key permission_task = NULL_KEY; |
|
default { |
state_entry() { |
llSetTimerEvent(1); |
} |
link_message(integer sender, integer num, string message, key id) { |
if(sender != 1 || id != "configuration") return; |
configuration = message; |
state off; |
} |
timer() { |
llMessageLinked(LINK_ROOT, 0, "configuration", NULL_KEY); |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
|
state off { |
state_entry() { |
llReleaseControls(); |
llSetColor(<.5,0,0>, ALL_SIDES); |
} |
touch_end(integer num) { |
state on; |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
} |
|
state on { |
state_entry() { |
llSetColor(<0,.5,0>, ALL_SIDES); |
state url; |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
} |
|
state url { |
state_entry() { |
// DEBUG |
llOwnerSay("Requesting URL..."); |
llRequestURL(); |
} |
touch_end(integer num) { |
llSetColor(<.5,0,0>, ALL_SIDES); |
llResetScript(); |
} |
http_request(key id, string method, string body) { |
if(method != URL_REQUEST_GRANTED) return; |
callback = body; |
// DEBUG |
llOwnerSay("Got URL..."); |
state permission; |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
} |
|
state permission { |
state_entry() { |
// DEBUG |
llOwnerSay("Binding to the script permission notification..."); |
llInstantMessage( |
(key)wasKeyValueGet( |
"corrade", |
configuration |
), |
wasKeyValueEncode( |
[ |
"command", "notify", |
"group", wasURLEscape( |
wasKeyValueGet( |
"group", |
configuration |
) |
), |
"password", wasURLEscape( |
wasKeyValueGet( |
"password", |
configuration |
) |
), |
"action", "add", |
"type", "permission", |
"URL", wasURLEscape(callback), |
"callback", wasURLEscape(callback) |
] |
) |
); |
llSetTimerEvent(60); |
} |
touch_end(integer num) { |
llSetColor(<.5,0,0>, ALL_SIDES); |
llResetScript(); |
} |
timer() { |
// DEBUG |
llOwnerSay("Timeout binding to the script permission notification..."); |
llResetScript(); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) != "notify" || |
wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Failed to bind to the script permission notification..." + wasKeyValueGet("error", body)); |
llResetScript(); |
} |
// DEBUG |
llOwnerSay("Script permission notification installed..."); |
state main; |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
|
state main { |
state_entry() { |
// DEBUG |
llOwnerSay("Waiting for script permission..."); |
llSetTimerEvent(1); |
} |
touch_end(integer num) { |
state uninstall; |
} |
timer() { |
llRequestAgentData( |
(key)wasKeyValueGet( |
"corrade", |
configuration |
), DATA_ONLINE); |
} |
dataserver(key id, string data) { |
if(data != "1") llResetScript(); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
|
// get the script permission region |
permission_region = wasURLUnescape( |
wasKeyValueGet( |
"region", |
body |
) |
); |
|
// get the script permission item |
permission_item = (key)wasURLUnescape( |
wasKeyValueGet( |
"item", |
body |
) |
); |
|
// get the script permission task |
permission_task = (key)wasURLUnescape( |
wasKeyValueGet( |
"task", |
body |
) |
); |
|
// get the buttons that the dialog has |
permission_requested = wasCSVToList( |
wasURLUnescape( |
wasKeyValueGet( |
"permissions", |
body |
) |
) |
); |
|
// get the name of the object owner. |
string name = wasURLUnescape( |
wasKeyValueGet( |
"firstname", |
body |
) |
) + " " + wasURLUnescape( |
wasKeyValueGet( |
"lastname", |
body |
) |
); |
|
// DEBUG |
llOwnerSay("Requested permissions: " + llDumpList2String(permission_requested, "|")); |
listenHandle = llListen(-14, "", llGetOwner(), ""); |
llDialog(llGetOwner(), |
"[CORRADE]: Script permissions requested: " + wasListToCSV(permission_requested) + " from an object owned by " + name, |
[ |
"Accept", |
"Decline" |
], |
-14 |
); |
} |
listen(integer channel, string name, key id, string message) { |
llListenRemove(listenHandle); |
if(message == "Decline") |
permission_requested = []; |
llInstantMessage( |
wasKeyValueGet( |
"corrade", |
configuration |
), |
wasKeyValueEncode( |
[ |
"command", "replytoscriptpermissionrequest", |
"group", wasURLEscape( |
wasKeyValueGet( |
"group", |
configuration |
) |
), |
"password", wasURLEscape( |
wasKeyValueGet( |
"password", |
configuration |
) |
), |
"action", "reply", |
"task", permission_task, |
"item", permission_item, |
"permissions", wasListToCSV(permission_requested), |
"region", wasURLEscape(permission_region) |
] |
) |
); |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
|
state uninstall { |
state_entry() { |
// DEBUG |
llOwnerSay("Uninstalling script permission notification..."); |
llInstantMessage( |
(key)wasKeyValueGet( |
"corrade", |
configuration |
), |
wasKeyValueEncode( |
[ |
"command", "notify", |
"group", wasURLEscape( |
wasKeyValueGet( |
"group", |
configuration |
) |
), |
"password", wasURLEscape( |
wasKeyValueGet( |
"password", |
configuration |
) |
), |
"action", "remove", |
"type", "permission", |
"URL", wasURLEscape(callback), |
"callback", wasURLEscape(callback) |
] |
) |
); |
llSetTimerEvent(60); |
} |
timer() { |
// DEBUG |
llOwnerSay("Timeout uninstalling the script permission notification..."); |
llResetScript(); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) != "notify" || |
wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Failed to uninstall the script permission notification..." + wasKeyValueGet("error", body)); |
llResetScript(); |
} |
// DEBUG |
llOwnerSay("Script permission notification uninstalled..."); |
llResetScript(); |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
} |
/heads-up-display/point-at/point-at.lsl |
@@ -0,0 +1,618 @@ |
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) Wizardry and Steamworks 2016 - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// |
// This script will make Corrade scan for primitives in the vicinity and |
// will then offer a dialog for you to pick to which primitive Corrade will |
// point to. Note that the point effect can only be observed in case the |
// outfit that Corrade is currently wearing has a proper mapping for arms. |
// |
// For more information on Corrade, please see: |
// http://grimore.org/secondlife/scripted_agents/corrade |
// |
/////////////////////////////////////////////////////////////////////////// |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueGet(string k, string data) { |
if(llStringLength(data) == 0) return ""; |
if(llStringLength(k) == 0) return ""; |
list a = llParseString2List(data, ["&", "="], []); |
integer i = llListFindList(a, [ k ]); |
if(i != -1) return llList2String(a, i+1); |
return ""; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueEncode(list data) { |
list k = llList2ListStrided(data, 0, -1, 2); |
list v = llList2ListStrided(llDeleteSubList(data, 0, 0), 0, -1, 2); |
data = []; |
do { |
data += llList2String(k, 0) + "=" + llList2String(v, 0); |
k = llDeleteSubList(k, 0, 0); |
v = llDeleteSubList(v, 0, 0); |
} while(llGetListLength(k) != 0); |
return llDumpList2String(data, "&"); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2011 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// http://was.fm/secondlife/wanderer |
vector wasCirclePoint(float radius) { |
float x = llPow(-1, 1 + (integer) llFrand(2)) * llFrand(radius*2); |
float y = llPow(-1, 1 + (integer) llFrand(2)) * llFrand(radius*2); |
if(llPow(x,2) + llPow(y,2) <= llPow(radius,2)) |
return <x, y, 0>; |
return wasCirclePoint(radius); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// escapes a string in conformance with RFC1738 |
string wasURLEscape(string i) { |
string o = ""; |
do { |
string c = llGetSubString(i, 0, 0); |
i = llDeleteSubString(i, 0, 0); |
if(c == "") jump continue; |
if(c == " ") { |
o += "+"; |
jump continue; |
} |
if(c == "\n") { |
o += "%0D" + llEscapeURL(c); |
jump continue; |
} |
o += llEscapeURL(c); |
@continue; |
} while(i != ""); |
return o; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
list wasCSVToList(string csv) { |
list l = []; |
list s = []; |
string m = ""; |
do { |
string a = llGetSubString(csv, 0, 0); |
csv = llDeleteSubString(csv, 0, 0); |
if(a == ",") { |
if(llList2String(s, -1) != "\"") { |
l += m; |
m = ""; |
jump continue; |
} |
m += a; |
jump continue; |
} |
if(a == "\"" && llGetSubString(csv, 0, 0) == a) { |
m += a; |
csv = llDeleteSubString(csv, 0, 0); |
jump continue; |
} |
if(a == "\"") { |
if(llList2String(s, -1) != a) { |
s += a; |
jump continue; |
} |
s = llDeleteSubList(s, -1, -1); |
jump continue; |
} |
m += a; |
@continue; |
} while(csv != ""); |
// postcondition: length(s) = 0 |
return l + m; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasListToCSV(list l) { |
list v = []; |
do { |
string a = llDumpList2String( |
llParseStringKeepNulls( |
llList2String( |
l, |
0 |
), |
["\""], |
[] |
), |
"\"\"" |
); |
if(llParseStringKeepNulls( |
a, |
[" ", ",", "\n", "\""], [] |
) != |
(list) a |
) a = "\"" + a + "\""; |
v += a; |
l = llDeleteSubList(l, 0, 0); |
} while(l != []); |
return llDumpList2String(v, ","); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// unescapes a string in conformance with RFC1738 |
string wasURLUnescape(string i) { |
return llUnescapeURL( |
llDumpList2String( |
llParseString2List( |
llDumpList2String( |
llParseString2List( |
i, |
["+"], |
[] |
), |
" " |
), |
["%0D%0A"], |
[] |
), |
"\n" |
) |
); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
integer wasMenuIndex = 0; |
list wasDialogMenu(list input, list actions, string direction) { |
integer cut = 11-wasListCountExclude(actions, [""]); |
if(direction == ">" && (wasMenuIndex+1)*cut+wasMenuIndex+1 < llGetListLength(input)) { |
++wasMenuIndex; |
jump slice; |
} |
if(direction == "<" && wasMenuIndex-1 >= 0) { |
--wasMenuIndex; |
jump slice; |
} |
@slice; |
integer multiple = wasMenuIndex*cut; |
input = llList2List(input, multiple+wasMenuIndex, multiple+cut+wasMenuIndex); |
input = wasListMerge(input, actions, ""); |
return input; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
integer wasListCountExclude(list input, list exclude) { |
if(llGetListLength(input) == 0) return 0; |
if(llListFindList(exclude, (list)llList2String(input, 0)) == -1) |
return 1 + wasListCountExclude(llDeleteSubList(input, 0, 0), exclude); |
return wasListCountExclude(llDeleteSubList(input, 0, 0), exclude); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
list wasListMerge(list l, list m, string merge) { |
if(llGetListLength(l) == 0 && llGetListLength(m) == 0) return []; |
string a = llList2String(m, 0); |
if(a != merge) return [ a ] + wasListMerge(l, llDeleteSubList(m, 0, 0), merge); |
return [ llList2String(l, 0) ] + wasListMerge(llDeleteSubList(l, 0, 0), llDeleteSubList(m, 0, 0), merge); |
} |
|
// configuration data |
string configuration = ""; |
// callback URL |
string callback = ""; |
// scanned primitives |
list names = []; |
list UUIDs = []; |
// temporary list for button name normalization |
list menu = []; |
integer select = -1; |
|
default { |
state_entry() { |
llSetTimerEvent(1); |
} |
link_message(integer sender, integer num, string message, key id) { |
if(sender != 1 || id != "configuration") return; |
configuration = message; |
state off; |
} |
timer() { |
llMessageLinked(LINK_ROOT, 0, "configuration", NULL_KEY); |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
|
state off { |
state_entry() { |
llSetColor(<.5,0,0>, ALL_SIDES); |
} |
touch_end(integer num) { |
state on; |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
} |
|
state on { |
state_entry() { |
llSetColor(<0,.5,0>, ALL_SIDES); |
state url; |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
} |
|
state url { |
state_entry() { |
// DEBUG |
llOwnerSay("Requesting URL..."); |
llRequestURL(); |
} |
touch_end(integer num) { |
llSetColor(<.5,0,0>, ALL_SIDES); |
llResetScript(); |
} |
http_request(key id, string method, string body) { |
if(method != URL_REQUEST_GRANTED) return; |
callback = body; |
// DEBUG |
llOwnerSay("Got URL..."); |
state scan; |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
} |
|
state scan { |
state_entry() { |
// DEBUG |
llOwnerSay("Getting primitives..."); |
llInstantMessage( |
wasKeyValueGet( |
"corrade", |
configuration |
), |
wasKeyValueEncode( |
[ |
"command", "getprimitivesdata", |
"group", wasURLEscape( |
wasKeyValueGet( |
"group", |
configuration |
) |
), |
"password", wasURLEscape( |
wasKeyValueGet( |
"password", |
configuration |
) |
), |
"entity", "range", |
"range", wasURLEscape( |
wasKeyValueGet( |
"radar", |
configuration |
) |
), |
"data", wasListToCSV( |
[ |
"Properties.Name", |
"ID" |
] |
), |
"sift", wasListToCSV( |
[ |
"take", 32 |
] |
), |
"callback", wasURLEscape(callback) |
] |
) |
); |
} |
touch_end(integer num) { |
llSetColor(<.5,0,0>, ALL_SIDES); |
llResetScript(); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) != "getprimitivesdata" || |
wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Too many primitives or unable to query primitives data..."); |
llResetScript(); |
} |
string dataKey = wasURLUnescape( |
wasKeyValueGet( |
"data", |
body |
) |
); |
if(dataKey == "") { |
// DEBUG |
llOwnerSay("No data for scanned primitives..."); |
llResetScript(); |
} |
list data = wasCSVToList(dataKey); |
// Copy the names and UUIDs of the primitives. |
names = []; |
UUIDs = []; |
do { |
string v = llList2String(data, -1); |
data = llDeleteSubList(data, -1, -1); |
string k = llList2String(data, -1); |
data = llDeleteSubList(data, -1, -1); |
if(k == "Properties.Name") { |
names += v; |
jump continue; |
} |
UUIDs += (key)v; |
@continue; |
} while(llGetListLength(data)); |
state choose; |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
|
state choose { |
state_entry() { |
// DEBUG |
llOwnerSay("Sending menu..."); |
menu = []; |
integer i = 0; |
do { |
menu += llGetSubString(llList2String(names, i), 0, 23); |
} while(++i < llGetListLength(names)); |
llListen(-10, "", llGetOwner(), ""); |
llDialog(llGetOwner(), "\nPlease choose a primitive for Corrade to point at from the list below:\n", wasDialogMenu(menu, ["⟵ Back", "", "Next ⟶"], ""), -10); |
llSetTimerEvent(60); |
} |
touch_end(integer num) { |
llSetColor(<.5,0,0>, ALL_SIDES); |
llResetScript(); |
} |
listen(integer channel, string name, key id, string message) { |
if(message == "⟵ Back") { |
llDialog(id, "\nPlease choose a primitive for Corrade to point at from the list below:\n", wasDialogMenu(menu, ["⟵ Back", "", "Next ⟶"], "<"), -10); |
return; |
} |
if(message == "Next ⟶") { |
llDialog(id, "\nPlease choose a primitive for Corrade to sit on from the list below:\n", wasDialogMenu(menu, ["⟵ Back", "", "Next ⟶"], ">"), -10); |
return; |
} |
integer i = llGetListLength(menu) - 1; |
do { |
string v = llList2String(menu, i); |
if(llSubStringIndex(v, message) != -1) |
jump sit; |
} while(--i > -1); |
// GC |
menu = []; |
// DEBUG |
llOwnerSay("Invalid menu item selected..."); |
llResetScript(); |
@sit; |
// GC |
menu = []; |
select = i; |
// Got a menu item so bind to permission notifications and sit. |
state point_at; |
|
} |
timer() { |
// DEBUG |
llOwnerSay("Dialog menu timeout..."); |
llResetScript(); |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
|
state point_at { |
state_entry() { |
// DEBUG |
llOwnerSay("Pointing at: " + |
llList2String(names, select) + |
" UUID: " + |
llList2String(UUIDs, select) |
); |
llInstantMessage( |
(key)wasKeyValueGet( |
"corrade", |
configuration |
), |
wasKeyValueEncode( |
[ |
"command", "setviewereffect", |
"group", wasURLEscape( |
wasKeyValueGet( |
"group", |
configuration |
) |
), |
"password", wasURLEscape( |
wasKeyValueGet( |
"password", |
configuration |
) |
), |
"effect", "point", |
"offset", ZERO_VECTOR, |
"type", "Select", |
"id", wasURLEscape( |
llGetKey() |
), |
"item", wasURLEscape( |
llList2String( |
UUIDs, |
select |
) |
), |
"range", wasURLEscape( |
wasKeyValueGet( |
"radar", |
configuration |
) |
), |
"callback", wasURLEscape(callback) |
] |
) |
); |
} |
touch_end(integer num) { |
state remove; |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) != "setviewereffect" || |
wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Failed to point at..."); |
llResetScript(); |
} |
// DEBUG |
llOwnerSay("Pointing..."); |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
|
state remove { |
state_entry() { |
// DEBUG |
llOwnerSay("Stopping point at..."); |
llInstantMessage( |
(key)wasKeyValueGet( |
"corrade", |
configuration |
), wasKeyValueEncode( |
[ |
"command", "deleteviewereffect", |
"group", wasURLEscape( |
wasKeyValueGet( |
"group", |
configuration |
) |
), |
"password", wasURLEscape( |
wasKeyValueGet( |
"password", |
configuration |
) |
), |
"effect", "point", |
"id", wasURLEscape( |
llGetKey() |
) |
] |
) |
); |
llResetScript(); |
} |
touch_start(integer num) { |
llResetScript(); |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
/heads-up-display/sit/sit.lsl |
@@ -0,0 +1,729 @@ |
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) Wizardry and Steamworks 2016 - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// |
// This script is used to make Corrade sit on a primitive. The script first |
// uses a Corrade command to scan the surrounding area for primitives and |
// then sends a command to make Corrade sit on that primitive. |
// |
// For more information on Corrade, please see: |
// http://grimore.org/secondlife/scripted_agents/corrade |
// |
/////////////////////////////////////////////////////////////////////////// |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueGet(string k, string data) { |
if(llStringLength(data) == 0) return ""; |
if(llStringLength(k) == 0) return ""; |
list a = llParseString2List(data, ["&", "="], []); |
integer i = llListFindList(a, [ k ]); |
if(i != -1) return llList2String(a, i+1); |
return ""; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueEncode(list data) { |
list k = llList2ListStrided(data, 0, -1, 2); |
list v = llList2ListStrided(llDeleteSubList(data, 0, 0), 0, -1, 2); |
data = []; |
do { |
data += llList2String(k, 0) + "=" + llList2String(v, 0); |
k = llDeleteSubList(k, 0, 0); |
v = llDeleteSubList(v, 0, 0); |
} while(llGetListLength(k) != 0); |
return llDumpList2String(data, "&"); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2011 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// http://was.fm/secondlife/wanderer |
vector wasCirclePoint(float radius) { |
float x = llPow(-1, 1 + (integer) llFrand(2)) * llFrand(radius*2); |
float y = llPow(-1, 1 + (integer) llFrand(2)) * llFrand(radius*2); |
if(llPow(x,2) + llPow(y,2) <= llPow(radius,2)) |
return <x, y, 0>; |
return wasCirclePoint(radius); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// escapes a string in conformance with RFC1738 |
string wasURLEscape(string i) { |
string o = ""; |
do { |
string c = llGetSubString(i, 0, 0); |
i = llDeleteSubString(i, 0, 0); |
if(c == "") jump continue; |
if(c == " ") { |
o += "+"; |
jump continue; |
} |
if(c == "\n") { |
o += "%0D" + llEscapeURL(c); |
jump continue; |
} |
o += llEscapeURL(c); |
@continue; |
} while(i != ""); |
return o; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
list wasCSVToList(string csv) { |
list l = []; |
list s = []; |
string m = ""; |
do { |
string a = llGetSubString(csv, 0, 0); |
csv = llDeleteSubString(csv, 0, 0); |
if(a == ",") { |
if(llList2String(s, -1) != "\"") { |
l += m; |
m = ""; |
jump continue; |
} |
m += a; |
jump continue; |
} |
if(a == "\"" && llGetSubString(csv, 0, 0) == a) { |
m += a; |
csv = llDeleteSubString(csv, 0, 0); |
jump continue; |
} |
if(a == "\"") { |
if(llList2String(s, -1) != a) { |
s += a; |
jump continue; |
} |
s = llDeleteSubList(s, -1, -1); |
jump continue; |
} |
m += a; |
@continue; |
} while(csv != ""); |
// postcondition: length(s) = 0 |
return l + m; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasListToCSV(list l) { |
list v = []; |
do { |
string a = llDumpList2String( |
llParseStringKeepNulls( |
llList2String( |
l, |
0 |
), |
["\""], |
[] |
), |
"\"\"" |
); |
if(llParseStringKeepNulls( |
a, |
[" ", ",", "\n", "\""], [] |
) != |
(list) a |
) a = "\"" + a + "\""; |
v += a; |
l = llDeleteSubList(l, 0, 0); |
} while(l != []); |
return llDumpList2String(v, ","); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// unescapes a string in conformance with RFC1738 |
string wasURLUnescape(string i) { |
return llUnescapeURL( |
llDumpList2String( |
llParseString2List( |
llDumpList2String( |
llParseString2List( |
i, |
["+"], |
[] |
), |
" " |
), |
["%0D%0A"], |
[] |
), |
"\n" |
) |
); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
integer wasMenuIndex = 0; |
list wasDialogMenu(list input, list actions, string direction) { |
integer cut = 11-wasListCountExclude(actions, [""]); |
if(direction == ">" && (wasMenuIndex+1)*cut+wasMenuIndex+1 < llGetListLength(input)) { |
++wasMenuIndex; |
jump slice; |
} |
if(direction == "<" && wasMenuIndex-1 >= 0) { |
--wasMenuIndex; |
jump slice; |
} |
@slice; |
integer multiple = wasMenuIndex*cut; |
input = llList2List(input, multiple+wasMenuIndex, multiple+cut+wasMenuIndex); |
input = wasListMerge(input, actions, ""); |
return input; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
integer wasListCountExclude(list input, list exclude) { |
if(llGetListLength(input) == 0) return 0; |
if(llListFindList(exclude, (list)llList2String(input, 0)) == -1) |
return 1 + wasListCountExclude(llDeleteSubList(input, 0, 0), exclude); |
return wasListCountExclude(llDeleteSubList(input, 0, 0), exclude); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
list wasListMerge(list l, list m, string merge) { |
if(llGetListLength(l) == 0 && llGetListLength(m) == 0) return []; |
string a = llList2String(m, 0); |
if(a != merge) return [ a ] + wasListMerge(l, llDeleteSubList(m, 0, 0), merge); |
return [ llList2String(l, 0) ] + wasListMerge(llDeleteSubList(l, 0, 0), llDeleteSubList(m, 0, 0), merge); |
} |
|
// configuration data |
string configuration = ""; |
// callback URL |
string callback = ""; |
// scanned primitives |
list names = []; |
list UUIDs = []; |
// temporary list for button name normalization |
list menu = []; |
integer select = -1; |
|
default { |
state_entry() { |
llSetTimerEvent(1); |
} |
link_message(integer sender, integer num, string message, key id) { |
if(sender != 1 || id != "configuration") return; |
configuration = message; |
state off; |
} |
timer() { |
llMessageLinked(LINK_ROOT, 0, "configuration", NULL_KEY); |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
|
state off { |
state_entry() { |
llSetColor(<.5,0,0>, ALL_SIDES); |
} |
touch_end(integer num) { |
state on; |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
} |
|
state on { |
state_entry() { |
llSetColor(<0,.5,0>, ALL_SIDES); |
state url; |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
} |
|
state url { |
state_entry() { |
// DEBUG |
llOwnerSay("Requesting URL..."); |
llRequestURL(); |
} |
touch_end(integer num) { |
llSetColor(<.5,0,0>, ALL_SIDES); |
llResetScript(); |
} |
http_request(key id, string method, string body) { |
if(method != URL_REQUEST_GRANTED) return; |
callback = body; |
// DEBUG |
llOwnerSay("Got URL..."); |
state scan; |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
} |
|
state scan { |
state_entry() { |
// DEBUG |
llOwnerSay("Getting objects..."); |
llInstantMessage( |
wasKeyValueGet( |
"corrade", |
configuration |
), |
wasKeyValueEncode( |
[ |
"command", "getobjectsdata", |
"group", wasURLEscape( |
wasKeyValueGet( |
"group", |
configuration |
) |
), |
"password", wasURLEscape( |
wasKeyValueGet( |
"password", |
configuration |
) |
), |
"entity", "world", |
"range", wasURLEscape( |
wasKeyValueGet( |
"radar", |
configuration |
) |
), |
"data", wasListToCSV( |
[ |
"Properties.Name", |
"ID" |
] |
), |
"sift", wasListToCSV( |
[ |
"take", 32 |
] |
), |
"callback", wasURLEscape(callback) |
] |
) |
); |
} |
touch_end(integer num) { |
llSetColor(<.5,0,0>, ALL_SIDES); |
llResetScript(); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) != "getobjectsdata" || |
wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Error querying primitives: " + wasKeyValueGet("error", body)); |
llResetScript(); |
} |
string dataKey = wasURLUnescape( |
wasKeyValueGet( |
"data", |
body |
) |
); |
if(dataKey == "") { |
// DEBUG |
llOwnerSay("No data for scanned primitives..."); |
llResetScript(); |
} |
list data = wasCSVToList(dataKey); |
// Copy the names and UUIDs of the primitives. |
names = []; |
UUIDs = []; |
do { |
string v = llList2String(data, -1); |
data = llDeleteSubList(data, -1, -1); |
string k = llList2String(data, -1); |
data = llDeleteSubList(data, -1, -1); |
if(k == "Properties.Name") { |
names += v; |
jump continue; |
} |
UUIDs += (key)v; |
@continue; |
} while(llGetListLength(data)); |
state choose; |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
|
state choose { |
state_entry() { |
// DEBUG |
llOwnerSay("Sending menu..."); |
menu = []; |
integer i = 0; |
do { |
menu += llGetSubString(llList2String(names, i), 0, 23); |
} while(++i < llGetListLength(names)); |
llListen(-10, "", llGetOwner(), ""); |
llDialog(llGetOwner(), "\nPlease choose a primitive for Corrade to sit on from the list below:\n", wasDialogMenu(menu, ["⟵ Back", "", "Next ⟶"], ""), -10); |
llSetTimerEvent(60); |
} |
touch_end(integer num) { |
llSetColor(<.5,0,0>, ALL_SIDES); |
llResetScript(); |
} |
listen(integer channel, string name, key id, string message) { |
if(message == "⟵ Back") { |
llDialog(id, "\nPlease choose a primitive for Corrade to sit on from the list below:\n", wasDialogMenu(menu, ["⟵ Back", "", "Next ⟶"], "<"), -10); |
return; |
} |
if(message == "Next ⟶") { |
llDialog(id, "\nPlease choose a primitive for Corrade to sit on from the list below:\n", wasDialogMenu(menu, ["⟵ Back", "", "Next ⟶"], ">"), -10); |
return; |
} |
integer i = llGetListLength(menu) - 1; |
do { |
string v = llList2String(menu, i); |
if(llSubStringIndex(v, message) != -1) |
jump sit; |
} while(--i > -1); |
// GC |
menu = []; |
// DEBUG |
llOwnerSay("Invalid menu item selected..."); |
llResetScript(); |
@sit; |
// GC |
menu = []; |
select = i; |
// Got a menu item so bind to permission notifications and sit. |
state notify; |
|
} |
timer() { |
// DEBUG |
llOwnerSay("Dialog menu timeout..."); |
llResetScript(); |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
|
state notify { |
state_entry() { |
// DEBUG |
llOwnerSay("Binding to the permission Corrade notification..."); |
llInstantMessage( |
(key)wasKeyValueGet( |
"corrade", |
configuration |
), |
wasKeyValueEncode( |
[ |
"command", "notify", |
"group", wasURLEscape( |
wasKeyValueGet( |
"group", |
configuration |
) |
), |
"password", wasURLEscape( |
wasKeyValueGet( |
"password", |
configuration |
) |
), |
"action", "add", |
"type", "permission", |
"URL", wasURLEscape(callback), |
"callback", wasURLEscape(callback) |
] |
) |
); |
llSetTimerEvent(60); |
} |
touch_end(integer num) { |
llSetColor(<.5,0,0>, ALL_SIDES); |
llResetScript(); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) != "notify" || |
wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Failed to bind to the permission notification..."); |
llResetScript(); |
} |
// DEBUG |
llOwnerSay("Permission notification installed..."); |
state sit; |
} |
timer() { |
// DEBUG |
llOwnerSay("Timeout binding to permission notification..."); |
llResetScript(); |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
|
state sit { |
state_entry() { |
// DEBUG |
llOwnerSay("Sitting on: " + |
llList2String(names, select) + |
" UUID: " + |
llList2String(UUIDs, select) |
); |
llInstantMessage( |
(key)wasKeyValueGet( |
"corrade", |
configuration |
), |
wasKeyValueEncode( |
[ |
"command", "sit", |
"group", wasURLEscape( |
wasKeyValueGet( |
"group", |
configuration |
) |
), |
"password", wasURLEscape( |
wasKeyValueGet( |
"password", |
configuration |
) |
), |
"item", wasURLEscape( |
llList2String(UUIDs, select) |
), |
"range", wasURLEscape( |
wasKeyValueGet( |
"radar", |
configuration |
) |
), |
"callback", wasURLEscape(callback) |
] |
) |
); |
llSetTimerEvent(60); |
} |
touch_end(integer num) { |
state unbind; |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
llSetTimerEvent(5); |
if(wasKeyValueGet("type", body) != "permission" || |
wasKeyValueGet("permissions", body) != "TriggerAnimation") return; |
llSetTimerEvent(10); |
// DEBUG |
llOwnerSay("Corrade received the permission request to trigger an animation, replying..."); |
llInstantMessage( |
(key)wasKeyValueGet( |
"corrade", |
configuration |
), |
wasKeyValueEncode( |
[ |
"command", "replytoscriptpermissionrequest", |
"group", wasKeyValueGet( |
"group", |
configuration |
), |
"password", wasKeyValueGet( |
"password", |
configuration |
), |
"item", wasKeyValueGet( |
"item", |
body |
), |
"task", wasKeyValueGet( |
"task", |
body |
), |
"region", wasKeyValueGet( |
"region", |
body |
), |
"permissions", "TriggerAnimation", |
"callback", wasURLEscape(callback) |
] |
) |
); |
llResetScript(); |
} |
timer() { |
state unbind; |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
|
state unbind { |
state_entry() { |
// DEBUG |
llOwnerSay("Unbinding from the permission Corrade notification..."); |
llInstantMessage( |
(key)wasKeyValueGet( |
"corrade", |
configuration |
), |
wasKeyValueEncode( |
[ |
"command", "notify", |
"group", wasURLEscape( |
wasKeyValueGet( |
"group", |
configuration |
) |
), |
"password", wasURLEscape( |
wasKeyValueGet( |
"password", |
configuration |
) |
), |
"action", "remove", |
"type", "permission", |
"URL", wasURLEscape(callback), |
"callback", wasURLEscape(callback) |
] |
) |
); |
llSetTimerEvent(60); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) != "notify" || |
wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Failed to unbind from the permission notification..."); |
llResetScript(); |
} |
// DEBUG |
llOwnerSay("Permission notification uninstalled..."); |
llResetScript(); |
} |
timer() { |
// DEBUG |
llOwnerSay("Timeout unbinding from the permission notification..."); |
llResetScript(); |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
/heads-up-display/tell/tell.lsl |
@@ -0,0 +1,638 @@ |
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) Wizardry and Steamworks 2016 - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// |
// This script is able to send messages to avatars, groups or talk in local |
// using Corrade. |
// |
// For more information on Corrade, please see: |
// http://grimore.org/secondlife/scripted_agents/corrade |
// |
/////////////////////////////////////////////////////////////////////////// |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueGet(string k, string data) { |
if(llStringLength(data) == 0) return ""; |
if(llStringLength(k) == 0) return ""; |
list a = llParseString2List(data, ["&", "="], []); |
integer i = llListFindList(llList2ListStrided(a, 0, -1, 2), [ k ]); |
if(i != -1) return llList2String(a, 2*i+1); |
return ""; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueEncode(list data) { |
list k = llList2ListStrided(data, 0, -1, 2); |
list v = llList2ListStrided(llDeleteSubList(data, 0, 0), 0, -1, 2); |
data = []; |
do { |
data += llList2String(k, 0) + "=" + llList2String(v, 0); |
k = llDeleteSubList(k, 0, 0); |
v = llDeleteSubList(v, 0, 0); |
} while(llGetListLength(k) != 0); |
return llDumpList2String(data, "&"); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// escapes a string in conformance with RFC1738 |
string wasURLEscape(string i) { |
string o = ""; |
do { |
string c = llGetSubString(i, 0, 0); |
i = llDeleteSubString(i, 0, 0); |
if(c == "") jump continue; |
if(c == " ") { |
o += "+"; |
jump continue; |
} |
if(c == "\n") { |
o += "%0D" + llEscapeURL(c); |
jump continue; |
} |
o += llEscapeURL(c); |
@continue; |
} while(i != ""); |
return o; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
list wasCSVToList(string csv) { |
list l = []; |
list s = []; |
string m = ""; |
do { |
string a = llGetSubString(csv, 0, 0); |
csv = llDeleteSubString(csv, 0, 0); |
if(a == ",") { |
if(llList2String(s, -1) != "\"") { |
l += m; |
m = ""; |
jump continue; |
} |
m += a; |
jump continue; |
} |
if(a == "\"" && llGetSubString(csv, 0, 0) == a) { |
m += a; |
csv = llDeleteSubString(csv, 0, 0); |
jump continue; |
} |
if(a == "\"") { |
if(llList2String(s, -1) != a) { |
s += a; |
jump continue; |
} |
s = llDeleteSubList(s, -1, -1); |
jump continue; |
} |
m += a; |
@continue; |
} while(csv != ""); |
// postcondition: length(s) = 0 |
return l + m; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasListToCSV(list l) { |
list v = []; |
do { |
string a = llDumpList2String( |
llParseStringKeepNulls( |
llList2String( |
l, |
0 |
), |
["\""], |
[] |
), |
"\"\"" |
); |
if(llParseStringKeepNulls( |
a, |
[" ", ",", "\n", "\""], [] |
) != |
(list) a |
) a = "\"" + a + "\""; |
v += a; |
l = llDeleteSubList(l, 0, 0); |
} while(l != []); |
return llDumpList2String(v, ","); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// unescapes a string in conformance with RFC1738 |
string wasURLUnescape(string i) { |
return llUnescapeURL( |
llDumpList2String( |
llParseString2List( |
llDumpList2String( |
llParseString2List( |
i, |
["+"], |
[] |
), |
" " |
), |
["%0D%0A"], |
[] |
), |
"\n" |
) |
); |
} |
|
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
integer wasMenuIndex = 0; |
list wasDialogMenu(list input, list actions, string direction) { |
integer cut = 11-wasListCountExclude(actions, [""]); |
if(direction == ">" && (wasMenuIndex+1)*cut+wasMenuIndex+1 < llGetListLength(input)) { |
++wasMenuIndex; |
jump slice; |
} |
if(direction == "<" && wasMenuIndex-1 >= 0) { |
--wasMenuIndex; |
jump slice; |
} |
@slice; |
integer multiple = wasMenuIndex*cut; |
input = llList2List(input, multiple+wasMenuIndex, multiple+cut+wasMenuIndex); |
input = wasListMerge(input, actions, ""); |
return input; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
integer wasListCountExclude(list input, list exclude) { |
if(llGetListLength(input) == 0) return 0; |
if(llListFindList(exclude, (list)llList2String(input, 0)) == -1) |
return 1 + wasListCountExclude(llDeleteSubList(input, 0, 0), exclude); |
return wasListCountExclude(llDeleteSubList(input, 0, 0), exclude); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
list wasListMerge(list l, list m, string merge) { |
if(llGetListLength(l) == 0 && llGetListLength(m) == 0) return []; |
string a = llList2String(m, 0); |
if(a != merge) return [ a ] + wasListMerge(l, llDeleteSubList(m, 0, 0), merge); |
return [ llList2String(l, 0) ] + wasListMerge(llDeleteSubList(l, 0, 0), llDeleteSubList(m, 0, 0), merge); |
} |
|
// callback URL |
string callback = ""; |
// configuration data |
string configuration = ""; |
// listen handle |
integer listenHandle = 0; |
string firstname = ""; |
string lastname = ""; |
list names = []; |
list UUIDs = []; |
// temporary list for button name normalization |
list menu = []; |
integer select = -1; |
string entity = ""; |
|
default { |
state_entry() { |
llSetTimerEvent(1); |
} |
link_message(integer sender, integer num, string message, key id) { |
if(sender != 1 || id != "configuration") return; |
configuration = message; |
state off; |
} |
timer() { |
llMessageLinked(LINK_ROOT, 0, "configuration", NULL_KEY); |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
|
state off { |
state_entry() { |
llReleaseControls(); |
llSetColor(<.5,0,0>, ALL_SIDES); |
} |
touch_end(integer num) { |
state on; |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
} |
|
state on { |
state_entry() { |
llSetColor(<0,.5,0>, ALL_SIDES); |
state url; |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
} |
|
state url { |
state_entry() { |
// DEBUG |
llOwnerSay("Requesting URL..."); |
llRequestURL(); |
} |
touch_end(integer num) { |
llSetColor(<.5,0,0>, ALL_SIDES); |
llResetScript(); |
} |
http_request(key id, string method, string body) { |
if(method != URL_REQUEST_GRANTED) return; |
callback = body; |
// DEBUG |
llOwnerSay("Got URL..."); |
state choose; |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
} |
|
state choose { |
state_entry() { |
// DEBUG |
llOwnerSay("Tell..."); |
llSetTimerEvent(1); |
listenHandle = llListen(-24, "", llGetOwner(), ""); |
llDialog( |
llGetOwner(), |
"[CORRADE] Select avatar, local or group:", |
[ |
"avatar", |
"local", |
"group" |
], |
-24 |
); |
} |
touch_end(integer num) { |
llResetScript(); |
} |
listen(integer channel, string name, key id, string message) { |
llListenRemove(listenHandle); |
|
entity = message; |
|
if(message == "local") state tell; |
if(message == "avatar") state select_avatar; |
if(message == "group") state get_groups; |
|
llResetScript(); |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
|
state get_groups { |
state_entry() { |
// DEBUG |
llOwnerSay("Getting groups..."); |
llSetTimerEvent(1); |
llInstantMessage( |
wasKeyValueGet( |
"corrade", |
configuration |
), |
wasKeyValueEncode( |
[ |
"command", "getcurrentgroups", |
"group", wasURLEscape( |
wasKeyValueGet( |
"group", |
configuration |
) |
), |
"password", wasURLEscape( |
wasKeyValueGet( |
"password", |
configuration |
) |
), |
"sift", wasListToCSV( |
[ |
"take", 16 |
] |
), |
"callback", wasURLEscape(callback) |
] |
) |
); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) != "getcurrentgroups" || |
wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Too many groups or unable to groups..."); |
llResetScript(); |
} |
list data = wasCSVToList( |
wasURLUnescape( |
wasKeyValueGet( |
"data", |
body |
) |
) |
); |
// Copy the names and UUIDs of the groups for the menu. |
names = []; |
UUIDs = []; |
do { |
UUIDs += llList2String(data, -1); |
data = llDeleteSubList(data, -1, -1); |
names += llList2String(data, -1); |
data = llDeleteSubList(data, -1, -1); |
} while(llGetListLength(data)); |
state select_group; |
} |
timer() { |
llRequestAgentData( |
(key)wasKeyValueGet( |
"corrade", |
configuration |
), DATA_ONLINE); |
} |
dataserver(key id, string data) { |
if(data != "1") llResetScript(); |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
|
state select_group { |
state_entry() { |
// DEBUG |
llOwnerSay("Sending menu..."); |
menu = []; |
integer i = 0; |
do { |
menu += llGetSubString(llList2String(names, i), 0, 23); |
} while(++i < llGetListLength(names)); |
llListen(-10, "", llGetOwner(), ""); |
llDialog(llGetOwner(), "\nPlease choose a group for Corrade to talk in from the list below:\n", wasDialogMenu(menu, ["⟵ Back", "", "Next ⟶"], ""), -10); |
llSetTimerEvent(60); |
} |
touch_end(integer num) { |
llSetColor(<.5,0,0>, ALL_SIDES); |
llResetScript(); |
} |
listen(integer channel, string name, key id, string message) { |
if(message == "⟵ Back") { |
llDialog(id, "\nPlease choose a group for Corrade to talk in from the list below:\n", wasDialogMenu(menu, ["⟵ Back", "", "Next ⟶"], "<"), -10); |
return; |
} |
if(message == "Next ⟶") { |
llDialog(id, "\nPlease choose a primitive for Corrade to sit on from the list below:\n", wasDialogMenu(menu, ["⟵ Back", "", "Next ⟶"], ">"), -10); |
return; |
} |
integer i = llGetListLength(menu) - 1; |
do { |
string v = llList2String(menu, i); |
if(llSubStringIndex(v, message) != -1) |
jump sit; |
} while(--i > -1); |
// GC |
menu = []; |
// DEBUG |
llOwnerSay("Invalid menu item selected..."); |
llResetScript(); |
@sit; |
// GC |
menu = []; |
select = i; |
|
state tell; |
|
} |
timer() { |
// DEBUG |
llOwnerSay("Dialog menu timeout..."); |
llResetScript(); |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
|
state select_avatar { |
state_entry() { |
// DEBUG |
llOwnerSay("Select avatar..."); |
llSetTimerEvent(1); |
listenHandle = llListen(-24, "", llGetOwner(), ""); |
llTextBox(llGetOwner(), "[CORRADE] Enter the avatar name:", -24); |
} |
touch_end(integer num) { |
llResetScript(); |
} |
listen(integer channel, string name, key id, string message) { |
llListenRemove(listenHandle); |
list name = llParseString2List(message, [" "], [""]); |
if(llGetListLength(name) != 2) { |
llOwnerSay("Invalid avatar name..."); |
state tell; |
} |
firstname = llList2String(name, 0); |
lastname = llList2String(name, 1); |
state tell; |
} |
timer() { |
llRequestAgentData( |
(key)wasKeyValueGet( |
"corrade", |
configuration |
), DATA_ONLINE); |
} |
dataserver(key id, string data) { |
if(data != "1") llResetScript(); |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
|
state tell { |
state_entry() { |
// DEBUG |
llOwnerSay("Tell..."); |
llSetTimerEvent(1); |
listenHandle = llListen(-24, "", llGetOwner(), ""); |
llTextBox(llGetOwner(), "[CORRADE] Enter the message:", -24); |
} |
touch_end(integer num) { |
llResetScript(); |
} |
timer() { |
llRequestAgentData( |
(key)wasKeyValueGet( |
"corrade", |
configuration |
), DATA_ONLINE); |
} |
dataserver(key id, string data) { |
if(data != "1") llResetScript(); |
} |
listen(integer channel, string name, key id, string message) { |
llListenRemove(listenHandle); |
llInstantMessage( |
wasKeyValueGet( |
"corrade", |
configuration |
), |
wasKeyValueEncode( |
[ |
"command", "tell", |
"group", wasURLEscape( |
wasKeyValueGet( |
"group", |
configuration |
) |
), |
"password", wasURLEscape( |
wasKeyValueGet( |
"password", |
configuration |
) |
), |
"entity", entity, |
"type", "normal", |
"target", wasURLEscape( |
llList2String(UUIDs, select) |
), |
"firstname", wasURLEscape(firstname), |
"lastname", wasURLEscape(lastname), |
"message", wasURLEscape(message), |
"callback", wasURLEscape(callback) |
] |
) |
); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) != "tell" || |
wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Failed to say message: " + wasKeyValueGet("error", body)); |
} |
llResetScript(); |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
/heads-up-display/touch/touch.lsl |
@@ -0,0 +1,569 @@ |
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) Wizardry and Steamworks 2016 - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// |
// This script will make Corrade scan for primitives in the vicinity and |
// will then offer a dialog for you to pick which primitive to touch. |
// |
// For more information on Corrade, please see: |
// http://grimore.org/secondlife/scripted_agents/corrade |
// |
/////////////////////////////////////////////////////////////////////////// |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueGet(string k, string data) { |
if(llStringLength(data) == 0) return ""; |
if(llStringLength(k) == 0) return ""; |
list a = llParseString2List(data, ["&", "="], []); |
integer i = llListFindList(a, [ k ]); |
if(i != -1) return llList2String(a, i+1); |
return ""; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueEncode(list data) { |
list k = llList2ListStrided(data, 0, -1, 2); |
list v = llList2ListStrided(llDeleteSubList(data, 0, 0), 0, -1, 2); |
data = []; |
do { |
data += llList2String(k, 0) + "=" + llList2String(v, 0); |
k = llDeleteSubList(k, 0, 0); |
v = llDeleteSubList(v, 0, 0); |
} while(llGetListLength(k) != 0); |
return llDumpList2String(data, "&"); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2011 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// http://was.fm/secondlife/wanderer |
vector wasCirclePoint(float radius) { |
float x = llPow(-1, 1 + (integer) llFrand(2)) * llFrand(radius*2); |
float y = llPow(-1, 1 + (integer) llFrand(2)) * llFrand(radius*2); |
if(llPow(x,2) + llPow(y,2) <= llPow(radius,2)) |
return <x, y, 0>; |
return wasCirclePoint(radius); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// escapes a string in conformance with RFC1738 |
string wasURLEscape(string i) { |
string o = ""; |
do { |
string c = llGetSubString(i, 0, 0); |
i = llDeleteSubString(i, 0, 0); |
if(c == "") jump continue; |
if(c == " ") { |
o += "+"; |
jump continue; |
} |
if(c == "\n") { |
o += "%0D" + llEscapeURL(c); |
jump continue; |
} |
o += llEscapeURL(c); |
@continue; |
} while(i != ""); |
return o; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
list wasCSVToList(string csv) { |
list l = []; |
list s = []; |
string m = ""; |
do { |
string a = llGetSubString(csv, 0, 0); |
csv = llDeleteSubString(csv, 0, 0); |
if(a == ",") { |
if(llList2String(s, -1) != "\"") { |
l += m; |
m = ""; |
jump continue; |
} |
m += a; |
jump continue; |
} |
if(a == "\"" && llGetSubString(csv, 0, 0) == a) { |
m += a; |
csv = llDeleteSubString(csv, 0, 0); |
jump continue; |
} |
if(a == "\"") { |
if(llList2String(s, -1) != a) { |
s += a; |
jump continue; |
} |
s = llDeleteSubList(s, -1, -1); |
jump continue; |
} |
m += a; |
@continue; |
} while(csv != ""); |
// postcondition: length(s) = 0 |
return l + m; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasListToCSV(list l) { |
list v = []; |
do { |
string a = llDumpList2String( |
llParseStringKeepNulls( |
llList2String( |
l, |
0 |
), |
["\""], |
[] |
), |
"\"\"" |
); |
if(llParseStringKeepNulls( |
a, |
[" ", ",", "\n", "\""], [] |
) != |
(list) a |
) a = "\"" + a + "\""; |
v += a; |
l = llDeleteSubList(l, 0, 0); |
} while(l != []); |
return llDumpList2String(v, ","); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// unescapes a string in conformance with RFC1738 |
string wasURLUnescape(string i) { |
return llUnescapeURL( |
llDumpList2String( |
llParseString2List( |
llDumpList2String( |
llParseString2List( |
i, |
["+"], |
[] |
), |
" " |
), |
["%0D%0A"], |
[] |
), |
"\n" |
) |
); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
integer wasMenuIndex = 0; |
list wasDialogMenu(list input, list actions, string direction) { |
integer cut = 11-wasListCountExclude(actions, [""]); |
if(direction == ">" && (wasMenuIndex+1)*cut+wasMenuIndex+1 < llGetListLength(input)) { |
++wasMenuIndex; |
jump slice; |
} |
if(direction == "<" && wasMenuIndex-1 >= 0) { |
--wasMenuIndex; |
jump slice; |
} |
@slice; |
integer multiple = wasMenuIndex*cut; |
input = llList2List(input, multiple+wasMenuIndex, multiple+cut+wasMenuIndex); |
input = wasListMerge(input, actions, ""); |
return input; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
integer wasListCountExclude(list input, list exclude) { |
if(llGetListLength(input) == 0) return 0; |
if(llListFindList(exclude, (list)llList2String(input, 0)) == -1) |
return 1 + wasListCountExclude(llDeleteSubList(input, 0, 0), exclude); |
return wasListCountExclude(llDeleteSubList(input, 0, 0), exclude); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
list wasListMerge(list l, list m, string merge) { |
if(llGetListLength(l) == 0 && llGetListLength(m) == 0) return []; |
string a = llList2String(m, 0); |
if(a != merge) return [ a ] + wasListMerge(l, llDeleteSubList(m, 0, 0), merge); |
return [ llList2String(l, 0) ] + wasListMerge(llDeleteSubList(l, 0, 0), llDeleteSubList(m, 0, 0), merge); |
} |
|
// configuration data |
string configuration = ""; |
// callback URL |
string callback = ""; |
// scanned primitives |
list names = []; |
list UUIDs = []; |
// temporary list for button name normalization |
list menu = []; |
integer select = -1; |
|
default { |
state_entry() { |
llSetTimerEvent(1); |
} |
link_message(integer sender, integer num, string message, key id) { |
if(sender != 1 || id != "configuration") return; |
configuration = message; |
state off; |
} |
timer() { |
llMessageLinked(LINK_ROOT, 0, "configuration", NULL_KEY); |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
|
state off { |
state_entry() { |
llSetColor(<.5,0,0>, ALL_SIDES); |
} |
touch_end(integer num) { |
state on; |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
} |
|
state on { |
state_entry() { |
llSetColor(<0,.5,0>, ALL_SIDES); |
state url; |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
} |
|
state url { |
state_entry() { |
// DEBUG |
llOwnerSay("Requesting URL..."); |
llRequestURL(); |
} |
touch_end(integer num) { |
llSetColor(<.5,0,0>, ALL_SIDES); |
llResetScript(); |
} |
http_request(key id, string method, string body) { |
if(method != URL_REQUEST_GRANTED) return; |
callback = body; |
// DEBUG |
llOwnerSay("Got URL..."); |
state scan; |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
} |
|
state scan { |
state_entry() { |
// DEBUG |
llOwnerSay("Getting objects..."); |
llInstantMessage( |
wasKeyValueGet( |
"corrade", |
configuration |
), |
wasKeyValueEncode( |
[ |
"command", "getobjectsdata", |
"group", wasURLEscape( |
wasKeyValueGet( |
"group", |
configuration |
) |
), |
"password", wasURLEscape( |
wasKeyValueGet( |
"password", |
configuration |
) |
), |
"entity", "world", |
"range", wasURLEscape( |
wasKeyValueGet( |
"radar", |
configuration |
) |
), |
"data", wasListToCSV( |
[ |
"Properties.Name", |
"ID" |
] |
), |
"sift", wasListToCSV( |
[ |
"take", 32 |
] |
), |
"callback", wasURLEscape(callback) |
] |
) |
); |
} |
touch_end(integer num) { |
llSetColor(<.5,0,0>, ALL_SIDES); |
llResetScript(); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) != "getobjectsdata" || |
wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Unable to query primitives data: " + |
wasURLUnescape( |
wasKeyValueGet( |
"error", |
body |
) |
) |
); |
llResetScript(); |
} |
string dataKey = wasURLUnescape( |
wasKeyValueGet( |
"data", |
body |
) |
); |
if(dataKey == "") { |
// DEBUG |
llOwnerSay("No data for scanned primitives..."); |
llResetScript(); |
} |
list data = wasCSVToList(dataKey); |
// Copy the names and UUIDs of the primitives. |
names = []; |
UUIDs = []; |
do { |
string v = llList2String(data, -1); |
data = llDeleteSubList(data, -1, -1); |
string k = llList2String(data, -1); |
data = llDeleteSubList(data, -1, -1); |
if(k == "Properties.Name") { |
names += v; |
jump continue; |
} |
UUIDs += (key)v; |
@continue; |
} while(llGetListLength(data)); |
state choose; |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
|
state choose { |
state_entry() { |
// DEBUG |
llOwnerSay("Sending menu..."); |
menu = []; |
integer i = 0; |
do { |
menu += llGetSubString(llList2String(names, i), 0, 23); |
} while(++i < llGetListLength(names)); |
llListen(-10, "", llGetOwner(), ""); |
llDialog(llGetOwner(), "\nPlease choose a primitive for Corrade to sit on from the list below:\n", wasDialogMenu(menu, ["⟵ Back", "", "Next ⟶"], ""), -10); |
llSetTimerEvent(60); |
} |
touch_end(integer num) { |
llSetColor(<.5,0,0>, ALL_SIDES); |
llResetScript(); |
} |
listen(integer channel, string name, key id, string message) { |
if(message == "⟵ Back") { |
llDialog(id, "\nPlease choose a primitive for Corrade to sit on from the list below:\n", wasDialogMenu(menu, ["⟵ Back", "", "Next ⟶"], "<"), -10); |
return; |
} |
if(message == "Next ⟶") { |
llDialog(id, "\nPlease choose a primitive for Corrade to sit on from the list below:\n", wasDialogMenu(menu, ["⟵ Back", "", "Next ⟶"], ">"), -10); |
return; |
} |
integer i = llGetListLength(menu) - 1; |
do { |
string v = llList2String(menu, i); |
if(llSubStringIndex(v, message) != -1) |
jump sit; |
} while(--i > -1); |
// GC |
menu = []; |
// DEBUG |
llOwnerSay("Invalid menu item selected..."); |
llResetScript(); |
@sit; |
// GC |
menu = []; |
select = i; |
// Got a menu item so bind to permission notifications and sit. |
state touch_primitive; |
|
} |
timer() { |
// DEBUG |
llOwnerSay("Dialog menu timeout..."); |
llResetScript(); |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
|
state touch_primitive { |
state_entry() { |
// DEBUG |
llOwnerSay("Touching: " + |
llList2String(names, select) + |
" UUID: " + |
llList2String(UUIDs, select) |
); |
llInstantMessage( |
(key)wasKeyValueGet( |
"corrade", |
configuration |
), |
wasKeyValueEncode( |
[ |
"command", "touch", |
"group", wasURLEscape( |
wasKeyValueGet( |
"group", |
configuration |
) |
), |
"password", wasURLEscape( |
wasKeyValueGet( |
"password", |
configuration |
) |
), |
"item", wasURLEscape( |
llList2String(UUIDs, select) |
), |
"range", wasURLEscape( |
wasKeyValueGet( |
"radar", |
configuration |
) |
), |
"callback", wasURLEscape(callback) |
] |
) |
); |
llSetTimerEvent(60); |
} |
touch_end(integer num) { |
llSetColor(<.5,0,0>, ALL_SIDES); |
llResetScript(); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) != "touch" || |
wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Failed to touch primitive..."); |
llResetScript(); |
} |
// DEBUG |
llOwnerSay("Touched..."); |
llResetScript(); |
} |
timer() { |
llResetScript(); |
} |
attach(key id) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START) || (change & CHANGED_OWNER)) { |
llResetScript(); |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
/instant-message-chatbot/instant-message-chatbot.lsl |
@@ -0,0 +1,396 @@ |
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// |
// This is relays instant messages chat to an avatar and meant to work with |
// Corrade the Second Life / OpenSim bot. You can find more details about |
// the bot by following the URL: |
// |
// http://was.fm/secondlife/scripted_agents/corrade |
// |
// The script works in combination with a "configuration" notecard that |
// must be placed in the same primitive as this script. The purpose of this |
// script is to demonstrate relaying instant messages to an avatar using |
// Corrade and you are free to use, change, and commercialize it under the |
// terms of the GNU/GPLv3 license at: http://www.gnu.org/licenses/gpl.html |
// |
/////////////////////////////////////////////////////////////////////////// |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueGet(string k, string data) { |
if(llStringLength(data) == 0) return ""; |
if(llStringLength(k) == 0) return ""; |
list a = llParseString2List(data, ["&", "="], []); |
integer i = llListFindList(llList2ListStrided(a, 0, -1, 2), [ k ]); |
if(i != -1) return llList2String(a, 2*i+1); |
return ""; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueEncode(list data) { |
list k = llList2ListStrided(data, 0, -1, 2); |
list v = llList2ListStrided(llDeleteSubList(data, 0, 0), 0, -1, 2); |
data = []; |
do { |
data += llList2String(k, 0) + "=" + llList2String(v, 0); |
k = llDeleteSubList(k, 0, 0); |
v = llDeleteSubList(v, 0, 0); |
} while(llGetListLength(k) != 0); |
return llDumpList2String(data, "&"); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// escapes a string in conformance with RFC1738 |
string wasURLEscape(string i) { |
string o = ""; |
do { |
string c = llGetSubString(i, 0, 0); |
i = llDeleteSubString(i, 0, 0); |
if(c == "") jump continue; |
if(c == " ") { |
o += "+"; |
jump continue; |
} |
if(c == "\n") { |
o += "%0D" + llEscapeURL(c); |
jump continue; |
} |
o += llEscapeURL(c); |
@continue; |
} while(i != ""); |
return o; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// unescapes a string in conformance with RFC1738 |
string wasURLUnescape(string i) { |
return llUnescapeURL( |
llDumpList2String( |
llParseString2List( |
llDumpList2String( |
llParseString2List( |
i, |
["+"], |
[] |
), |
" " |
), |
["%0D%0A"], |
[] |
), |
"\n" |
) |
); |
} |
|
// corrade data |
key CORRADE = NULL_KEY; |
string GROUP = ""; |
string PASSWORD = ""; |
|
// instance variables |
string firstname = ""; |
string lastname = ""; |
string message = ""; |
|
// for holding the callback URL |
string callback = ""; |
|
// for notecard reading |
integer line = 0; |
|
// key-value data will be read into this list |
list tuples = []; |
|
default { |
state_entry() { |
if(llGetInventoryType("configuration") != INVENTORY_NOTECARD) { |
llOwnerSay("Sorry, could not find a configuration inventory notecard."); |
return; |
} |
// DEBUG |
llOwnerSay("Reading configuration file..."); |
llGetNotecardLine("configuration", line); |
} |
dataserver(key id, string data) { |
if(data == EOF) { |
// invariant, length(tuples) % 2 == 0 |
if(llGetListLength(tuples) % 2 != 0) { |
llOwnerSay("Error in configuration notecard."); |
return; |
} |
CORRADE = llList2Key( |
tuples, |
llListFindList( |
tuples, |
[ |
"corrade" |
] |
) |
+1); |
if(CORRADE == NULL_KEY) { |
llOwnerSay("Error in configuration notecard: corrade"); |
return; |
} |
GROUP = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"group" |
] |
) |
+1); |
if(GROUP == "") { |
llOwnerSay("Error in configuration notecard: group"); |
return; |
} |
PASSWORD = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"password" |
] |
) |
+1); |
if(PASSWORD == "") { |
llOwnerSay("Error in configuration notecard: password"); |
return; |
} |
// DEBUG |
llOwnerSay("Read configuration notecard..."); |
state url; |
} |
if(data == "") jump continue; |
integer i = llSubStringIndex(data, "#"); |
if(i != -1) data = llDeleteSubString(data, i, -1); |
list o = llParseString2List(data, ["="], []); |
// get rid of starting and ending quotes |
string k = llDumpList2String( |
llParseString2List( |
llStringTrim( |
llList2String( |
o, |
0 |
), |
STRING_TRIM), |
["\""], [] |
), "\""); |
string v = llDumpList2String( |
llParseString2List( |
llStringTrim( |
llList2String( |
o, |
1 |
), |
STRING_TRIM), |
["\""], [] |
), "\""); |
if(k == "" || v == "") jump continue; |
tuples += k; |
tuples += v; |
@continue; |
llGetNotecardLine("configuration", ++line); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state url { |
state_entry() { |
// DEBUG |
llOwnerSay("Requesting URL..."); |
llRequestURL(); |
} |
http_request(key id, string method, string body) { |
if(method != URL_REQUEST_GRANTED) return; |
callback = body; |
// DEBUG |
llOwnerSay("Got URL..."); |
state detect; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state detect { |
state_entry() { |
// DEBUG |
llOwnerSay("Detecting if Corrade is online..."); |
llSetTimerEvent(5); |
} |
timer() { |
llRequestAgentData((key)CORRADE, DATA_ONLINE); |
} |
dataserver(key id, string data) { |
if(data != "1") { |
// DEBUG |
llOwnerSay("Corrade is not online, sleeping..."); |
llSetTimerEvent(30); |
return; |
} |
state notify; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state notify { |
state_entry() { |
// DEBUG |
llOwnerSay("Binding to the instant message notification..."); |
llInstantMessage( |
(key)CORRADE, |
wasKeyValueEncode( |
[ |
"command", "notify", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"action", "set", |
"type", "message", |
"URL", wasURLEscape(callback), |
"callback", wasURLEscape(callback) |
] |
) |
); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) != "notify" || |
wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Failed to bind to the message notification..."); |
state detect; |
} |
// DEBUG |
llOwnerSay("Local chat notification installed..."); |
state chat; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state chat { |
state_entry() { |
// DEBUG |
llOwnerSay("Waiting for instant messages..."); |
llSetTimerEvent(5); |
} |
timer() { |
llRequestAgentData((key)CORRADE, DATA_ONLINE); |
} |
dataserver(key id, string data) { |
if(data == "1") return; |
// DEBUG |
llOwnerSay("Corrade is not online, sleeping..."); |
// Switch to detect loop and wait there for Corrade to come online. |
state detect; |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
firstname = wasURLUnescape(wasKeyValueGet("firstname", body)); |
lastname = wasURLUnescape(wasKeyValueGet("lastname", body)); |
// DEBUG |
llOwnerSay("Got instant message from: " + firstname + " " + lastname); |
message = wasKeyValueGet("message", body); |
state process; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state process { |
state_entry() { |
// DEBUG |
llOwnerSay("Sending message to Corrade's AI facility..."); |
llInstantMessage(CORRADE, |
wasKeyValueEncode( |
[ |
"command", "ai", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"action", "process", |
"message", wasURLEscape(message), |
"callback", wasURLEscape(callback) |
] |
) |
); |
llSetTimerEvent(5); |
} |
timer() { |
llRequestAgentData((key)CORRADE, DATA_ONLINE); |
} |
dataserver(key id, string data) { |
if(data == "1") return; |
// DEBUG |
llOwnerSay("Corrade is not online, sleeping..."); |
// Switch to detect loop and wait there for Corrade to come online. |
state detect; |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
// DEBUG |
llOwnerSay("Sending answer to: " + firstname + " " + lastname); |
llInstantMessage(CORRADE, |
wasKeyValueEncode( |
[ |
"command", "tell", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"firstname", firstname, |
"lastname", lastname, |
"entity", "avatar", |
"message", wasKeyValueGet("data", body) |
] |
) |
); |
state chat; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
/local-chat-chatbot/local-chat-chatbot.lsl |
@@ -0,0 +1,394 @@ |
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// |
// This script demonstrates the built-in local chat AIML talking feature of |
// the Corrade Second Life / OpenSim bot. For more documentation on Corrade |
// follow the URL: http://grimore.org/secondlife/scripted_agents/corrade |
// |
// The script works in combination with a "configuration" notecard that |
// must be placed in the same primitive as this script. The purpose of this |
// script is to demonstrate answering messages with Corrade's built-in AI |
// and you are free to use, change, and commercialize it under the terms |
// of the GNU/GPLv3 license at: http://www.gnu.org/licenses/gpl.html |
// |
/////////////////////////////////////////////////////////////////////////// |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueGet(string k, string data) { |
if(llStringLength(data) == 0) return ""; |
if(llStringLength(k) == 0) return ""; |
list a = llParseString2List(data, ["&", "="], []); |
integer i = llListFindList(llList2ListStrided(a, 0, -1, 2), [ k ]); |
if(i != -1) return llList2String(a, 2*i+1); |
return ""; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueEncode(list data) { |
list k = llList2ListStrided(data, 0, -1, 2); |
list v = llList2ListStrided(llDeleteSubList(data, 0, 0), 0, -1, 2); |
data = []; |
do { |
data += llList2String(k, 0) + "=" + llList2String(v, 0); |
k = llDeleteSubList(k, 0, 0); |
v = llDeleteSubList(v, 0, 0); |
} while(llGetListLength(k) != 0); |
return llDumpList2String(data, "&"); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// escapes a string in conformance with RFC1738 |
string wasURLEscape(string i) { |
string o = ""; |
do { |
string c = llGetSubString(i, 0, 0); |
i = llDeleteSubString(i, 0, 0); |
if(c == "") jump continue; |
if(c == " ") { |
o += "+"; |
jump continue; |
} |
if(c == "\n") { |
o += "%0D" + llEscapeURL(c); |
jump continue; |
} |
o += llEscapeURL(c); |
@continue; |
} while(i != ""); |
return o; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// unescapes a string in conformance with RFC1738 |
string wasURLUnescape(string i) { |
return llUnescapeURL( |
llDumpList2String( |
llParseString2List( |
llDumpList2String( |
llParseString2List( |
i, |
["+"], |
[] |
), |
" " |
), |
["%0D%0A"], |
[] |
), |
"\n" |
) |
); |
} |
|
// corrade data |
key CORRADE = NULL_KEY; |
string GROUP = ""; |
string PASSWORD = ""; |
|
// instance variables |
string firstname = ""; |
string lastname = ""; |
string message = ""; |
|
// for holding the callback URL |
string callback = ""; |
|
// for notecard reading |
integer line = 0; |
|
// key-value data will be read into this list |
list tuples = []; |
|
default { |
state_entry() { |
if(llGetInventoryType("configuration") != INVENTORY_NOTECARD) { |
llOwnerSay("Sorry, could not find a configuration inventory notecard."); |
return; |
} |
// DEBUG |
llOwnerSay("Reading configuration file..."); |
llGetNotecardLine("configuration", line); |
} |
dataserver(key id, string data) { |
if(data == EOF) { |
// invariant, length(tuples) % 2 == 0 |
if(llGetListLength(tuples) % 2 != 0) { |
llOwnerSay("Error in configuration notecard."); |
return; |
} |
CORRADE = llList2Key( |
tuples, |
llListFindList( |
tuples, |
[ |
"corrade" |
] |
) |
+1); |
if(CORRADE == NULL_KEY) { |
llOwnerSay("Error in configuration notecard: corrade"); |
return; |
} |
GROUP = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"group" |
] |
) |
+1); |
if(GROUP == "") { |
llOwnerSay("Error in configuration notecard: group"); |
return; |
} |
PASSWORD = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"password" |
] |
) |
+1); |
if(PASSWORD == "") { |
llOwnerSay("Error in configuration notecard: password"); |
return; |
} |
// DEBUG |
llOwnerSay("Read configuration notecard..."); |
state url; |
} |
if(data == "") jump continue; |
integer i = llSubStringIndex(data, "#"); |
if(i != -1) data = llDeleteSubString(data, i, -1); |
list o = llParseString2List(data, ["="], []); |
// get rid of starting and ending quotes |
string k = llDumpList2String( |
llParseString2List( |
llStringTrim( |
llList2String( |
o, |
0 |
), |
STRING_TRIM), |
["\""], [] |
), "\""); |
string v = llDumpList2String( |
llParseString2List( |
llStringTrim( |
llList2String( |
o, |
1 |
), |
STRING_TRIM), |
["\""], [] |
), "\""); |
if(k == "" || v == "") jump continue; |
tuples += k; |
tuples += v; |
@continue; |
llGetNotecardLine("configuration", ++line); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state url { |
state_entry() { |
// DEBUG |
llOwnerSay("Requesting URL..."); |
llRequestURL(); |
} |
http_request(key id, string method, string body) { |
if(method != URL_REQUEST_GRANTED) return; |
callback = body; |
// DEBUG |
llOwnerSay("Got URL..."); |
state detect; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state detect { |
state_entry() { |
// DEBUG |
llOwnerSay("Detecting if Corrade is online..."); |
llSetTimerEvent(5); |
} |
timer() { |
llRequestAgentData((key)CORRADE, DATA_ONLINE); |
} |
dataserver(key id, string data) { |
if(data != "1") { |
// DEBUG |
llOwnerSay("Corrade is not online, sleeping..."); |
llSetTimerEvent(30); |
return; |
} |
state notify; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state notify { |
state_entry() { |
// DEBUG |
llOwnerSay("Binding to the local message notification..."); |
llInstantMessage( |
(key)CORRADE, |
wasKeyValueEncode( |
[ |
"command", "notify", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"action", "set", |
"type", "local", |
"URL", wasURLEscape(callback), |
"callback", wasURLEscape(callback) |
] |
) |
); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) != "notify" || |
wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Failed to bind to the local chat notification..."); |
state detect; |
} |
// DEBUG |
llOwnerSay("Local chat notification installed..."); |
state chat; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state chat { |
state_entry() { |
// DEBUG |
llOwnerSay("Waiting for local chat message..."); |
llSetTimerEvent(5); |
} |
timer() { |
llRequestAgentData((key)CORRADE, DATA_ONLINE); |
} |
dataserver(key id, string data) { |
if(data == "1") return; |
// DEBUG |
llOwnerSay("Corrade is not online, sleeping..."); |
// Switch to detect loop and wait there for Corrade to come online. |
state detect; |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
firstname = wasURLUnescape(wasKeyValueGet("firstname", body)); |
lastname = wasURLUnescape(wasKeyValueGet("lastname", body)); |
// DEBUG |
message = wasKeyValueGet("message", body); |
if(message != "") { |
llOwnerSay("Got local chat from: " + firstname + " " + lastname); |
state process; |
} |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state process { |
state_entry() { |
// DEBUG |
llOwnerSay("Sending message to Corrade's AI facility..."); |
llInstantMessage(CORRADE, |
wasKeyValueEncode( |
[ |
"command", "ai", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"action", "process", |
"message", wasURLEscape(message), |
"callback", wasURLEscape(callback) |
] |
) |
); |
llSetTimerEvent(5); |
} |
timer() { |
llRequestAgentData((key)CORRADE, DATA_ONLINE); |
} |
dataserver(key id, string data) { |
if(data == "1") return; |
// DEBUG |
llOwnerSay("Corrade is not online, sleeping..."); |
// Switch to detect loop and wait there for Corrade to come online. |
state detect; |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
// DEBUG |
llOwnerSay("Sending answer to: " + firstname + " " + lastname); |
llInstantMessage(CORRADE, |
wasKeyValueEncode( |
[ |
"command", "tell", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"entity", "local", |
"message", wasKeyValueGet("data", body) |
] |
) |
); |
state chat; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
/local-chat-to-instant-message/local-chat-to-instant-message.lsl |
@@ -0,0 +1,361 @@ |
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// |
// This is relays local chat to an avatar and meant to work with Corrade |
// the Second Life / OpenSim bot. You can find more details about the bot |
// by following the URL: http://was.fm/secondlife/scripted_agents/corrade |
// |
// The script works in combination with a "configuration" notecard that |
// must be placed in the same primitive as this script. The purpose of this |
// script is to demonstrate relaying local chat to an avatar with Corrade |
// and you are free to use, change, and commercialize it under the terms |
// of the GNU/GPLv3 license at: http://www.gnu.org/licenses/gpl.html |
// |
/////////////////////////////////////////////////////////////////////////// |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueGet(string k, string data) { |
if(llStringLength(data) == 0) return ""; |
if(llStringLength(k) == 0) return ""; |
list a = llParseString2List(data, ["&", "="], []); |
integer i = llListFindList(a, [ k ]); |
if(i != -1) return llList2String(a, i+1); |
return ""; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueEncode(list data) { |
list k = llList2ListStrided(data, 0, -1, 2); |
list v = llList2ListStrided(llDeleteSubList(data, 0, 0), 0, -1, 2); |
data = []; |
do { |
data += llList2String(k, 0) + "=" + llList2String(v, 0); |
k = llDeleteSubList(k, 0, 0); |
v = llDeleteSubList(v, 0, 0); |
} while(llGetListLength(k) != 0); |
return llDumpList2String(data, "&"); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// escapes a string in conformance with RFC1738 |
string wasURLEscape(string i) { |
string o = ""; |
do { |
string c = llGetSubString(i, 0, 0); |
i = llDeleteSubString(i, 0, 0); |
if(c == "") jump continue; |
if(c == " ") { |
o += "+"; |
jump continue; |
} |
if(c == "\n") { |
o += "%0D" + llEscapeURL(c); |
jump continue; |
} |
o += llEscapeURL(c); |
@continue; |
} while(i != ""); |
return o; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// unescapes a string in conformance with RFC1738 |
string wasURLUnescape(string i) { |
return llUnescapeURL( |
llDumpList2String( |
llParseString2List( |
llDumpList2String( |
llParseString2List( |
i, |
["+"], |
[] |
), |
" " |
), |
["%0D%0A"], |
[] |
), |
"\n" |
) |
); |
} |
|
// corrade data |
key CORRADE = NULL_KEY; |
string GROUP = ""; |
string PASSWORD = ""; |
key DESTINATION = NULL_KEY; |
|
// for holding the callback URL |
string callback = ""; |
|
// for notecard reading |
integer line = 0; |
|
// key-value data will be read into this list |
list tuples = []; |
|
default { |
state_entry() { |
if(llGetInventoryType("configuration") != INVENTORY_NOTECARD) { |
llOwnerSay("Sorry, could not find a configuration inventory notecard."); |
return; |
} |
// DEBUG |
llOwnerSay("Reading configuration file..."); |
llGetNotecardLine("configuration", line); |
} |
dataserver(key id, string data) { |
if(data == EOF) { |
// invariant, length(tuples) % 2 == 0 |
if(llGetListLength(tuples) % 2 != 0) { |
llOwnerSay("Error in configuration notecard."); |
return; |
} |
CORRADE = llList2Key( |
tuples, |
llListFindList( |
tuples, |
[ |
"corrade" |
] |
) |
+1); |
if(CORRADE == NULL_KEY) { |
llOwnerSay("Error in configuration notecard: corrade"); |
return; |
} |
GROUP = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"group" |
] |
) |
+1); |
if(GROUP == "") { |
llOwnerSay("Error in configuration notecard: group"); |
return; |
} |
PASSWORD = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"password" |
] |
) |
+1); |
if(PASSWORD == "") { |
llOwnerSay("Error in configuration notecard: password"); |
return; |
} |
DESTINATION = llList2Key( |
tuples, |
llListFindList( |
tuples, |
[ |
"destination" |
] |
) |
+1); |
if(DESTINATION == NULL_KEY) { |
llOwnerSay("Error in configuration notecard: destination"); |
return; |
} |
// DEBUG |
llOwnerSay("Read configuration notecard..."); |
state url; |
} |
if(data == "") jump continue; |
integer i = llSubStringIndex(data, "#"); |
if(i != -1) data = llDeleteSubString(data, i, -1); |
list o = llParseString2List(data, ["="], []); |
// get rid of starting and ending quotes |
string k = llDumpList2String( |
llParseString2List( |
llStringTrim( |
llList2String( |
o, |
0 |
), |
STRING_TRIM), |
["\""], [] |
), "\""); |
string v = llDumpList2String( |
llParseString2List( |
llStringTrim( |
llList2String( |
o, |
1 |
), |
STRING_TRIM), |
["\""], [] |
), "\""); |
if(k == "" || v == "") jump continue; |
tuples += k; |
tuples += v; |
@continue; |
llGetNotecardLine("configuration", ++line); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state url { |
state_entry() { |
// DEBUG |
llOwnerSay("Requesting URL..."); |
llRequestURL(); |
} |
http_request(key id, string method, string body) { |
if(method != URL_REQUEST_GRANTED) return; |
callback = body; |
// DEBUG |
llOwnerSay("Got URL..."); |
state detect; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state detect { |
state_entry() { |
// DEBUG |
llOwnerSay("Detecting if Corrade is online..."); |
llSetTimerEvent(5); |
} |
timer() { |
llRequestAgentData((key)CORRADE, DATA_ONLINE); |
} |
dataserver(key id, string data) { |
if(data != "1") { |
// DEBUG |
llOwnerSay("Corrade is not online, sleeping..."); |
llSetTimerEvent(30); |
return; |
} |
state notify; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state notify { |
state_entry() { |
// DEBUG |
llOwnerSay("Binding to the local chat notification..."); |
llInstantMessage( |
(key)CORRADE, |
wasKeyValueEncode( |
[ |
"command", "notify", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"action", "set", |
"type", "local", |
"URL", wasURLEscape(callback), |
"callback", wasURLEscape(callback) |
] |
) |
); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) != "notify" || |
wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Failed to bind to the local chat notification..."); |
state detect; |
} |
// DEBUG |
llOwnerSay("Local chat notification installed..."); |
state relay; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state relay { |
state_entry() { |
// DEBUG |
llOwnerSay("Waiting for chatter..."); |
} |
timer() { |
llRequestAgentData((key)CORRADE, DATA_ONLINE); |
} |
dataserver(key id, string data) { |
if(data == "1") return; |
// DEBUG |
llOwnerSay("Corrade is not online, sleeping..."); |
// Switch to detect loop and wait there for Corrade to come online. |
state detect; |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
|
llInstantMessage(DESTINATION, |
wasURLUnescape( |
wasKeyValueGet( |
"firstname", |
body |
) |
) + " " + |
wasURLUnescape( |
wasKeyValueGet( |
"lastname", |
body |
) |
) + ": " + |
wasURLUnescape( |
wasKeyValueGet( |
"message", |
body |
) |
) |
); |
|
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
/look-at-typing-avatar/look-at-typing-avatar.lsl |
@@ -0,0 +1,533 @@ |
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// |
// This project makes Corrade, the Second Life / OpenSim bot look at the |
// avatars typing in local chat. You can find more details about the bot |
// at the URL: http://grimore.org/secondlife/scripted_agents/corrade |
// |
// The script works in combination with a "configuration" notecard that |
// must be placed in the same primitive as this script. The purpose of this |
// script is to show setting and disposing of viewer effects with Corrade |
// and you are free to use, change, and commercialize it under the terms |
// of the GNU/GPLv3 license at: http://www.gnu.org/licenses/gpl.html |
// |
/////////////////////////////////////////////////////////////////////////// |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueGet(string k, string data) { |
if(llStringLength(data) == 0) return ""; |
if(llStringLength(k) == 0) return ""; |
list a = llParseString2List(data, ["&", "="], []); |
integer i = llListFindList(a, [ k ]); |
if(i != -1) return llList2String(a, i+1); |
return ""; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueEncode(list data) { |
list k = llList2ListStrided(data, 0, -1, 2); |
list v = llList2ListStrided(llDeleteSubList(data, 0, 0), 0, -1, 2); |
data = []; |
do { |
data += llList2String(k, 0) + "=" + llList2String(v, 0); |
k = llDeleteSubList(k, 0, 0); |
v = llDeleteSubList(v, 0, 0); |
} while(llGetListLength(k) != 0); |
return llDumpList2String(data, "&"); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// escapes a string in conformance with RFC1738 |
string wasURLEscape(string i) { |
string o = ""; |
do { |
string c = llGetSubString(i, 0, 0); |
i = llDeleteSubString(i, 0, 0); |
if(c == "") jump continue; |
if(c == " ") { |
o += "+"; |
jump continue; |
} |
if(c == "\n") { |
o += "%0D" + llEscapeURL(c); |
jump continue; |
} |
o += llEscapeURL(c); |
@continue; |
} while(i != ""); |
return o; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// unescapes a string in conformance with RFC1738 |
string wasURLUnescape(string i) { |
return llUnescapeURL( |
llDumpList2String( |
llParseString2List( |
llDumpList2String( |
llParseString2List( |
i, |
["+"], |
[] |
), |
" " |
), |
["%0D%0A"], |
[] |
), |
"\n" |
) |
); |
} |
|
// corrade data |
key CORRADE = NULL_KEY; |
string GROUP = ""; |
string PASSWORD = ""; |
// the look UUID |
key LOOK = NULL_KEY; |
// the effect to use |
string EFFECT = "look"; |
key EFFECT_UUID = NULL_KEY; |
string EFFECT_TYPE = ""; |
integer EFFECT_DURATION = 10; |
|
// for holding the callback URL |
string callback = ""; |
|
// for notecard reading |
integer line = 0; |
|
// key-value data will be read into this list |
list tuples = []; |
|
// the name to look at |
string firstName = ""; |
string lastName = ""; |
|
default { |
state_entry() { |
if(llGetInventoryType("configuration") != INVENTORY_NOTECARD) { |
llOwnerSay("Sorry, could not find a configuration inventory notecard."); |
return; |
} |
// DEBUG |
llOwnerSay("Reading configuration file..."); |
llGetNotecardLine("configuration", line); |
} |
dataserver(key id, string data) { |
if(data == EOF) { |
// invariant, length(tuples) % 2 == 0 |
if(llGetListLength(tuples) % 2 != 0) { |
llOwnerSay("Error in configuration notecard."); |
return; |
} |
CORRADE = llList2Key( |
tuples, |
llListFindList( |
tuples, |
[ |
"corrade" |
] |
) |
+1 |
); |
if(CORRADE == NULL_KEY) { |
llOwnerSay("Error in configuration notecard: corrade"); |
return; |
} |
GROUP = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"group" |
] |
) |
+1 |
); |
if(GROUP == "") { |
llOwnerSay("Error in configuration notecard: group"); |
return; |
} |
PASSWORD = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"password" |
] |
) |
+1 |
); |
if(PASSWORD == "") { |
llOwnerSay("Error in configuration notecard: password"); |
return; |
} |
EFFECT = llToLower( |
llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"effect" |
] |
) |
+1 |
) |
); |
if(EFFECT != "look" && EFFECT != "point") { |
llOwnerSay("Error in configuration notecard: effect"); |
return; |
} |
if(EFFECT == "look") { |
EFFECT_UUID = llList2Key( |
tuples, |
llListFindList( |
tuples, |
[ |
"lookUUID" |
] |
) |
+1 |
); |
if(EFFECT_UUID == NULL_KEY) { |
llOwnerSay("Error in configuration notecard: lookUUID"); |
return; |
} |
EFFECT_TYPE = "Focus"; |
} |
if(EFFECT == "point") { |
EFFECT_UUID = llList2Key( |
tuples, |
llListFindList( |
tuples, |
[ |
"pointUUID" |
] |
) |
+1 |
); |
if(EFFECT_UUID == NULL_KEY) { |
llOwnerSay("Error in configuration notecard: pointUUID"); |
return; |
} |
EFFECT_TYPE = "Select"; |
} |
EFFECT_DURATION = llList2Integer( |
tuples, |
llListFindList( |
tuples, |
[ |
"duration" |
] |
) |
+1 |
); |
if(EFFECT_DURATION == 0) { |
llOwnerSay("Error in configuration notecard: duration"); |
return; |
} |
// DEBUG |
llOwnerSay("Read configuration notecard..."); |
state url; |
} |
if(data == "") jump continue; |
integer i = llSubStringIndex(data, "#"); |
if(i != -1) data = llDeleteSubString(data, i, -1); |
list o = llParseString2List(data, ["="], []); |
// get rid of starting and ending quotes |
string k = llDumpList2String( |
llParseString2List( |
llStringTrim( |
llList2String( |
o, |
0 |
), |
STRING_TRIM), |
["\""], [] |
), "\""); |
string v = llDumpList2String( |
llParseString2List( |
llStringTrim( |
llList2String( |
o, |
1 |
), |
STRING_TRIM), |
["\""], [] |
), "\""); |
if(k == "" || v == "") jump continue; |
tuples += k; |
tuples += v; |
@continue; |
llGetNotecardLine("configuration", ++line); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state url { |
state_entry() { |
// DEBUG |
llOwnerSay("Requesting URL..."); |
llRequestURL(); |
} |
http_request(key id, string method, string body) { |
if(method != URL_REQUEST_GRANTED) return; |
callback = body; |
// DEBUG |
llOwnerSay("Got URL..."); |
state detect; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state detect { |
state_entry() { |
// DEBUG |
llOwnerSay("Detecting if Corrade is online..."); |
llSetTimerEvent(5); |
} |
timer() { |
llRequestAgentData((key)CORRADE, DATA_ONLINE); |
} |
dataserver(key id, string data) { |
if(data != "1") { |
// DEBUG |
llOwnerSay("Corrade is not online, sleeping..."); |
llSetTimerEvent(30); |
return; |
} |
state notify; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state notify { |
state_entry() { |
// DEBUG |
llOwnerSay("Binding to the typing notification for local chat..."); |
llInstantMessage( |
(key)CORRADE, |
wasKeyValueEncode( |
[ |
"command", "notify", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"action", "set", |
"type", "typing", |
"URL", wasURLEscape(callback), |
"callback", wasURLEscape(callback) |
] |
) |
); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) != "notify") return; |
if(wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Failed to bind to the typing notification..."); |
state sense; |
} |
// DEBUG |
llOwnerSay("Typing notification installed..."); |
state sense; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state sense { |
state_entry() { |
// DEBUG |
llOwnerSay("Waiting for typing messages..."); |
} |
timer() { |
llRequestAgentData((key)CORRADE, DATA_ONLINE); |
} |
dataserver(key id, string data) { |
if(data == "1") return; |
// DEBUG |
llOwnerSay("Corrade is not online, sleeping..."); |
// Switch to detect loop and wait there for Corrade to come online. |
state detect; |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
firstName = wasURLUnescape( |
wasKeyValueGet( |
"firstname", |
body |
) |
); |
lastName = wasURLUnescape( |
wasKeyValueGet( |
"lastname", |
body |
) |
); |
state delete; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state delete { |
state_entry() { |
llInstantMessage( |
(key)CORRADE, wasKeyValueEncode( |
[ |
"command", "deleteviewereffect", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"effect", EFFECT, |
"id", EFFECT_UUID, |
"callback", wasURLEscape(callback) |
] |
) |
); |
// alarm 10 |
llSetTimerEvent(10); |
} |
timer() { |
state effect; |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
state effect; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
|
state effect { |
state_entry() { |
llInstantMessage( |
(key)CORRADE, wasKeyValueEncode( |
[ |
"command", "setviewereffect", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"effect", EFFECT, |
"offset", ZERO_VECTOR, |
"firstname", firstName, |
"lastname", lastName, |
"type", EFFECT_TYPE, |
"id", EFFECT_UUID, |
"callback", wasURLEscape(callback) |
] |
) |
); |
// alarm 10 |
llSetTimerEvent(10); |
} |
timer() { |
state sense; |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) |
!= "setviewereffect") return; |
if(wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Failed to look at..."); |
state sense; |
} |
state wait; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
|
state wait { |
state_entry() { |
llSetTimerEvent(EFFECT_DURATION); |
} |
timer() { |
llSetTimerEvent(0); |
llInstantMessage( |
(key)CORRADE, wasKeyValueEncode( |
[ |
"command", "deleteviewereffect", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"effect", EFFECT, |
"id", EFFECT_UUID |
] |
) |
); |
state sense; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
/multiple-group-notices/multiple-group-notices.lsl |
@@ -0,0 +1,399 @@ |
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) Wizardry and Steamworks 2013 - License: GNU GPLv3 // |
// Please see: http://www.gnu.org/licenses/gpl.html for legal details, // |
// rights of fair usage, the disclaimer and warranty conditions. // |
/////////////////////////////////////////////////////////////////////////// |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueEncode(list data) { |
list k = llList2ListStrided(data, 0, -1, 2); |
list v = llList2ListStrided(llDeleteSubList(data, 0, 0), 0, -1, 2); |
data = []; |
do { |
data += llList2String(k, 0) + "=" + llList2String(v, 0); |
k = llDeleteSubList(k, 0, 0); |
v = llDeleteSubList(v, 0, 0); |
} while(llGetListLength(k) != 0); |
return llDumpList2String(data, "&"); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueGet(string k, string data) { |
if(llStringLength(data) == 0) return ""; |
if(llStringLength(k) == 0) return ""; |
list a = llParseString2List(data, ["&", "="], []); |
integer i = llListFindList(a, [ k ]); |
if(i != -1) return llList2String(a, i+1); |
return ""; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// escapes a string in conformance with RFC1738 |
string wasURLEscape(string i) { |
string o = ""; |
do { |
string c = llGetSubString(i, 0, 0); |
i = llDeleteSubString(i, 0, 0); |
if(c == "") jump continue; |
if(c == " ") { |
o += "+"; |
jump continue; |
} |
if(c == "\n") { |
o += "%0D" + llEscapeURL(c); |
jump continue; |
} |
o += llEscapeURL(c); |
@continue; |
} while(i != ""); |
return o; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// unescapes a string in conformance with RFC1738 |
string wasURLUnescape(string i) { |
return llUnescapeURL( |
llDumpList2String( |
llParseString2List( |
llDumpList2String( |
llParseString2List( |
i, |
["+"], |
[] |
), |
" " |
), |
["%0D%0A"], |
[] |
), |
"\n" |
) |
); |
} |
|
// corrade data |
string CORRADE = ""; |
list GROUPS = []; |
list PASSWORDS = []; |
// holds the original count for notice to send |
integer notices = 0; |
// subject and message for notices |
string SUBJECT = ""; |
string MESSAGE = ""; |
|
// holds the current group and password |
string group = ""; |
string password = ""; |
|
// store URL |
string URL = ""; |
|
// for notecard reading |
integer line = 0; |
|
// key-value data will be read into this list |
list tuples = []; |
|
default { |
state_entry() { |
if(llGetInventoryType("configuration") != INVENTORY_NOTECARD) { |
llOwnerSay("Sorry, could not find an inventory notecard."); |
return; |
} |
// DEBUG |
llOwnerSay("Reading configuration file..."); |
llGetNotecardLine("configuration", line); |
} |
dataserver(key id, string data) { |
if(data == EOF) { |
// invariant, length(tuples) % 2 == 0 |
if(llGetListLength(tuples) % 2 != 0) { |
llOwnerSay("Error in configuration notecard."); |
return; |
} |
CORRADE = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"corrade" |
] |
) |
+1 |
); |
if(CORRADE == "") { |
llOwnerSay("Error in configuration notecard: corrade"); |
return; |
} |
|
SUBJECT = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"subject" |
] |
) |
+1 |
); |
if(SUBJECT == "") { |
llOwnerSay("Error in configuration notecard: subject"); |
return; |
} |
|
MESSAGE = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"message" |
] |
) |
+1 |
); |
if(MESSAGE == "") { |
llOwnerSay("Error in configuration notecard: message"); |
return; |
} |
|
// Retrieve groups. |
integer i = llGetListLength(tuples)-1; |
do { |
string n = llList2String(tuples, i); |
if(llSubStringIndex(n, "group_") == -1) jump skip_group; |
list l = llParseString2List(n, ["_"], []); |
if(llList2String(l, 0) != "group") jump skip_group; |
GROUPS += llList2String(tuples, i + 1); |
@skip_group; |
} while(--i>-1); |
|
// Retrieve passwords. |
i = llGetListLength(tuples)-1; |
do { |
string n = llList2String(tuples, i); |
if(llSubStringIndex(n, "password_") == -1) jump skip_password; |
list l = llParseString2List(n, ["_"], []); |
if(llList2String(l, 0) != "password") jump skip_password; |
PASSWORDS += llList2String(tuples, i + 1); |
@skip_password; |
} while(--i>-1); |
|
notices = llGetListLength(GROUPS); |
if(notices != llGetListLength(PASSWORDS)) { |
llOwnerSay("Error in configuration notecard: groups and passwords"); |
return; |
} |
|
// GC |
tuples = []; |
|
// DEBUG |
llOwnerSay("Read configuration file..."); |
state url; |
} |
if(data == "") jump continue; |
integer i = llSubStringIndex(data, "#"); |
if(i != -1) data = llDeleteSubString(data, i, -1); |
list o = llParseString2List(data, ["="], []); |
// get rid of starting and ending quotes |
string k = llDumpList2String( |
llParseString2List( |
llStringTrim( |
llList2String( |
o, |
0 |
), |
STRING_TRIM), |
["\""], [] |
), "\""); |
string v = llDumpList2String( |
llParseString2List( |
llStringTrim( |
llList2String( |
o, |
1 |
), |
STRING_TRIM), |
["\""], [] |
), "\""); |
if(k == "" || v == "") jump continue; |
tuples += k; |
tuples += v; |
@continue; |
llGetNotecardLine("configuration", ++line); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state url { |
state_entry() { |
llRequestURL(); |
} |
http_request(key id, string method, string body) { |
if(method != URL_REQUEST_GRANTED) { |
llOwnerSay("I cannot get any more URLs"); |
return; |
} |
URL = body; |
state ready; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state ready { |
state_entry() { |
llSetText("Touch to start sending notices...", <0, 1, 0>, 1.0); |
} |
touch_start(integer num) { |
state send_trampoline; |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, ""); |
llOwnerSay(wasURLUnescape(body)); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state send_trampoline { |
state_entry() { |
llSetText( |
"Notices [" + (string)llGetListLength(GROUPS) + "/" + (string)notices + "]", |
<1, 1, 0>, |
1.0 |
); |
llSetTimerEvent(1); |
} |
timer() { |
state send; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
|
state send { |
state_entry() { |
|
// re-entry / recursion |
if(llGetListLength(GROUPS) == 0 || |
llGetListLength(PASSWORDS) == 0) state done; |
|
group = llList2String(GROUPS, 0); |
GROUPS = llDeleteSubList(GROUPS, 0, 0); |
|
password = llList2String(PASSWORDS, 0); |
PASSWORDS = llDeleteSubList(PASSWORDS, 0, 0); |
|
llInstantMessage(CORRADE, |
wasKeyValueEncode( |
[ |
"command", "notice", |
"group", wasURLEscape(group), |
"password", wasURLEscape(password), |
"subject", wasURLEscape(SUBJECT), |
"message", wasURLEscape(MESSAGE), |
"callback", wasURLEscape(URL) |
] |
) |
); |
|
llSetTimerEvent(60); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, ""); |
if(wasKeyValueGet("success", body) != "True") { |
llSetText( |
"Could not send notice to group: " + |
group + |
"\n" + |
"Error: " + |
wasURLUnescape( |
wasKeyValueGet( |
"error", |
body |
) |
), |
<1, 0, 0>, |
1.0 |
); |
} |
state send_trampoline; |
} |
timer() { |
llSetText( |
"Timeout waiting for response from Corrade...", |
<1, 0, 0>, |
1.0 |
); |
state send_trampoline; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
|
state done { |
state_entry() { |
llSetText( |
"Notices sent! Touch for restart...", |
<0, 1, 0>, |
1.0 |
); |
} |
touch_start(integer num) { |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
/polygon-patrol-movement/polygon-patrol-movement.lsl |
@@ -0,0 +1,557 @@ |
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// |
// This is an automatic teleporter, and patrol script for the Corrade |
// Second Life / OpenSim bot. You can find more details about the bot |
// by following the URL: http://was.fm/secondlife/scripted_agents/corrade |
// |
// The purpose of this script is to demonstrate patroling with Corrade and |
// you are free to use, change, and commercialize it under the GNU/GPLv3 |
// license which can be found at: http://www.gnu.org/licenses/gpl.html |
// |
/////////////////////////////////////////////////////////////////////////// |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueGet(string k, string data) { |
if(llStringLength(data) == 0) return ""; |
if(llStringLength(k) == 0) return ""; |
list a = llParseString2List(data, ["&", "="], []); |
integer i = llListFindList(a, [ k ]); |
if(i != -1) return llList2String(a, i+1); |
return ""; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueEncode(list data) { |
list k = llList2ListStrided(data, 0, -1, 2); |
list v = llList2ListStrided(llDeleteSubList(data, 0, 0), 0, -1, 2); |
data = []; |
do { |
data += llList2String(k, 0) + "=" + llList2String(v, 0); |
k = llDeleteSubList(k, 0, 0); |
v = llDeleteSubList(v, 0, 0); |
} while(llGetListLength(k) != 0); |
return llDumpList2String(data, "&"); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
list wasDualQuicksort(list a, list b) { |
if(llGetListLength(a) <= 1) return a+b; |
|
float pivot_a = llList2Float(a, 0); |
a = llDeleteSubList(a, 0, 0); |
vector pivot_b = llList2Vector(b, 0); |
b = llDeleteSubList(b, 0, 0); |
|
list less = []; |
list less_b = []; |
list more = []; |
list more_b = []; |
|
do { |
if(llList2Float(a, 0) > pivot_a) { |
less += llList2List(a, 0, 0); |
less_b += llList2List(b, 0, 0); |
jump continue; |
} |
more += llList2List(a, 0, 0); |
more_b += llList2List(b, 0, 0); |
@continue; |
a = llDeleteSubList(a, 0, 0); |
b = llDeleteSubList(b, 0, 0); |
} while(llGetListLength(a)); |
return wasDualQuicksort(less, less_b) + [ pivot_a ] + [ pivot_b ] + wasDualQuicksort(more, more_b); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// determines whether the segment AB intersects the segment CD |
integer wasSegmentIntersect(vector A, vector B, vector C, vector D) { |
vector s1 = <B.x - A.x, B.y - A.y, B.z - A.z>; |
vector s2 = <D.x - C.x, D.y - C.y, D.y - C.z>; |
|
float d = (s1.x * s2.y -s2.x * s1.y); |
|
if(d == 0) return FALSE; |
|
float s = (s1.x * (A.y - C.y) - s1.y * (A.x - C.x)) / d; |
float t = (s2.x * (A.y - C.y) - s2.y * (A.x - C.x)) / d; |
|
// intersection at <A.x + (t * s1.x), A.y + (t * s1.y), A.z + (t * s1.z)>; |
return (integer)(s >= 0 && s <= 1 && t >= 0 && t <= 1 && |
A.z + t*(B.z - A.z) == C.z + s*(D.z - C.z)); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // |
// www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html // |
/////////////////////////////////////////////////////////////////////////// |
integer wasPointInPolygon(vector p, list polygon) { |
integer inside = FALSE; |
integer i = 0; |
integer nvert = llGetListLength(polygon); |
integer j = nvert-1; |
do { |
vector pi = llList2Vector(polygon, i); |
vector pj = llList2Vector(polygon, j); |
if ((pi.y > p.y) != (pj.y > p.y)) |
if(p.x < (pj.x - pi.x) * (p.y - pi.y) / (pj.y - pi.y) + pi.x) |
inside = !inside; |
j = i++; |
} while(i<nvert); |
return inside; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // |
// www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html // |
/////////////////////////////////////////////////////////////////////////// |
list wasPointToPolygon(list polygon, vector point) { |
integer i = llGetListLength(polygon)-1; |
list l = []; |
do { |
l = llListInsertList(l, (list)llVecDist(point, llList2Vector(polygon, i)), 0); |
} while(--i>-1); |
l = wasDualQuicksort(l, polygon); |
return [llList2Float(l, 0), llList2Vector(l, 1)]; |
|
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // |
// www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html // |
/////////////////////////////////////////////////////////////////////////// |
vector wasPolygonCentroid(list polygon, vector start, float tollerance, integer power) { |
// calculate the distance to the point farthest away from the start. |
list wpf = wasPointToPolygon(polygon, start); |
float dist = llList2Float(wpf, 0); |
vector next = llList2Vector(wpf, 1); |
|
// now calculate the next jump point |
next = start + ((dist/power)/dist) * (next-start); |
|
// if it falls withing the tollerance range, return it; |
if(llVecMag(start-next) < tollerance) return next; |
return wasPolygonCentroid(polygon, next, tollerance, power*power); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2011 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
vector wasCirclePoint(float radius) { |
float x = llPow(-1, 1 + (integer) llFrand(2)) * llFrand(radius*2); |
float y = llPow(-1, 1 + (integer) llFrand(2)) * llFrand(radius*2); |
if(llPow(x,2) + llPow(y,2) <= llPow(radius,2)) |
return <x, y, 0>; |
return wasCirclePoint(radius); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2011 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
vector wasPolygonPoint(list polygon) { |
vector c = wasPolygonCentroid(polygon, llList2Vector(polygon, 0), 0.05, 2); |
float r = llList2Float(wasPointToPolygon(polygon, c), 0); |
vector d; |
do { |
d = c + wasCirclePoint(r); |
} while(wasPointInPolygon(d, polygon) == FALSE); |
return d; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// determines whether the path between the current positon m and the |
// computed next position d will intersect any two sides of the polygon |
vector wasPolygonPath(vector m, list polygon) { |
integer c = llGetListLength(polygon) - 1; |
vector d = wasPolygonPoint(polygon); |
integer i = 0; |
do { |
vector s = llList2Vector(polygon, c); |
vector p = llList2Vector(polygon, c-1); |
// project in plane |
if(wasSegmentIntersect( |
<m.x, m.y, 0>, |
<d.x, d.y, 0>, |
<s.x, s.y, 0>, |
<p.x, p.y, 0>)) |
++i; |
} while(--c > 0); |
if(i > 1) return wasPolygonPath(m, polygon); |
return d; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// escapes a string in conformance with RFC1738 |
string wasURLEscape(string i) { |
string o = ""; |
do { |
string c = llGetSubString(i, 0, 0); |
i = llDeleteSubString(i, 0, 0); |
if(c == "") jump continue; |
if(c == " ") { |
o += "+"; |
jump continue; |
} |
if(c == "\n") { |
o += "%0D" + llEscapeURL(c); |
jump continue; |
} |
o += llEscapeURL(c); |
@continue; |
} while(i != ""); |
return o; |
} |
|
// corrade data |
string CORRADE = ""; |
string GROUP = ""; |
string PASSWORD = ""; |
float RADIUS = 0; |
float WAIT = 0; |
list POLYGON = []; |
|
// for holding Corrade's current location |
vector location = ZERO_VECTOR; |
|
// for holding the callback URL |
string callback = ""; |
|
// for notecard reading |
integer line = 0; |
|
// key-value data will be read into this list |
list tuples = []; |
|
default { |
state_entry() { |
if(llGetInventoryType("configuration") != INVENTORY_NOTECARD) { |
llOwnerSay("Sorry, could not find an inventory notecard."); |
return; |
} |
// DEBUG |
llOwnerSay("Reading configuration file..."); |
llGetNotecardLine("configuration", line); |
} |
dataserver(key id, string data) { |
if(data == EOF) { |
// invariant, length(tuples) % 2 == 0 |
if(llGetListLength(tuples) % 2 != 0) { |
llOwnerSay("Error in configuration notecard."); |
return; |
} |
CORRADE = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"corrade" |
] |
) |
+1); |
if(CORRADE == "") { |
llOwnerSay("Error in configuration notecard: corrade"); |
return; |
} |
GROUP = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"group" |
] |
) |
+1); |
if(GROUP == "") { |
llOwnerSay("Error in configuration notecard: group"); |
return; |
} |
PASSWORD = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"password" |
] |
) |
+1); |
if(PASSWORD == "") { |
llOwnerSay("Error in configuration notecard: password"); |
return; |
} |
|
// BEGIN POLYGON |
integer i = llGetListLength(tuples)-1; |
do { |
string n = llList2String(tuples, i); |
if(llSubStringIndex(n, "point_") != -1) { |
list l = llParseString2List(n, ["_"], []); |
if(llList2String(l, 0) == "point") { |
integer x = llList2Integer( |
l, |
1 |
)-1; |
// extend the polygon to the number of points |
while(llGetListLength(POLYGON) < x) |
POLYGON += ""; |
// and insert the point at the location |
POLYGON = llListReplaceList( |
POLYGON, |
(list)( |
(vector)( |
"<" + llList2CSV( |
llParseString2List( |
llList2String( |
tuples, |
llListFindList( |
tuples, |
(list)n |
) |
+1 |
), |
["<", ",", ">"], |
[] |
) |
) + ">") |
), |
x, |
x |
); |
} |
} |
} while(--i>-1); |
// now clean up any empty slots |
i = llGetListLength(POLYGON)-1; |
do { |
if(llList2String(POLYGON, i) == "") |
POLYGON = llDeleteSubList(POLYGON, i, i); |
} while(--i > -1); |
// END POLYGON |
|
WAIT = llList2Float( |
tuples, |
llListFindList( |
tuples, |
[ |
"wait" |
] |
) |
+1); |
if(WAIT == 0) { |
llOwnerSay("Error in configuration notecard: wait"); |
return; |
} |
// DEBUG |
llOwnerSay("Read configuration file..."); |
state url; |
} |
if(data == "") jump continue; |
integer i = llSubStringIndex(data, "#"); |
if(i != -1) data = llDeleteSubString(data, i, -1); |
list o = llParseString2List(data, ["="], []); |
// get rid of starting and ending quotes |
string k = llDumpList2String( |
llParseString2List( |
llStringTrim( |
llList2String( |
o, |
0 |
), |
STRING_TRIM), |
["\""], [] |
), "\""); |
string v = llDumpList2String( |
llParseString2List( |
llStringTrim( |
llList2String( |
o, |
1 |
), |
STRING_TRIM), |
["\""], [] |
), "\""); |
if(k == "" || v == "") jump continue; |
tuples += k; |
tuples += v; |
@continue; |
llGetNotecardLine("configuration", ++line); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state url { |
state_entry() { |
// DEBUG |
llOwnerSay("Requesting URL..."); |
llRequestURL(); |
} |
http_request(key id, string method, string body) { |
if(method != URL_REQUEST_GRANTED) return; |
callback = body; |
// DEBUG |
llOwnerSay("Got URL..."); |
state detect; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state detect { |
state_entry() { |
// DEBUG |
llOwnerSay("Detecting if Corrade is online..."); |
llSetTimerEvent(5); |
} |
timer() { |
llRequestAgentData((key)CORRADE, DATA_ONLINE); |
} |
dataserver(key id, string data) { |
if(data != "1") { |
// DEBUG |
llOwnerSay("Corrade is not online, sleeping..."); |
llSetTimerEvent(30); |
return; |
} |
llSensor("", (key)CORRADE, AGENT, 10, TWO_PI); |
} |
no_sensor() { |
// DEBUG |
llOwnerSay("Teleporting Corrade..."); |
llInstantMessage((key)CORRADE, |
wasKeyValueEncode( |
[ |
"command", "teleport", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"entity", "region", |
"region", wasURLEscape(llGetRegionName()), |
"position", wasURLEscape( |
(string)( |
llGetPos() + wasCirclePoint(RADIUS) |
) |
), |
"callback", callback |
] |
) |
); |
llSensorRepeat("", (key)CORRADE, AGENT, 10, TWO_PI, 60); |
} |
sensor(integer num) { |
llSetTimerEvent(0); |
state wander; |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) != "teleport" || |
wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Teleport failed..."); |
return; |
} |
llSetTimerEvent(0); |
state wander; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state wander { |
state_entry() { |
// DEBUG |
llOwnerSay("Wandering ready..."); |
// initialize location from current location |
location = llGetPos(); |
llSetTimerEvent(1 + llFrand(WAIT)); |
} |
timer() { |
llRequestAgentData((key)CORRADE, DATA_ONLINE); |
} |
dataserver(key id, string data) { |
if(data != "1") { |
// DEBUG |
llOwnerSay("Corrade is not online, sleeping..."); |
llResetScript(); |
return; |
} |
// DEBUG |
//llOwnerSay("Sending stop..."); |
llInstantMessage(CORRADE, |
wasKeyValueEncode( |
[ |
"command", "autopilot", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"action", "stop", |
"callback", wasURLEscape(callback) |
] |
) |
); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) != "autopilot" || |
wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Could not get Corrade to stop, restarting script..."); |
llResetScript(); |
} |
// DEBUG |
llOwnerSay("Sending next move..."); |
// get the next location |
location = wasPolygonPath(location, POLYGON); |
vector pos = llGetPos(); |
llInstantMessage(CORRADE, |
wasKeyValueEncode( |
[ |
"command", "autopilot", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"position", wasURLEscape( |
(string)(<location.x, location.y, pos.z>) |
), |
"action", "start" |
] |
) |
); |
llSetTimerEvent(1 + llFrand(WAIT)); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
/punish-bad-words/punish-bad-words.lsl |
@@ -0,0 +1,495 @@ |
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// |
// This is an automatic teleporter, sitter and animator for the Corrade |
// Second Life / OpenSim bot. You can find more details about the bot |
// by following the URL: http://was.fm/secondlife/scripted_agents/corrade |
// |
// The sit script works together with a "configuration" notecard and an |
// animation that must both be placed in the same primitive as this script. |
// The purpose of this script is to demonstrate sitting with Corrade and |
// you are free to use, change, and commercialize it under the GNU/GPLv3 |
// license at: http://www.gnu.org/licenses/gpl.html |
// |
/////////////////////////////////////////////////////////////////////////// |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueGet(string k, string data) { |
if(llStringLength(data) == 0) return ""; |
if(llStringLength(k) == 0) return ""; |
list a = llParseString2List(data, ["&", "="], []); |
integer i = llListFindList(a, [ k ]); |
if(i != -1) return llList2String(a, i+1); |
return ""; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueEncode(list data) { |
list k = llList2ListStrided(data, 0, -1, 2); |
list v = llList2ListStrided(llDeleteSubList(data, 0, 0), 0, -1, 2); |
data = []; |
do { |
data += llList2String(k, 0) + "=" + llList2String(v, 0); |
k = llDeleteSubList(k, 0, 0); |
v = llDeleteSubList(v, 0, 0); |
} while(llGetListLength(k) != 0); |
return llDumpList2String(data, "&"); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
integer wasListCountExclude(list input, list exclude) { |
if(llGetListLength(input) == 0) return 0; |
if(llListFindList(exclude, (list)llList2String(input, 0)) == -1) |
return 1 + wasListCountExclude(llDeleteSubList(input, 0, 0), exclude); |
return wasListCountExclude(llDeleteSubList(input, 0, 0), exclude); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// escapes a string in conformance with RFC1738 |
string wasURLEscape(string i) { |
string o = ""; |
do { |
string c = llGetSubString(i, 0, 0); |
i = llDeleteSubString(i, 0, 0); |
if(c == "") jump continue; |
if(c == " ") { |
o += "+"; |
jump continue; |
} |
if(c == "\n") { |
o += "%0D" + llEscapeURL(c); |
jump continue; |
} |
o += llEscapeURL(c); |
@continue; |
} while(i != ""); |
return o; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// unescapes a string in conformance with RFC1738 |
string wasURLUnescape(string i) { |
return llUnescapeURL( |
llDumpList2String( |
llParseString2List( |
llDumpList2String( |
llParseString2List( |
i, |
["+"], |
[] |
), |
" " |
), |
["%0D%0A"], |
[] |
), |
"\n" |
) |
); |
} |
|
// corrade data |
string CORRADE = ""; |
string GROUP = ""; |
string PASSWORD = ""; |
string PUNISHMENT = ""; |
list SPLIT = []; |
list ANNOUNCE = []; |
|
// for holding the callback URL |
string callback = ""; |
|
// for notecard reading |
integer line = 0; |
|
// key-value data will be read into this list |
list tuples = []; |
// blacklisted words will be here |
list badwords = []; |
|
default { |
state_entry() { |
if(llGetInventoryType("configuration") != INVENTORY_NOTECARD) { |
llOwnerSay("Sorry, could not find a configuration inventory notecard."); |
return; |
} |
// DEBUG |
llOwnerSay("Reading configuration file..."); |
llGetNotecardLine("configuration", line); |
} |
dataserver(key id, string data) { |
if(data == EOF) { |
// invariant, length(tuples) % 2 == 0 |
if(llGetListLength(tuples) % 2 != 0) { |
llOwnerSay("Error in configuration notecard."); |
return; |
} |
CORRADE = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"corrade" |
] |
) |
+1); |
if(CORRADE == "") { |
llOwnerSay("Error in configuration notecard: corrade"); |
return; |
} |
GROUP = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"group" |
] |
) |
+1); |
if(GROUP == "") { |
llOwnerSay("Error in configuration notecard: password"); |
return; |
} |
PASSWORD = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"password" |
] |
) |
+1); |
if(PASSWORD == "") { |
llOwnerSay("Error in configuration notecard: password"); |
return; |
} |
PUNISHMENT = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"punishment" |
] |
) |
+1); |
if(PUNISHMENT == "") { |
llOwnerSay("Error in configuration notecard: punishment"); |
return; |
} |
string split = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"split" |
] |
) |
+1 |
); |
do { |
SPLIT += llGetSubString(split, 0, 0); |
split = llDeleteSubString(split, 0, 0); |
} while(llStringLength(split) != 0); |
if(SPLIT == []) { |
llOwnerSay("Error in configuration notecard: split"); |
return; |
} |
ANNOUNCE = llCSV2List( |
llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"announce" |
] |
) |
+1 |
) |
); |
if(ANNOUNCE == []) { |
llOwnerSay("Error in configuration notecard: announce"); |
return; |
} |
// DEBUG |
llOwnerSay("Read configuration notecard..."); |
state words; |
} |
if(data == "") jump continue; |
integer i = llSubStringIndex(data, "#"); |
if(i != -1) data = llDeleteSubString(data, i, -1); |
list o = llParseString2List(data, ["="], []); |
// get rid of starting and ending quotes |
string k = llDumpList2String( |
llParseString2List( |
llStringTrim( |
llList2String( |
o, |
0 |
), |
STRING_TRIM), |
["\""], [] |
), "\""); |
string v = llDumpList2String( |
llParseString2List( |
llStringTrim( |
llList2String( |
o, |
1 |
), |
STRING_TRIM), |
["\""], [] |
), "\""); |
if(k == "" || v == "") jump continue; |
tuples += k; |
tuples += v; |
@continue; |
llGetNotecardLine("configuration", ++line); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state words { |
state_entry() { |
if(llGetInventoryType("badwords") != INVENTORY_NOTECARD) { |
llOwnerSay("Sorry, could not find a blacklist inventory notecard."); |
return; |
} |
// DEBUG |
llOwnerSay("Reading badwords notecard..."); |
line = 0; |
llGetNotecardLine("badwords", line); |
} |
dataserver(key id, string data) { |
if(data == EOF) { |
// DEBUG |
llOwnerSay("Read badwords notcard..."); |
state url; |
} |
if(data == "") jump continue; |
badwords += data; |
@continue; |
llGetNotecardLine("badwords", ++line); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state url { |
state_entry() { |
// DEBUG |
llOwnerSay("Requesting URL..."); |
llRequestURL(); |
} |
http_request(key id, string method, string body) { |
if(method != URL_REQUEST_GRANTED) return; |
callback = body; |
// DEBUG |
llOwnerSay("Got URL..."); |
state detect; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state detect { |
state_entry() { |
// DEBUG |
llOwnerSay("Detecting if Corrade is online..."); |
llSetTimerEvent(5); |
} |
timer() { |
llRequestAgentData((key)CORRADE, DATA_ONLINE); |
} |
dataserver(key id, string data) { |
if(data != "1") { |
// DEBUG |
llOwnerSay("Corrade is not online, sleeping..."); |
llSetTimerEvent(30); |
return; |
} |
state notify; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state notify { |
state_entry() { |
// DEBUG |
llOwnerSay("Binding to the group chat notification..."); |
llInstantMessage( |
(key)CORRADE, |
wasKeyValueEncode( |
[ |
"command", "notify", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"action", "set", |
"type", "group", |
"URL", wasURLEscape(callback), |
"callback", wasURLEscape(callback) |
] |
) |
); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) != "notify" || |
wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Failed to bind to the group chat notification..."); |
state detect; |
} |
// DEBUG |
llOwnerSay("Permission notification installed..."); |
state main; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state main { |
state_entry() { |
// DEBUG |
llOwnerSay("Waiting for badwords..."); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
|
// split the input |
list input = llParseString2List( |
wasURLUnescape( |
wasKeyValueGet( |
"message", |
body |
) |
), |
SPLIT, []); |
|
// now find badwords |
string badword = ""; |
do { |
badword = llList2String(input, 0); |
if(llListFindList(badwords, (list)badword) != -1) jump punish; |
input = llDeleteSubList(input, 0, 0); |
} while(llGetListLength(input) != 0); |
return; |
|
@punish; |
|
string firstname = wasURLUnescape(wasKeyValueGet("firstname", body)); |
string lastname = wasURLUnescape(wasKeyValueGet("lastname", body)); |
|
if(PUNISHMENT == "eject") { |
llOwnerSay("Ejecting: " + firstname + " " + lastname); |
llInstantMessage((key)CORRADE, |
wasKeyValueEncode( |
[ |
"command", "eject", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"firstname", wasURLEscape(firstname), |
"lastname", wasURLEscape(lastname) |
] |
) |
); |
jump announce; |
} |
|
llOwnerSay("Muting: " + firstname + " " + lastname); |
llInstantMessage((key)CORRADE, |
wasKeyValueEncode( |
[ |
"command", "moderate", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"firstname", wasURLEscape(firstname), |
"lastname", wasURLEscape(lastname), |
"type", "text", |
"silence", "true" |
] |
) |
); |
|
@announce; |
|
// Go through the list of avatars to announce and |
// tell them who has been ajected and for what word |
integer i = llGetListLength(ANNOUNCE)-1; |
do { |
string full = llList2String(ANNOUNCE, i); |
list name = llParseString2List(full, [" "], []); |
llInstantMessage((key)CORRADE, |
wasKeyValueEncode( |
[ |
"command", "tell", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"entity", "avatar", |
"firstname", wasURLEscape(llList2String(name, 0)), |
"lastname", wasURLEscape(llList2String(name, 1)), |
"message", wasURLEscape( |
"The avatar " + |
firstname + |
" " + |
lastname + |
" was ejecteded from: " + |
GROUP + " for saying: \"" + |
badword + "\"." |
) |
] |
) |
); |
} while(--i>-1); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
/puppeteer-pathfinding/puppeteer-pathfinding.lsl |
@@ -0,0 +1,534 @@ |
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// |
// This is a puppeteer script for the Corrade Second Life / OpenSim bot |
// that, given a set of local coordinates, will make the bot traverse a |
// path while also minding collisions with object. You can find more |
// details about the Corrade bot and how to get it to work on your machine |
// by following the URL: http://grimore.org/secondlife/scripted_agents/corrade |
// |
// This script works together with a "configuration" notecard that must |
// be placed in the same primitive as this script. The purpose of this |
// script is to demonstrate how Corrade can be made to walk on a path and |
// you are free to use, change, and commercialize it under the GNU/GPLv3 |
// license at: http://www.gnu.org/licenses/gpl.html |
// |
/////////////////////////////////////////////////////////////////////////// |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueGet(string k, string data) { |
if(llStringLength(data) == 0) return ""; |
if(llStringLength(k) == 0) return ""; |
list a = llParseString2List(data, ["&", "="], []); |
integer i = llListFindList(a, [ k ]); |
if(i != -1) return llList2String(a, i+1); |
return ""; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueEncode(list data) { |
list k = llList2ListStrided(data, 0, -1, 2); |
list v = llList2ListStrided(llDeleteSubList(data, 0, 0), 0, -1, 2); |
data = []; |
do { |
data += llList2String(k, 0) + "=" + llList2String(v, 0); |
k = llDeleteSubList(k, 0, 0); |
v = llDeleteSubList(v, 0, 0); |
} while(llGetListLength(k) != 0); |
return llDumpList2String(data, "&"); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// escapes a string in conformance with RFC1738 |
string wasURLEscape(string i) { |
string o = ""; |
do { |
string c = llGetSubString(i, 0, 0); |
i = llDeleteSubString(i, 0, 0); |
if(c == "") jump continue; |
if(c == " ") { |
o += "+"; |
jump continue; |
} |
if(c == "\n") { |
o += "%0D" + llEscapeURL(c); |
jump continue; |
} |
o += llEscapeURL(c); |
@continue; |
} while(i != ""); |
return o; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// unescapes a string in conformance with RFC1738 |
string wasURLUnescape(string i) { |
return llUnescapeURL( |
llDumpList2String( |
llParseString2List( |
llDumpList2String( |
llParseString2List( |
i, |
["+"], |
[] |
), |
" " |
), |
["%0D%0A"], |
[] |
), |
"\n" |
) |
); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
list wasCSVToList(string csv) { |
list l = []; |
list s = []; |
string m = ""; |
do { |
string a = llGetSubString(csv, 0, 0); |
csv = llDeleteSubString(csv, 0, 0); |
if(a == ",") { |
if(llList2String(s, -1) != "\"") { |
l += m; |
m = ""; |
jump continue; |
} |
m += a; |
jump continue; |
} |
if(a == "\"" && llGetSubString(csv, 0, 0) == a) { |
m += a; |
csv = llDeleteSubString(csv, 0, 0); |
jump continue; |
} |
if(a == "\"") { |
if(llList2String(s, -1) != a) { |
s += a; |
jump continue; |
} |
s = llDeleteSubList(s, -1, -1); |
jump continue; |
} |
m += a; |
@continue; |
} while(csv != ""); |
// postcondition: length(s) = 0 |
return l + m; |
} |
|
// corrade data |
key CORRADE = NULL_KEY; |
string GROUP = ""; |
string PASSWORD = ""; |
list PATH = []; |
float PAUSE = 0; |
integer RANDOMIZE = FALSE; |
|
// for holding the callback URL |
string callback = ""; |
|
// for notecard reading |
integer line = 0; |
|
// key-value data will be read into this list |
list tuples = []; |
// stores COrrade's current position |
vector origin = ZERO_VECTOR; |
|
default { |
state_entry() { |
if(llGetInventoryType("configuration") != INVENTORY_NOTECARD) { |
llOwnerSay("Sorry, could not find a configuration inventory notecard."); |
return; |
} |
// DEBUG |
llOwnerSay("Reading configuration file..."); |
llGetNotecardLine("configuration", line); |
} |
dataserver(key id, string data) { |
if(data == EOF) { |
// invariant, length(tuples) % 2 == 0 |
if(llGetListLength(tuples) % 2 != 0) { |
llOwnerSay("Error in configuration notecard."); |
return; |
} |
CORRADE = llList2Key( |
tuples, |
llListFindList( |
tuples, |
[ |
"corrade" |
] |
) |
+1); |
if(CORRADE == NULL_KEY) { |
llOwnerSay("Error in configuration notecard: corrade"); |
return; |
} |
GROUP = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"group" |
] |
) |
+1); |
if(GROUP == "") { |
llOwnerSay("Error in configuration notecard: group"); |
return; |
} |
PASSWORD = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"password" |
] |
) |
+1); |
if(PASSWORD == "") { |
llOwnerSay("Error in configuration notecard: password"); |
return; |
} |
PATH = llCSV2List( |
llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"path" |
] |
) |
+1) |
); |
if(PATH == []) { |
llOwnerSay("Error in configuration notecard: points"); |
return; |
} |
PAUSE = llList2Float( |
tuples, |
llListFindList( |
tuples, |
[ |
"pause" |
] |
) |
+1); |
if(PAUSE == 0) { |
llOwnerSay("Error in configuration notecard: pause"); |
return; |
} |
string boolean = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"randomize" |
] |
) |
+1); |
if(llToLower(boolean) == "true") RANDOMIZE = TRUE; |
// DEBUG |
llOwnerSay("Read configuration notecard..."); |
state url; |
} |
if(data == "") jump continue; |
integer i = llSubStringIndex(data, "#"); |
if(i != -1) data = llDeleteSubString(data, i, -1); |
list o = llParseString2List(data, ["="], []); |
// get rid of starting and ending quotes |
string k = llDumpList2String( |
llParseString2List( |
llStringTrim( |
llList2String( |
o, |
0 |
), |
STRING_TRIM), |
["\""], [] |
), "\""); |
string v = llDumpList2String( |
llParseString2List( |
llStringTrim( |
llList2String( |
o, |
1 |
), |
STRING_TRIM), |
["\""], [] |
), "\""); |
if(k == "" || v == "") jump continue; |
tuples += k; |
tuples += v; |
@continue; |
llGetNotecardLine("configuration", ++line); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state url { |
state_entry() { |
// DEBUG |
llOwnerSay("Requesting URL..."); |
llRequestURL(); |
} |
http_request(key id, string method, string body) { |
if(method != URL_REQUEST_GRANTED) return; |
callback = body; |
// DEBUG |
llOwnerSay("Got URL..."); |
state detect; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state detect { |
state_entry() { |
// DEBUG |
llOwnerSay("Detecting if Corrade is online..."); |
llSetTimerEvent(5); |
} |
timer() { |
llRequestAgentData((key)CORRADE, DATA_ONLINE); |
} |
dataserver(key id, string data) { |
if(data != "1") { |
// DEBUG |
llOwnerSay("Corrade is not online, sleeping..."); |
llSetTimerEvent(30); |
return; |
} |
state notify; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state notify { |
state_entry() { |
// DEBUG |
llOwnerSay("Binding to the collision notification..."); |
llInstantMessage( |
(key)CORRADE, |
wasKeyValueEncode( |
[ |
"command", "notify", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"action", "set", |
"type", "collision", |
"URL", wasURLEscape(callback), |
"callback", wasURLEscape(callback) |
] |
) |
); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) != "notify" || |
wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Failed to bind to the collisioin notification..."); |
state detect; |
} |
// DEBUG |
llOwnerSay("Collision notification installed..."); |
state pause; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state pause { |
state_entry() { |
//DEBUG |
llOwnerSay("Pausing..."); |
// Check whether Corrade is still online first. |
llRequestAgentData((key)CORRADE, DATA_ONLINE); |
} |
dataserver(key id, string data) { |
if(data != "1") { |
// DEBUG |
llOwnerSay("Corrade is not online, sleeping..."); |
llSetTimerEvent(30); |
return; |
} |
// Corrade is online, so schedule the next walk. |
if(RANDOMIZE) { |
// The minimal trigger time for a timer event is ~0.045s |
// This ensures we do not end up stuck in the pause state. |
llSetTimerEvent(0.045 + llFrand(PAUSE - 0.045)); |
return; |
} |
llSetTimerEvent(PAUSE); |
} |
timer() { |
llSetTimerEvent(0); |
state find; |
} |
} |
|
state find { |
state_entry() { |
// We now query Corrade for its current position. |
llInstantMessage(CORRADE, |
wasKeyValueEncode( |
[ |
"command", "getselfdata", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"data", "SimPosition", |
"callback", wasURLEscape(callback) |
] |
) |
); |
// alarm 60 for Corrade not responding |
llSetTimerEvent(60); |
} |
timer() { |
llSetTimerEvent(0); |
// DEBUG |
llOwnerSay("Corrade not responding to data query..."); |
state pause; |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
list data = wasCSVToList( |
wasKeyValueGet( |
"data", |
wasURLUnescape(body) |
) |
); |
origin= (vector)llList2String( |
data, |
llListFindList( |
data, |
(list)"SimPosition" |
)+1 |
); |
state walk; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state walk { |
state_entry() { |
// DEBUG |
llOwnerSay("Walking..."); |
|
// extract next destination and permute the set |
vector next = (vector)llList2String(PATH, 0); |
PATH = llDeleteSubList(PATH, 0, 0); |
PATH += next; |
|
llInstantMessage(CORRADE, |
wasKeyValueEncode( |
[ |
"command", "autopilot", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"position", next, |
"action", "start" |
] |
) |
); |
// We now determine the waiting time for Corrade to reach |
// its next destination by extracting time as a function |
// of the distance it has to walk and the speed of travel: |
// t = s / v |
// This, of course, is prone to error since the distance |
// is calculated on the shortest direct path. Nevertheless, |
// it is a pretty good appoximation for terrain that is |
// mostly flat and without too many curvatures. |
// NB. 3.20 m/s is the walking speed of an avatar. |
llSetTimerEvent(llVecDist(origin, next)/3.20); |
} |
http_request(key id, string method, string body) { |
// since we have bound to the collision notification, |
// this region of code deals with Corrade colliding |
// with in-world assets; in which case, we stop |
// moving to not seem awkward |
|
// DEBUG |
llOwnerSay("Collided..."); |
|
llHTTPResponse(id, 200, "OK"); |
|
llInstantMessage(CORRADE, |
wasKeyValueEncode( |
[ |
"command", "autopilot", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"action", "stop" |
] |
) |
); |
// We did not reach our destination since we collided with |
// something on our path, so switch directly to waiting and |
// attempt to reach the next destination on our path. |
state pause; |
} |
timer() { |
// We most likely reached our destination, so switch to pause. |
llSetTimerEvent(0); |
state pause; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
/region-restart-evader/region-restart-evader.lsl |
@@ -0,0 +1,623 @@ |
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) Wizardry and Steamworks 2016 - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// |
// This project makes Corrade, the Second Life / OpenSim bot evade region |
// restarts by teleporting to other regions. More details about Corrade |
// can be found at the URL: |
// |
// http://grimore.org/secondlife/scripted_agents/corrade |
// |
// The script works in combination with a "configuration" notecard that |
// must be placed in the same primitive as this script. The purpose of this |
// script is to illustrate how region restarts can be evaded with Corrade |
// and you are free to use, change, and commercialize it under the terms |
// of the GNU/GPLv3 license at: http://www.gnu.org/licenses/gpl.html |
// |
/////////////////////////////////////////////////////////////////////////// |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueGet(string k, string data) { |
if(llStringLength(data) == 0) return ""; |
if(llStringLength(k) == 0) return ""; |
list a = llParseString2List(data, ["&", "="], []); |
integer i = llListFindList(a, [ k ]); |
if(i != -1) return llList2String(a, i+1); |
return ""; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueEncode(list data) { |
list k = llList2ListStrided(data, 0, -1, 2); |
list v = llList2ListStrided(llDeleteSubList(data, 0, 0), 0, -1, 2); |
data = []; |
do { |
data += llList2String(k, 0) + "=" + llList2String(v, 0); |
k = llDeleteSubList(k, 0, 0); |
v = llDeleteSubList(v, 0, 0); |
} while(llGetListLength(k) != 0); |
return llDumpList2String(data, "&"); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// escapes a string in conformance with RFC1738 |
string wasURLEscape(string i) { |
string o = ""; |
do { |
string c = llGetSubString(i, 0, 0); |
i = llDeleteSubString(i, 0, 0); |
if(c == "") jump continue; |
if(c == " ") { |
o += "+"; |
jump continue; |
} |
if(c == "\n") { |
o += "%0D" + llEscapeURL(c); |
jump continue; |
} |
o += llEscapeURL(c); |
@continue; |
} while(i != ""); |
return o; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// unescapes a string in conformance with RFC1738 |
string wasURLUnescape(string i) { |
return llUnescapeURL( |
llDumpList2String( |
llParseString2List( |
llDumpList2String( |
llParseString2List( |
i, |
["+"], |
[] |
), |
" " |
), |
["%0D%0A"], |
[] |
), |
"\n" |
) |
); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
list wasCSVToList(string csv) { |
list l = []; |
list s = []; |
string m = ""; |
do { |
string a = llGetSubString(csv, 0, 0); |
csv = llDeleteSubString(csv, 0, 0); |
if(a == ",") { |
if(llList2String(s, -1) != "\"") { |
l += m; |
m = ""; |
jump continue; |
} |
m += a; |
jump continue; |
} |
if(a == "\"" && llGetSubString(csv, 0, 0) == a) { |
m += a; |
csv = llDeleteSubString(csv, 0, 0); |
jump continue; |
} |
if(a == "\"") { |
if(llList2String(s, -1) != a) { |
s += a; |
jump continue; |
} |
s = llDeleteSubList(s, -1, -1); |
jump continue; |
} |
m += a; |
@continue; |
} while(csv != ""); |
// postcondition: length(s) = 0 |
return l + m; |
} |
|
// corrade data |
key CORRADE = NULL_KEY; |
string GROUP = ""; |
string PASSWORD = ""; |
|
// hold all the escape regions |
list REGIONS = []; |
// the home region name |
string HOME_REGION = ""; |
vector HOME_POSITION = ZERO_VECTOR; |
// holds the restat delay dynamically |
integer RESTART_DELAY = 0; |
|
// for holding the callback URL |
string callback = ""; |
|
// for notecard reading |
integer line = 0; |
|
// key-value data will be read into this list |
list tuples = []; |
|
// jump label |
string setjmp = ""; |
|
default { |
state_entry() { |
if(llGetInventoryType("configuration") != INVENTORY_NOTECARD) { |
llOwnerSay("Sorry, could not find a configuration inventory notecard."); |
return; |
} |
// DEBUG |
llOwnerSay("Reading configuration file..."); |
llGetNotecardLine("configuration", line); |
} |
dataserver(key id, string data) { |
if(data == EOF) { |
// invariant, length(tuples) % 2 == 0 |
if(llGetListLength(tuples) % 2 != 0) { |
llOwnerSay("Error in configuration notecard."); |
return; |
} |
CORRADE = llList2Key( |
tuples, |
llListFindList( |
tuples, |
[ |
"corrade" |
] |
) |
+1 |
); |
if(CORRADE == NULL_KEY) { |
llOwnerSay("Error in configuration notecard: corrade"); |
return; |
} |
GROUP = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"group" |
] |
) |
+1 |
); |
if(GROUP == "") { |
llOwnerSay("Error in configuration notecard: group"); |
return; |
} |
PASSWORD = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"password" |
] |
) |
+1 |
); |
if(PASSWORD == "") { |
llOwnerSay("Error in configuration notecard: password"); |
return; |
} |
REGIONS = wasCSVToList( |
llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"regions" |
] |
) |
+1 |
) |
); |
if(REGIONS == []) { |
llOwnerSay("Error in configuration notecard: regions"); |
return; |
} |
HOME_REGION = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"home" |
] |
) |
+1 |
); |
if(HOME_REGION == "") { |
llOwnerSay("Error in configuration notecard: home"); |
return; |
} |
HOME_POSITION = (vector)llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"position" |
] |
) |
+1 |
); |
if(HOME_POSITION == ZERO_VECTOR) { |
llOwnerSay("Error in configuration notecard: position"); |
return; |
} |
// DEBUG |
llOwnerSay("Read configuration notecard..."); |
|
// The notecard has been read, so get and URL and switch into detect. |
setjmp = "detect"; |
state url; |
} |
if(data == "") jump continue; |
integer i = llSubStringIndex(data, "#"); |
if(i != -1) data = llDeleteSubString(data, i, -1); |
list o = llParseString2List(data, ["="], []); |
// get rid of starting and ending quotes |
string k = llDumpList2String( |
llParseString2List( |
llStringTrim( |
llList2String( |
o, |
0 |
), |
STRING_TRIM), |
["\""], [] |
), "\""); |
string v = llDumpList2String( |
llParseString2List( |
llStringTrim( |
llList2String( |
o, |
1 |
), |
STRING_TRIM), |
["\""], [] |
), "\""); |
if(k == "" || v == "") jump continue; |
tuples += k; |
tuples += v; |
@continue; |
llGetNotecardLine("configuration", ++line); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state url { |
state_entry() { |
// DEBUG |
llOwnerSay("Requesting URL..."); |
llRequestURL(); |
} |
http_request(key id, string method, string body) { |
if(method != URL_REQUEST_GRANTED) return; |
callback = body; |
|
// DEBUG |
llOwnerSay("Got URL..."); |
|
// Jump table |
if(setjmp == "detect") state detect; |
if(setjmp == "recall") state recall; |
|
// Here be HALT. |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY)) { |
llResetScript(); |
} |
} |
} |
|
state detect { |
state_entry() { |
// DEBUG |
llOwnerSay("Detecting if Corrade is online..."); |
llSetTimerEvent(5); |
} |
timer() { |
llRequestAgentData((key)CORRADE, DATA_ONLINE); |
} |
dataserver(key id, string data) { |
if(data != "1") { |
// DEBUG |
llOwnerSay("Corrade is not online, sleeping..."); |
llSetTimerEvent(30); |
return; |
} |
state notify; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state notify { |
state_entry() { |
// DEBUG |
llOwnerSay("Binding to the alert notification..."); |
llInstantMessage( |
(key)CORRADE, |
wasKeyValueEncode( |
[ |
"command", "notify", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"action", "set", |
"type", "alert", |
"URL", wasURLEscape(callback), |
"callback", wasURLEscape(callback) |
] |
) |
); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) != "notify") return; |
if(wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Failed to bind to the alert notification..."); |
state sense; |
} |
// DEBUG |
llOwnerSay("Alert notification installed..."); |
state sense; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state sense { |
state_entry() { |
// DEBUG |
llOwnerSay("Waiting for alert messages for region restarts..."); |
} |
timer() { |
llRequestAgentData((key)CORRADE, DATA_ONLINE); |
} |
dataserver(key id, string data) { |
if(data == "1") return; |
// DEBUG |
llOwnerSay("Corrade is not online, sleeping..."); |
// Switch to detect loop and wait there for Corrade to come online. |
state detect; |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
// Get the number of minutes after which the region will go down. |
RESTART_DELAY = llList2Integer( |
llParseString2List( |
wasURLUnescape( |
wasKeyValueGet( |
"message", |
body |
) |
), |
[ |
" will restart in ", |
" minutes." |
], |
[] |
), |
1 |
); |
|
if(RESTART_DELAY == 0) return; |
|
// DEBUG |
llOwnerSay("Attempting to evade region restart..."); |
|
// Evade! |
state evade; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state evade_trampoline { |
state_entry() { |
state evade; |
} |
} |
|
state evade { |
state_entry() { |
// DEBUG |
llOwnerSay("Teleporting Corrade out of the region..."); |
// Alarm 60 |
llSetTimerEvent(60); |
// Shuffle regions. |
string region = llList2String(REGIONS, 0); |
REGIONS = llDeleteSubList(REGIONS, 0, 0); |
REGIONS += region; |
llInstantMessage( |
(key)CORRADE, wasKeyValueEncode( |
[ |
"command", "teleport", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"entity", "region", |
"region", wasURLEscape(region), |
"callback", wasURLEscape(callback) |
] |
) |
); |
} |
timer() { |
state evade_trampoline; |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
// This message was most likely not for us. |
if(wasKeyValueGet("command", body) |
!= "teleport") return; |
if(wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Failed teleport, retrying..."); |
state evade_trampoline; |
} |
|
// DEBUG |
llOwnerSay("Corrade evaded region restart..."); |
|
state confront; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
|
/* |
* This state is suspended for the duration of the simulator downtime. |
* Effects: |
* - callback URL is lost |
* - timer is resumed |
* - CHANGED_REGION_RESTART raised |
*/ |
state confront { |
state_entry() { |
// Marty! The future is in the past. |
// The past is in the future! - Doc Brown, Back To The Future |
// |
// Schedule an event after the scheduled restart delay |
// sent to the region plus some convenience offset (60s). |
// |
// Even if the region is not back up after the restart |
// delay and the convenience offset, the script will not |
// be running anyway since the sim will be offline and |
// will be suspended. |
// |
// Instead, when the region is back up, the timer will |
// resume and the script will eventually raise the event. |
llSetTimerEvent(RESTART_DELAY * 60 + 60); |
} |
timer() { |
// Ok, either the region has restarted or the event was raised. |
// |
// Refresh the URL and then recall. |
setjmp = "recall"; |
state url; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if(change & CHANGED_INVENTORY) { |
llResetScript(); |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
|
state recall_trampoline { |
state_entry() { |
state recall; |
} |
} |
|
state recall { |
state_entry() { |
// DEBUG |
llOwnerSay("Teleporting Corrade back to the home region..."); |
// Alarm 60 |
llSetTimerEvent(60); |
llInstantMessage( |
(key)CORRADE, wasKeyValueEncode( |
[ |
"command", "teleport", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"entity", "region", |
"region", wasURLEscape(HOME_REGION), |
"position", wasURLEscape((string)HOME_POSITION), |
"callback", wasURLEscape(callback) |
] |
) |
); |
} |
timer() { |
state recall_trampoline; |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
// This message was most likely not for us. |
if(wasKeyValueGet("command", body) |
!= "teleport") return; |
if(wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Failed teleport, retrying..."); |
state recall_trampoline; |
} |
|
// DEBUG |
llOwnerSay("Corrade teleported to home region..."); |
|
// We are back to the home region now. |
state detect; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
/remote-region-scanning-device/remote-region-scanning-device.lsl |
@@ -0,0 +1,630 @@ |
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// |
// This is a device meant to scan regions and show metrics for the Corrade |
// Second Life / OpenSim bot. You can find more details about the bot |
// by following the URL: http://was.fm/secondlife/scripted_agents/corrade |
// |
// The script works in conjunction with a "configuration" notecard and a |
// "regions" notecard that must both be placed in the same primitive. |
// The purpose of this script is to demonstrate scanning with Corrade and |
// you are free to use, change, and commercialize it under the GNU/GPLv3 |
// license at: http://www.gnu.org/licenses/gpl.html |
// |
/////////////////////////////////////////////////////////////////////////// |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueGet(string k, string data) { |
if(llStringLength(data) == 0) return ""; |
if(llStringLength(k) == 0) return ""; |
list a = llParseString2List(data, ["&", "="], []); |
integer i = llListFindList(a, [ k ]); |
if(i != -1) return llList2String(a, i+1); |
return ""; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueEncode(list data) { |
list k = llList2ListStrided(data, 0, -1, 2); |
list v = llList2ListStrided(llDeleteSubList(data, 0, 0), 0, -1, 2); |
data = []; |
do { |
data += llList2String(k, 0) + "=" + llList2String(v, 0); |
k = llDeleteSubList(k, 0, 0); |
v = llDeleteSubList(v, 0, 0); |
} while(llGetListLength(k) != 0); |
return llDumpList2String(data, "&"); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
integer wasListCountExclude(list input, list exclude) { |
if(llGetListLength(input) == 0) return 0; |
if(llListFindList(exclude, (list)llList2String(input, 0)) == -1) |
return 1 + wasListCountExclude(llDeleteSubList(input, 0, 0), exclude); |
return wasListCountExclude(llDeleteSubList(input, 0, 0), exclude); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasListToCSV(list l) { |
list v = []; |
do { |
string a = llDumpList2String( |
llParseStringKeepNulls( |
llList2String( |
l, |
0 |
), |
["\""], |
[] |
), |
"\"\"" |
); |
if(llParseStringKeepNulls(a, [" ", ",", "\n"], []) != (list) a) |
a = "\"" + a + "\""; |
v += a; |
l = llDeleteSubList(l, 0, 0); |
} while(l != []); |
return llDumpList2String(v, ","); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
list wasCSVToList(string csv) { |
list l = []; |
list s = []; |
string m = ""; |
do { |
string a = llGetSubString(csv, 0, 0); |
csv = llDeleteSubString(csv, 0, 0); |
if(a == ",") { |
if(llList2String(s, -1) != "\"") { |
l += m; |
m = ""; |
jump continue; |
} |
m += a; |
jump continue; |
} |
if(a == "\"" && llGetSubString(csv, 0, 0) == a) { |
m += a; |
csv = llDeleteSubString(csv, 0, 0); |
jump continue; |
} |
if(a == "\"") { |
if(llList2String(s, -1) != a) { |
s += a; |
jump continue; |
} |
s = llDeleteSubList(s, -1, -1); |
jump continue; |
} |
m += a; |
@continue; |
} while(csv != ""); |
// invariant: length(s) = 0 |
return l + m; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// escapes a string in conformance with RFC1738 |
string wasURLEscape(string i) { |
string o = ""; |
do { |
string c = llGetSubString(i, 0, 0); |
i = llDeleteSubString(i, 0, 0); |
if(c == "") jump continue; |
if(c == " ") { |
o += "+"; |
jump continue; |
} |
if(c == "\n") { |
o += "%0D" + llEscapeURL(c); |
jump continue; |
} |
o += llEscapeURL(c); |
@continue; |
} while(i != ""); |
return o; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// unescapes a string in conformance with RFC1738 |
string wasURLUnescape(string i) { |
return llUnescapeURL( |
llDumpList2String( |
llParseString2List( |
llDumpList2String( |
llParseString2List( |
i, |
["+"], |
[] |
), |
" " |
), |
["%0D%0A"], |
[] |
), |
"\n" |
) |
); |
} |
|
// corrade data |
string CORRADE = ""; |
string GROUP = ""; |
string PASSWORD = ""; |
|
// for holding the callback URL |
string callback = ""; |
|
// for notecard reading |
integer line = 0; |
|
// key-value data will be read into this list |
list tuples = []; |
// regions will be stored here |
list regions = []; |
string region = ""; |
|
default { |
state_entry() { |
if(llGetInventoryType("configuration") != INVENTORY_NOTECARD) { |
llOwnerSay("Sorry, could not find a configuration inventory notecard."); |
return; |
} |
// DEBUG |
llOwnerSay("Reading configuration file..."); |
llGetNotecardLine("configuration", line); |
} |
dataserver(key id, string data) { |
if(data == EOF) { |
// invariant, length(tuples) % 2 == 0 |
if(llGetListLength(tuples) % 2 != 0) { |
llOwnerSay("Error in configuration notecard."); |
return; |
} |
CORRADE = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"corrade" |
] |
) |
+1); |
if(CORRADE == "") { |
llOwnerSay("Error in configuration notecard: corrade"); |
return; |
} |
GROUP = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"group" |
] |
) |
+1); |
if(GROUP == "") { |
llOwnerSay("Error in configuration notecard: group"); |
return; |
} |
PASSWORD = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"password" |
] |
) |
+1); |
if(PASSWORD == "") { |
llOwnerSay("Error in configuration notecard: password"); |
return; |
} |
// DEBUG |
llOwnerSay("Read configuration notecard..."); |
state read; |
} |
if(data == "") jump continue; |
integer i = llSubStringIndex(data, "#"); |
if(i != -1) data = llDeleteSubString(data, i, -1); |
list o = llParseString2List(data, ["="], []); |
// get rid of starting and ending quotes |
string k = llDumpList2String( |
llParseString2List( |
llStringTrim( |
llList2String( |
o, |
0 |
), |
STRING_TRIM), |
["\""], [] |
), "\""); |
string v = llDumpList2String( |
llParseString2List( |
llStringTrim( |
llList2String( |
o, |
1 |
), |
STRING_TRIM), |
["\""], [] |
), "\""); |
if(k == "" || v == "") jump continue; |
tuples += k; |
tuples += v; |
@continue; |
llGetNotecardLine("configuration", ++line); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state read { |
state_entry() { |
if(llGetInventoryType("regions") != INVENTORY_NOTECARD) { |
llOwnerSay("Sorry, could not find a regions inventory notecard."); |
return; |
} |
// DEBUG |
llOwnerSay("Reading regions notecard..."); |
line = 0; |
llGetNotecardLine("regions", line); |
} |
dataserver(key id, string data) { |
if(data == EOF) { |
// DEBUG |
llOwnerSay("Read regions notcard..."); |
state url; |
} |
if(data == "") jump continue; |
regions += data; |
@continue; |
llGetNotecardLine("regions", ++line); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state url { |
state_entry() { |
// DEBUG |
llOwnerSay("Requesting URL..."); |
llRequestURL(); |
} |
http_request(key id, string method, string body) { |
if(method != URL_REQUEST_GRANTED) return; |
callback = body; |
// DEBUG |
llOwnerSay("Got URL..."); |
state detect; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state detect { |
state_entry() { |
// DEBUG |
llOwnerSay("Detecting if Corrade is online..."); |
llSetTimerEvent(5); |
} |
timer() { |
llRequestAgentData((key)CORRADE, DATA_ONLINE); |
} |
dataserver(key id, string data) { |
if(data != "1") { |
// DEBUG |
llOwnerSay("Corrade is not online, sleeping..."); |
llSetTimerEvent(30); |
return; |
} |
state teleport; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state teleport { |
state_entry() { |
// Timeout in one minute. |
llSetTimerEvent(60); |
// Check that Corrade is online. |
llSensorRepeat("", NULL_KEY, AGENT, 0.1, 0.1, 5); |
// Shuffle the regions and grab the next region. |
region = llList2String(regions, 0); |
regions = llDeleteSubList(regions, 0, 0); |
regions += region; |
// DEBUG |
llOwnerSay("Teleporting to: " + region); |
llInstantMessage( |
(key)CORRADE, |
wasKeyValueEncode( |
[ |
"command", "teleport", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"entity", "region", |
"region", wasURLEscape(region), |
"callback", wasURLEscape(callback) |
] |
) |
); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) != "teleport" || |
wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Failed to teleport to " + region + " due to: " + |
wasURLUnescape( |
wasKeyValueGet( |
"error", |
body |
) |
) |
); |
// Jump to trampoline for re-entry. |
state teleport_trampoline; |
} |
// DEBUG |
llOwnerSay("Teleported successfully to: " + region); |
state stats_trampoline; |
} |
no_sensor() { |
llRequestAgentData((key)CORRADE, DATA_ONLINE); |
} |
dataserver(key id, string data) { |
if(data != "1") { |
// DEBUG |
llOwnerSay("Corrade is not online, sleeping..."); |
state detect; |
} |
} |
timer() { |
state teleport_trampoline; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
|
state teleport_trampoline { |
state_entry() { |
// DEBUG |
llOwnerSay("Sleeping..."); |
llSetTimerEvent(30); |
} |
timer() { |
state teleport; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
|
state stats_trampoline { |
state_entry() { |
// DEBUG |
llOwnerSay("Sleeping..."); |
llSetTimerEvent(10); |
} |
timer() { |
state stats; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
|
state stats { |
state_entry() { |
// Timeout in one minute. |
llSetTimerEvent(60); |
// Check that Corrade is online. |
llSensorRepeat("", NULL_KEY, AGENT, 0.1, 0.1, 5); |
// DEBUG |
llOwnerSay("Fetching region statistics..."); |
llInstantMessage( |
(key)CORRADE, |
wasKeyValueEncode( |
[ |
"command", "getregiondata", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"data", wasListToCSV([ |
// For a full list see: http://was.fm/secondlife/scripted_agents/corrade/application_programming_interface#get_region_data |
"Stats.LastLag", |
"Stats.Agents", |
"Stats.Dilation", |
"Stats.FPS", |
"Stats.ActiveScripts", |
"Stats.ScriptTime", |
"Stats.Objects", |
"Stats.PhysicsFPS", |
"Stats.ScriptTime" |
]), |
"callback", wasURLEscape(callback) |
] |
) |
); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) != "getregiondata" || |
wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Failed to get stats for " + region + " due to: " + |
wasURLUnescape( |
wasKeyValueGet( |
"error", |
body |
) |
) |
); |
// Jump to trampoline for teleport. |
state teleport_trampoline; |
} |
// DEBUG |
llOwnerSay("Got stats for region: " + region); |
// Get the stats and unescape. |
list stat = wasCSVToList( |
wasURLUnescape( |
wasKeyValueGet( |
"data", |
body |
) |
) |
); |
llSetText("-:[ " + region + " ]:- \n" + |
// Show the stats in the overhead text. |
"Agents: " + llList2String( |
stat, |
llListFindList( |
stat, |
(list)"Stats.Agents" |
)+1 |
) + "\n" + |
"LastLag: " + llList2String( |
stat, |
llListFindList( |
stat, |
(list)"Stats.LastLag" |
)+1 |
) + "\n" + |
"Time Dilation: " + llList2String( |
stat, |
llListFindList( |
stat, |
(list)"Stats.Dilation" |
)+1 |
) + "\n" + |
"FPS: " + llList2String( |
stat, |
llListFindList( |
stat, |
(list)"Stats.FPS" |
)+1 |
) + "\n" + |
"Physics FPS: " + llList2String( |
stat, |
llListFindList( |
stat, |
(list)"Stats.PhysicsFPS" |
)+1 |
) + "\n" + |
"Scripts: " + llList2String( |
stat, |
llListFindList( |
stat, |
(list)"Stats.ActiveScripts" |
)+1 |
) + "\n" + |
"Script Time: " + llList2String( |
stat, |
llListFindList( |
stat, |
(list)"Stats.ScriptTime" |
)+1 |
) + "\n" + |
"Objects: " + llList2String( |
stat, |
llListFindList( |
stat, (list)"Stats.Objects" |
)+1 |
), |
<1, 0, 0>, |
1.0 |
); |
stat = []; |
state teleport_trampoline; |
} |
no_sensor() { |
llRequestAgentData((key)CORRADE, DATA_ONLINE); |
} |
dataserver(key id, string data) { |
if(data != "1") { |
// DEBUG |
llOwnerSay("Corrade is not online, sleeping..."); |
state detect; |
} |
} |
timer() { |
state teleport_trampoline; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
|
/rental-system/rental-system.lsl |
@@ -0,0 +1,975 @@ |
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) Wizardry and Steamworks 2015 - License: GNU GPLv3 // |
// Please see: http://www.gnu.org/licenses/gpl.html for legal details, // |
// rights of fair usage, the disclaimer and warranty conditions. // |
/////////////////////////////////////////////////////////////////////////// |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// escapes a string in conformance with RFC1738 |
string wasURLEscape(string i) { |
string o = ""; |
do { |
string c = llGetSubString(i, 0, 0); |
i = llDeleteSubString(i, 0, 0); |
if(c == "") jump continue; |
if(c == " ") { |
o += "+"; |
jump continue; |
} |
if(c == "\n") { |
o += "%0D" + llEscapeURL(c); |
jump continue; |
} |
o += llEscapeURL(c); |
@continue; |
} while(i != ""); |
return o; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// unescapes a string in conformance with RFC1738 |
string wasURLUnescape(string i) { |
return llUnescapeURL( |
llDumpList2String( |
llParseString2List( |
llDumpList2String( |
llParseString2List( |
i, |
["+"], |
[] |
), |
" " |
), |
["%0D%0A"], |
[] |
), |
"\n" |
) |
); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueEncode(list data) { |
list k = llList2ListStrided(data, 0, -1, 2); |
list v = llList2ListStrided(llDeleteSubList(data, 0, 0), 0, -1, 2); |
data = []; |
do { |
data += llList2String(k, 0) + "=" + llList2String(v, 0); |
k = llDeleteSubList(k, 0, 0); |
v = llDeleteSubList(v, 0, 0); |
} while(llGetListLength(k) != 0); |
return llDumpList2String(data, "&"); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueGet(string k, string data) { |
if(llStringLength(data) == 0) return ""; |
if(llStringLength(k) == 0) return ""; |
list a = llParseString2List(data, ["&", "="], []); |
integer i = llListFindList(llList2ListStrided(a, 0, -1, 2), [ k ]); |
if(i != -1) return llList2String(a, 2*i+1); |
return ""; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
list wasCSVToList(string csv) { |
list l = []; |
list s = []; |
string m = ""; |
do { |
string a = llGetSubString(csv, 0, 0); |
csv = llDeleteSubString(csv, 0, 0); |
if(a == ",") { |
if(llList2String(s, -1) != "\"") { |
l += m; |
m = ""; |
jump continue; |
} |
m += a; |
jump continue; |
} |
if(a == "\"" && llGetSubString(csv, 0, 0) == a) { |
m += a; |
csv = llDeleteSubString(csv, 0, 0); |
jump continue; |
} |
if(a == "\"") { |
if(llList2String(s, -1) != a) { |
s += a; |
jump continue; |
} |
s = llDeleteSubList(s, -1, -1); |
jump continue; |
} |
m += a; |
@continue; |
} while(csv != ""); |
// postcondition: length(s) = 0 |
return l + m; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
integer wasDateTimeToStamp( |
integer year, |
integer month, |
integer day, |
integer hour, |
integer minute, |
integer second |
) { |
month -= 2; |
if (month <= 0) { |
month += 12; |
--year; |
} |
return |
( |
(((year / 4 - year / 100 + year / 400 + (367 * month) / 12 + day) + |
year * 365 - 719499 |
) * 24 + hour |
) * 60 + minute |
) * 60 + second; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
float wasFmod(float a, float p) { |
if(p == 0) return (float)"nan"; |
return a - ((integer)(a/p) * p); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // |
// Original: Clive Page, Leicester University, UK. 1995-MAY-2 // |
/////////////////////////////////////////////////////////////////////////// |
list wasUnixTimeToDateTime(integer seconds) { |
integer mjday = (integer)(seconds/86400 + 40587); |
integer dateYear = 1858 + (integer)( (mjday + 321.51) / 365.25); |
float day = (integer)( wasFmod(mjday + 262.25, 365.25) ) + 0.5; |
integer dateMonth = 1 + (integer)(wasFmod(day / 30.6 + 2.0, 12.0) ); |
integer dateDay = 1 + (integer)(wasFmod(day,30.6)); |
float nsecs = wasFmod(seconds, 86400); |
integer dateSeconds = (integer)wasFmod(nsecs, 60); |
nsecs = nsecs / 60; |
integer dateMinutes = (integer)wasFmod(nsecs, 60); |
integer dateHour = (integer)(nsecs / 60); |
return [ dateYear, |
dateMonth, dateDay, dateHour, dateMinutes, dateSeconds ]; |
} |
|
// for changing states |
string nextstate = ""; |
|
// notecard reading |
integer line = 0; |
list tuples = []; |
|
// corrade data |
key CORRADE = NULL_KEY; |
string GROUP = ""; |
string PASSWORD = ""; |
// the owner of the rental system |
// the dude or dudette that gets paid |
key OWNER = NULL_KEY; |
// the price of the rent |
integer PRICE = 0; |
// the time for the rent in seconds |
integer RENT = 0; |
string URL = ""; |
// the role to invite rentants to |
string ROLE = ""; |
|
default { |
state_entry() { |
if(llGetInventoryType("configuration") != INVENTORY_NOTECARD) { |
llSetText("Sorry, could not find an inventory notecard.", <1, 0, 0>, 1.0); |
return; |
} |
llSetText("Reading configuration notecard...", <1, 1, 0>, 1.0); |
llGetNotecardLine("configuration", line); |
} |
dataserver(key id, string data) { |
if(data == EOF) { |
// invariant, length(tuples) % 2 == 0 |
if(llGetListLength(tuples) % 2 != 0) { |
llSetText("Error in configuration notecard.", <1, 0, 0>, 1.0); |
return; |
} |
CORRADE = (key)llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"corrade" |
] |
) |
+1); |
if(CORRADE == NULL_KEY) { |
llSetText("Error in configuration notecard: corrade", <1, 0, 0>, 1.0); |
return; |
} |
GROUP = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"group" |
] |
) |
+1); |
if(GROUP == "") { |
llSetText("Error in configuration notecard: group", <1, 0, 0>, 1.0); |
return; |
} |
PASSWORD = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"password" |
] |
) |
+1); |
if(PASSWORD == "") { |
llSetText("Error in configuration notecard: password", <1, 0, 0>, 1.0); |
return; |
} |
OWNER = (key)llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"owner" |
] |
) |
+1); |
if(OWNER == NULL_KEY) { |
llSetText("Error in configuration notecard: owner", <1, 0, 0>, 1.0); |
return; |
} |
PRICE = (integer)llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"price" |
] |
) |
+1); |
if(PRICE == 0) { |
llSetText("Error in configuration notecard: price", <1, 0, 0>, 1.0); |
return; |
} |
RENT = (integer)llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"rent" |
] |
) |
+1); |
if(RENT == 0) { |
llSetText("Error in configuration notecard: rent", <1, 0, 0>, 1.0); |
return; |
} |
ROLE = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"role" |
] |
) |
+1 |
); |
if(ROLE == "") { |
llSetText("Error in configuration notecard: role", <1, 0, 0>, 1.0); |
return; |
} |
|
llSetText("Read configuration notecard.", <0, 1, 0>, 1.0); |
|
// if data is set switch to rented state |
if(llGetObjectDesc() != "") state rented; |
// otherwise switch to the payment state |
state payment; |
} |
if(data == "") jump continue; |
integer i = llSubStringIndex(data, "#"); |
if(i != -1) data = llDeleteSubString(data, i, -1); |
list o = llParseString2List(data, ["="], []); |
// get rid of starting and ending quotes |
string k = llDumpList2String( |
llParseString2List( |
llStringTrim( |
llList2String( |
o, |
0 |
), |
STRING_TRIM), |
["\""], [] |
), "\""); |
string v = llDumpList2String( |
llParseString2List( |
llStringTrim( |
llList2String( |
o, |
1 |
), |
STRING_TRIM), |
["\""], [] |
), "\""); |
if(k == "" || v == "") jump continue; |
tuples += k; |
tuples += v; |
@continue; |
llGetNotecardLine("configuration", ++line); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if(change & CHANGED_INVENTORY) { |
llResetScript(); |
} |
} |
} |
|
state trampoline { |
state_entry() { |
llSetTimerEvent(5); |
} |
timer() { |
llSetTimerEvent(0); |
// State jump table |
if(nextstate == "url") state url; |
if(nextstate == "getmembers") state getmembers; |
if(nextstate == "getrolemembers") state getrolemembers; |
if(nextstate == "addtorole") state addtorole; |
if(nextstate == "invite") state invite; |
if(nextstate == "rented") state rented; |
if(nextstate == "demote") state demote; |
// automata in invalid state |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if(change & CHANGED_INVENTORY) { |
llResetScript(); |
} |
} |
} |
|
state payment { |
state_entry() { |
llSetPayPrice(PRICE, [PRICE]); |
llSetClickAction(CLICK_ACTION_PAY); |
llSetText("☀ Touch me to rent this place! ☀", <0, 1, 0>, 1.0); |
} |
money(key id, integer amount) { |
// Get the current time stamp. |
list stamp = llList2List( |
llParseString2List( |
llGetTimestamp(), |
["-",":","T", "."],[""] |
), |
0, 5 |
); |
// convert to seconds and add the rent |
integer delta = wasDateTimeToStamp( |
llList2Integer(stamp, 0), |
llList2Integer(stamp, 1), |
llList2Integer(stamp, 2), |
llList2Integer(stamp, 3), |
llList2Integer(stamp, 4), |
llList2Integer(stamp, 5) |
) + |
// the amount of time is the amount paid |
// times the rent time divided by the price |
amount * (integer)( |
(float)RENT / |
(float)PRICE |
); |
// convert back to a timestamp |
stamp = wasUnixTimeToDateTime(delta); |
// and set the renter and the eviction date |
llSetObjectDesc( |
wasKeyValueEncode( |
[ |
"rentantUUID", id, |
"rentantName", llKey2Name(id), |
"expiresDate", llList2String(stamp, 0) + |
"-" + llList2String(stamp, 1) + |
"-" + llList2String(stamp, 2) + |
"T" + llList2String(stamp, 3) + |
":" + llList2String(stamp, 4) + |
":" + llList2String(stamp, 5) |
] |
) |
); |
nextstate = "getmembers"; |
state url; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if(change & CHANGED_INVENTORY) { |
llResetScript(); |
} |
} |
} |
|
state url { |
state_entry() { |
// release any previous URL |
llReleaseURL(URL); |
// request a new URL |
llRequestURL(); |
} |
http_request(key id, string method, string body) { |
if(method != URL_REQUEST_GRANTED) { |
llSetText("☀ Unable to get an URL! ☀", <1, 0, 0>, 1.0); |
nextstate = "url"; |
state trampoline; |
} |
URL = body; |
// state URL jump table |
if(nextstate == "getmembers") state getmembers; |
if(nextstate == "demote") state demote; |
// automata in invalid state |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if(change & CHANGED_INVENTORY) { |
llResetScript(); |
} |
} |
} |
|
state getmembers { |
state_entry() { |
llSetText("☀ Getting group members... ☀", <1, 1, 0>, 1.0); |
llInstantMessage(CORRADE, |
wasKeyValueEncode( |
[ |
"command", "getmembers", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
// we just care if the agent is in the group |
// so we use Corrade's sifting ability in order |
// to reduce the script memory usage |
"sift", wasURLEscape( |
"(" + |
wasKeyValueGet( |
"rentantUUID", |
llGetObjectDesc() |
) + |
")*" |
), |
"callback", wasURLEscape(URL) |
] |
) |
); |
llSetTimerEvent(60); |
} |
http_request(key id, string method, string body) { |
if(wasKeyValueGet("success", body) != "True") { |
llSetText("☀ Could not get group members! ☀", <1, 0, 0>, 1.0); |
nextstate = "getmembers"; |
state trampoline; |
} |
// check that the payer is part of the role |
integer i = |
llListFindList( |
wasCSVToList( |
wasURLUnescape( |
wasKeyValueGet( |
"data", |
body |
) |
) |
), |
(list)wasKeyValueGet( |
"rentantUUID", |
llGetObjectDesc() |
) |
); |
llSetTimerEvent(0); |
// if they are in the group then check roles. |
if(i != -1) state getrolemembers; |
// otherwise invite them to the group role. |
state invite; |
} |
timer() { |
llSetTimerEvent(0); |
nextstate = "getmembers"; |
state trampoline; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if(change & CHANGED_INVENTORY) { |
llResetScript(); |
} |
} |
} |
|
state getrolemembers { |
state_entry() { |
llSetText("☀ Getting role members... ☀", <1, 1, 0>, 1.0); |
llInstantMessage(CORRADE, |
wasKeyValueEncode( |
[ |
"command", "getrolemembers", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"role", wasURLEscape(ROLE), |
// we just care if the agent is in the renter role |
// so we use Corrade's sifting ability in order |
// to reduce the script memory usage |
"sift", wasURLEscape( |
"(" + |
wasKeyValueGet( |
"rentantUUID", |
llGetObjectDesc() |
) + |
")*" |
), |
"callback", wasURLEscape(URL) |
] |
) |
); |
llSetTimerEvent(60); |
} |
http_request(key id, string method, string body) { |
if(wasKeyValueGet("success", body) != "True") { |
llSetText("☀ Could not get role members! ☀", <1, 0, 0>, 1.0); |
nextstate = "getrolemembers"; |
state trampoline; |
} |
// check that the payer is part of the role |
integer i = |
llListFindList( |
wasCSVToList( |
wasURLUnescape( |
wasKeyValueGet( |
"data", |
body |
) |
) |
), |
(list)wasKeyValueGet( |
"rentantUUID", |
llGetObjectDesc() |
) |
); |
llSetTimerEvent(0); |
// if they are in the role then skip inviting them. |
if(i != -1) state rented; |
// otherwise add them to the land role. |
state addtorole; |
} |
timer() { |
llSetTimerEvent(0); |
nextstate = "getrolemembers"; |
state trampoline; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if(change & CHANGED_INVENTORY) { |
llResetScript(); |
} |
} |
} |
|
state addtorole { |
state_entry() { |
llSetText("☀ Adding to role... ☀", <1, 1, 0>, 1.0); |
llInstantMessage(CORRADE, |
wasKeyValueEncode( |
[ |
"command", "addtorole", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"agent", wasURLEscape( |
wasKeyValueGet( |
"rentantUUID", |
llGetObjectDesc() |
) |
), |
"role", wasURLEscape(ROLE), |
"callback", wasURLEscape(URL) |
] |
) |
); |
llSetTimerEvent(60); |
} |
http_request(key id, string method, string body) { |
if(wasKeyValueGet("success", body) != "True") { |
llSetText("☀ Could not add to role! ☀", <1, 0, 0>, 1.0); |
nextstate = "addtorole"; |
state trampoline; |
} |
// otherwise invite them to the group role. |
state rented; |
} |
timer() { |
llSetTimerEvent(0); |
nextstate = "addtorole"; |
state trampoline; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if(change & CHANGED_INVENTORY) { |
llResetScript(); |
} |
} |
} |
|
state invite { |
state_entry() { |
llSetText("☀ Please accept the group invite! ☀", <1, 1, 0>, 1.0); |
// invite the agent to the land group |
llInstantMessage(CORRADE, |
wasKeyValueEncode( |
[ |
"command", "invite", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"agent", wasURLEscape( |
wasKeyValueGet( |
"rentantUUID", |
llGetObjectDesc() |
) |
), |
"role", wasURLEscape(ROLE), |
"callback", wasURLEscape(URL) |
] |
) |
); |
// handle any Corrade timeouts |
llSetTimerEvent(60); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
// Checks if the invite was sent successfully or if that fails but the |
// agent is already in the group (status 15345) then continue. |
// Otherwise, jump to the trampoline and send the invite again. |
// Status codes: |
// http://grimore.org/secondlife/scripted_agents/corrade/status_codes/progressive |
if(wasKeyValueGet("success", body) != "True" && |
wasKeyValueGet("status", body) != "15345") { |
llSetText("☀ Group invite could not be sent! ☀", <1, 0, 0>, 1.0); |
nextstate = "invite"; |
state trampoline; |
} |
llSetText("☀ Group invitation sent! ☀", <1, 1, 0>, 1.0); |
llSetTimerEvent(0); |
state rented; |
} |
timer() { |
llSetTimerEvent(0); |
nextstate = "invite"; |
state trampoline; |
} |
} |
|
state rented { |
state_entry() { |
// Get the expiration date |
list expires = llList2List( |
llParseString2List( |
wasKeyValueGet( |
"expiresDate", |
llGetObjectDesc() |
), |
["-",":","T", "."],[""] |
), |
0, 5 |
); |
// Get the current date |
list stamp = llList2List( |
llParseString2List( |
llGetTimestamp(), |
["-",":","T", "."],[""] |
), |
0, 5 |
); |
|
integer delta = wasDateTimeToStamp( |
llList2Integer(expires, 0), |
llList2Integer(expires, 1), |
llList2Integer(expires, 2), |
llList2Integer(expires, 3), |
llList2Integer(expires, 4), |
llList2Integer(expires, 5) |
) - wasDateTimeToStamp( |
llList2Integer(stamp, 0), |
llList2Integer(stamp, 1), |
llList2Integer(stamp, 2), |
llList2Integer(stamp, 3), |
llList2Integer(stamp, 4), |
llList2Integer(stamp, 5) |
); |
|
// the rent has expired, now evict the rentant |
if(delta <= 0) { |
llSetTimerEvent(0); |
nextstate = "demote"; |
state url; |
} |
|
// otherwise, update the remaining time |
list remaining = wasUnixTimeToDateTime(delta); |
remaining = llListReplaceList(remaining, [ llList2Integer(remaining, 0)-1970 ], 0, 0); |
remaining = llListReplaceList(remaining, [ llList2Integer(remaining, 1)-1 ], 1, 1); |
remaining = llListReplaceList(remaining, [ llList2Integer(remaining, 2)-1 ], 2, 2); |
|
llSetText( |
"☀ Private property! ☀" |
+ "\n" + |
"Rented by: " + |
wasKeyValueGet( |
"rentantName", |
llGetObjectDesc() |
) |
+ "\n" + |
"Expires on: " + |
wasKeyValueGet( |
"expiresDate", |
llGetObjectDesc() |
) |
+ "\n" + |
"Remaining: " + |
llList2String(remaining, 0) + "-" + |
llList2String(remaining, 1) + "-" + |
llList2String(remaining, 2) + " " + |
llList2String(remaining, 3) + ":" + |
llList2String(remaining, 4) |
+ "\n" + |
"Touch to extend rent.", |
<0, 1, 1>, |
1.0 |
); |
llSetPayPrice(PRICE, [PRICE]); |
llSetClickAction(CLICK_ACTION_PAY); |
// set the countdown every minute |
llSetTimerEvent(60); |
} |
money(key id, integer amount) { |
// Get the expiration date |
list stamp = llList2List( |
llParseString2List( |
wasKeyValueGet( |
"expiresDate", |
llGetObjectDesc() |
), |
["-",":","T", "."],[""] |
), |
0, 5 |
); |
// convert to seconds and add the extended rent |
integer delta = wasDateTimeToStamp( |
llList2Integer(stamp, 0), |
llList2Integer(stamp, 1), |
llList2Integer(stamp, 2), |
llList2Integer(stamp, 3), |
llList2Integer(stamp, 4), |
llList2Integer(stamp, 5) |
) + |
// the amount of time to extend is the amount |
// paid times the rent time divided by the price |
amount * (integer)( |
(float)RENT / |
(float)PRICE |
); |
// convert back to a timestamp |
stamp = wasUnixTimeToDateTime(delta); |
// and set the renter and the eviction date |
llSetObjectDesc( |
wasKeyValueEncode( |
[ |
"rentantUUID", wasKeyValueGet( |
"rentantUUID", |
llGetObjectDesc() |
), |
"rentantName", wasKeyValueGet( |
"rentantName", |
llGetObjectDesc() |
), |
"expiresDate", llList2String(stamp, 0) + |
"-" + llList2String(stamp, 1) + |
"-" + llList2String(stamp, 2) + |
"T" + llList2String(stamp, 3) + |
":" + llList2String(stamp, 4) + |
":" + llList2String(stamp, 5) |
] |
) |
); |
llSetText("☀ Updating... ☀", <1, 1, 0>, 1.0); |
llSetTimerEvent(0); |
nextstate = "rented"; |
state trampoline; |
} |
timer() { |
// Get the expiration date |
list expires = llList2List( |
llParseString2List( |
wasKeyValueGet( |
"expiresDate", |
llGetObjectDesc() |
), |
["-",":","T", "."],[""] |
), |
0, 5 |
); |
// Get the current date |
list stamp = llList2List( |
llParseString2List( |
llGetTimestamp(), |
["-",":","T", "."],[""] |
), |
0, 5 |
); |
|
integer delta = wasDateTimeToStamp( |
llList2Integer(expires, 0), |
llList2Integer(expires, 1), |
llList2Integer(expires, 2), |
llList2Integer(expires, 3), |
llList2Integer(expires, 4), |
llList2Integer(expires, 5) |
) - wasDateTimeToStamp( |
llList2Integer(stamp, 0), |
llList2Integer(stamp, 1), |
llList2Integer(stamp, 2), |
llList2Integer(stamp, 3), |
llList2Integer(stamp, 4), |
llList2Integer(stamp, 5) |
); |
|
// the rent has expired, now evict the rentant |
if(delta <= 0) { |
llSetTimerEvent(0); |
nextstate = "demote"; |
state url; |
} |
|
// otherwise, update the remaining time |
list remaining = wasUnixTimeToDateTime(delta); |
remaining = llListReplaceList(remaining, [ llList2Integer(remaining, 0)-1970 ], 0, 0); |
remaining = llListReplaceList(remaining, [ llList2Integer(remaining, 1)-1 ], 1, 1); |
remaining = llListReplaceList(remaining, [ llList2Integer(remaining, 2)-1 ], 2, 2); |
|
llSetText( |
"☀ Private property! ☀" |
+ "\n" + |
"Rented by: " + |
wasKeyValueGet( |
"rentantName", |
llGetObjectDesc() |
) |
+ "\n" + |
"Expires on: " + |
wasKeyValueGet( |
"expiresDate", |
llGetObjectDesc() |
) |
+ "\n" + |
"Remaining: " + |
llList2String(remaining, 0) + "-" + |
llList2String(remaining, 1) + "-" + |
llList2String(remaining, 2) + " " + |
llList2String(remaining, 3) + ":" + |
llList2String(remaining, 4) |
+ "\n" + |
"Touch to extend rent.", |
<0, 1, 1>, |
1.0 |
); |
|
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if(change & CHANGED_INVENTORY) { |
llResetScript(); |
} |
} |
} |
|
state demote { |
state_entry() { |
llSetText("☀ Rent has expired! ☀", <1, 0, 0>, 1.0); |
// demote the agent from the renter role |
llInstantMessage(CORRADE, |
wasKeyValueEncode( |
[ |
"command", "deletefromrole", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"agent", wasURLEscape( |
wasKeyValueGet( |
"rentantUUID", |
llGetObjectDesc() |
) |
), |
"role", wasURLEscape(ROLE), |
"callback", wasURLEscape(URL) |
] |
) |
); |
// handle any Corrade timeouts |
llSetTimerEvent(60); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
// Checks if the demote was sent successfully or if that fails but the |
// agent has already left the group (status 11502) then continue. |
// Otherwise, jump to the trampoline and send the demote again. |
// Status codes: |
// http://grimore.org/secondlife/scripted_agents/corrade/status_codes/progressive |
if(wasKeyValueGet("success", body) != "True" && |
wasKeyValueGet("status", body) != "11502") { |
llSetText("☀ Could not demote! ☀", <1, 0, 0>, 1.0); |
nextstate = "demote"; |
state trampoline; |
} |
llSetText("☀ Renter demoted! ☀", <1, 1, 0>, 1.0); |
llSetTimerEvent(0); |
|
// Now clean up the rental and restart. |
llSetObjectDesc(""); |
llResetScript(); |
} |
timer() { |
llSetTimerEvent(0); |
nextstate = "demote"; |
state trampoline; |
} |
} |
|
/scheduled-group-notices/crontab.lsl |
@@ -0,0 +1,421 @@ |
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3 // |
// Please see: http://www.gnu.org/licenses/gpl.html for legal details, // |
// rights of fair usage, the disclaimer and warranty conditions. // |
/////////////////////////////////////////////////////////////////////////// |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2011 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
list wasListReverse(list lst) { |
if(llGetListLength(lst)<=1) return lst; |
return wasListReverse( |
llList2List(lst, 1, llGetListLength(lst)) |
) + llList2List(lst,0,0); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasDayOfWeek(integer year, integer month, integer day) { |
return llList2String( |
[ "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", |
"Saturday", "Sunday" ], |
( |
day |
+ ((153 * (month + 12 * ((14 - month) / 12) - 3) + 2) / 5) |
+ (365 * (year + 4800 - ((14 - month) / 12))) |
+ ((year + 4800 - ((14 - month) / 12)) / 4) |
- ((year + 4800 - ((14 - month) / 12)) / 100) |
+ ((year + 4800 - ((14 - month) / 12)) / 400) |
- 32045 |
) % 7 |
); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
integer wasGetYearDays(integer year) { |
integer leap = (year % 4 == 0 && year % 100 != 0) || |
(year % 400 == 0); |
if(leap == TRUE) { |
return 366; |
} |
return 365; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
integer wasGetMonthDays(integer month, integer year) { |
if (month == 4 || month == 6 || month == 9 || month == 11) { |
return 30; |
} |
if(month == 2) { |
integer leap = (year % 4 == 0 && year % 100 != 0) || |
(year % 400 == 0); |
if(leap == TRUE) { |
return 29; |
} |
return 28; |
} |
return 31; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasUnixTimeToStamp(integer unix) { |
integer year = 1970; |
integer dayno = unix / 86400; |
do { |
dayno -= wasGetYearDays(year); |
++year; |
} while (dayno >= wasGetYearDays(year)); |
integer month = 1; |
do { |
dayno -= wasGetMonthDays(month, year); |
++month; |
} while (dayno >= wasGetMonthDays(month, year)); |
return (string)year + "-" + |
(string)month + "-" + |
(string)(dayno + 1) + "T" + |
(string)((unix % 86400) / 3600) + ":" + |
(string)(((unix % 86400) % 3600) / 60) + ":" + |
(string)(unix % 60) + ".0Z"; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
integer wasMenuIndex = 0; |
list wasDialogMenu(list input, list actions, string direction) { |
integer cut = 11-wasListCountExclude(actions, [""]); |
if(direction == ">" && (wasMenuIndex+1)*cut+wasMenuIndex+1 < llGetListLength(input)) { |
++wasMenuIndex; |
jump slice; |
} |
if(direction == "<" && wasMenuIndex-1 >= 0) { |
--wasMenuIndex; |
jump slice; |
} |
@slice; |
integer multiple = wasMenuIndex*cut; |
input = llList2List(input, multiple+wasMenuIndex, multiple+cut+wasMenuIndex); |
input = wasListMerge(input, actions, ""); |
return input; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
integer wasListCountExclude(list input, list exclude) { |
if(llGetListLength(input) == 0) return 0; |
if(llListFindList(exclude, (list)llList2String(input, 0)) == -1) |
return 1 + wasListCountExclude(llDeleteSubList(input, 0, 0), exclude); |
return wasListCountExclude(llDeleteSubList(input, 0, 0), exclude); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
list wasListMerge(list l, list m, string merge) { |
if(llGetListLength(l) == 0 && llGetListLength(m) == 0) return []; |
string a = llList2String(m, 0); |
if(a != merge) return [ a ] + |
wasListMerge(l, llDeleteSubList(m, 0, 0), merge); |
return [ llList2String(l, 0) ] + |
wasListMerge( |
llDeleteSubList(l, 0, 0), |
llDeleteSubList(m, 0, 0), |
merge |
); |
} |
|
// for notecard reading |
integer line = 0; |
// time to execute |
list time = []; |
// message to send |
list exec = []; |
// subject to send |
list subj = []; |
// item to send |
list item = []; |
// inventory notecards |
list notes = []; |
// current notecard |
string note = ""; |
key agent = NULL_KEY; |
// minute store |
integer minute = -1; |
|
default { |
state_entry() { |
// get all the inventory notecards |
integer i = llGetInventoryNumber(INVENTORY_NOTECARD)-1; |
if(i == -1) { |
llSay(0, "No notecards found, idling..."); |
return; |
} |
do { |
notes += llGetInventoryName(INVENTORY_NOTECARD, i); |
} while(--i>-1); |
note = llList2String(notes, 0); |
notes = llDeleteSubList(notes, 0, 0); |
line = 0; |
llSay(0, "Reading notecard: " + note); |
llGetNotecardLine(note, line); |
} |
dataserver(key id, string data) { |
if(data == EOF) { |
// if we have read all the notecards, |
// start processing |
if(llGetListLength(notes) == 0) { |
// check if the crons are set-up properly or bail |
if(llGetListLength(time) == 0 || |
llGetListLength(exec) == 0 || |
llGetListLength(time) != llGetListLength(exec)) { |
llSay(0, "No valid schedules found..."); |
return; |
} |
llSay(0, "All notecards have been read..."); |
state cron; |
} |
// otherwise permute |
note = llList2String(notes, 0); |
notes = llDeleteSubList(notes, 0, 0); |
line = 0; |
llSay(0, "Reading notecard: " + note); |
llGetNotecardLine(note, line); |
return; |
} |
if(data == "") jump continue; |
integer i = llSubStringIndex(data, "#"); |
if(i != -1) data = llDeleteSubString(data, i, -1); |
if(data == "") jump continue; |
list data = llParseString2List(data, [" "], []); |
// * * * * * message to send to the link-set |
// sanity check a little |
if(llGetListLength(data) < 6 || |
(llList2String(data, 2) != "*" && llList2Integer(data, 2) == 0) || |
(llList2String(data, 3) != "*" && llList2Integer(data, 3) == 0)) jump continue; |
list t = llList2List(data, 0, 4); |
// normalize 0x |
i = llGetListLength(t)-1; |
do { |
if(llList2String(t, i) == "*") jump wildcard; |
t = llListReplaceList(t, [ llList2Integer(t, i) ], i, i); |
@wildcard; |
} while(--i>-1); |
time += llDumpList2String(t, " "); |
list ms = llParseString2List(llDumpList2String(llList2List(data, 5, -1), " "), ["|"], []); |
// check if subject and message are present |
if(llGetListLength(ms) > 0) { |
subj += llList2String(ms, 0); |
jump message; |
} |
subj += ""; |
@message; |
if(llGetListLength(ms) > 1) { |
exec += llList2String(ms, 1); |
jump attachment; |
} |
exec += ""; |
@attachment; |
if(llGetListLength(ms) >= 2) { |
item += llList2String(ms, 2); |
jump continue; |
} |
item += ""; |
@continue; |
llGetNotecardLine(note, ++line); |
} |
changed(integer change) { |
if(change & CHANGED_INVENTORY) llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// [WaS-K] Cron @ http://was.fm/secondlife/cron // |
/////////////////////////////////////////////////////////////////////////// |
state cron { |
state_entry() { |
// cron runs every minute so we bind the event handler |
// to the second in order to be precisely on the minute |
llSetTimerEvent(1); |
llSay(0, "Scheduler activated..."); |
} |
touch_start(integer num) { |
/////////////////////////////////////////////////////////////////////////// ENABLE ME |
// not part of the same group, so bail |
//if(llDetectedGroup(0) == FALSE) return; |
integer comChannel = (integer)("0x8" + llGetSubString(llGetKey(), 0, 6)); |
llListen(comChannel, "", "", ""); |
llDialog(llDetectedKey(0), "\n Welcome to the Scheduler.\nCreated in 2014 by Wizardry and Steamworks\n 10 September 2014: Version: 1.0\n\n", ["⌚ Show", "⎙ Remove"], comChannel); |
} |
listen(integer channel, string name, key toucher, string message) { |
if(message == "⌚ Show") { |
llInstantMessage(toucher, "-----------------------------------------"); |
integer i = llGetListLength(time)-1; |
do { |
llInstantMessage(toucher, llList2String(time, i) + " ▶︎ " + llList2String(exec, i)); |
} while(--i>-1); |
llInstantMessage(toucher, "-----------------------------------------"); |
return; |
} |
if(message == "⎙ Remove") { |
llSetTimerEvent(0); |
agent = toucher; |
state remove; |
} |
} |
timer() { |
// build the current date |
list stamp = llParseString2List( |
wasUnixTimeToStamp(llGetUnixTime() - ((integer) llGetGMTclock() - (integer) llGetWallclock())), |
["-",":","T"],[""] |
); |
|
// only once per minute |
if(llList2Integer(stamp, 4) == minute) return; |
|
list ymd = llList2List(stamp, 0, 2); |
integer weekDay = llListFindList( |
// convert to cron syntax where Sunday counts as day 0 or 7 |
[ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ], |
[ |
wasDayOfWeek( |
llList2Integer(ymd, 0), |
llList2Integer(ymd, 1), |
llList2Integer(ymd, 2) |
) |
] |
); |
|
// minute, hour, day, month, day of week |
list date = wasListReverse(llList2List(stamp, 1, -2)) + weekDay; |
integer i = llGetListLength(date)-1; |
// normalize 0x |
do { |
date = llListReplaceList(date, [ llList2Integer(date, i) ], i, i); |
} while(--i>-1); |
|
// check if it is time |
list times = time; |
list execs = exec; |
list subjs = subj; |
list items = item; |
do { |
list cron = llParseString2List(llList2String(times, 0), [" "], []); |
do { |
// crontab syntax counts Sunday as day 0 or 7 |
// so we add an exception on the week day element |
if(llGetListLength(times) == 1 && |
llList2Integer(date, 0) == 0 && |
llList2Integer(cron, 0) != 0 && |
llList2Integer(cron, 7) != 7) { |
jump continue; |
} |
if(llList2String(date, 0) != llList2String(cron, 0) && llList2String(cron, 0) != "*") { |
jump continue; |
} |
date = llDeleteSubList(date, 0, 0); |
cron = llDeleteSubList(cron, 0, 0); |
} while(llGetListLength(cron)); |
// Send the notice information. |
llMessageLinked( |
LINK_SET, |
0, |
llList2CSV( |
[ |
llList2String(subjs, 0), |
llList2String(execs, 0), |
llList2String(items, 0) |
] |
), |
NULL_KEY |
); |
@continue; |
times = llDeleteSubList(times, 0, 0); |
execs = llDeleteSubList(execs, 0, 0); |
subjs = llDeleteSubList(subjs, 0, 0); |
items = llDeleteSubList(items, 0, 0); |
} while(llGetListLength(times)); |
|
// only once per minute |
minute = llList2Integer(stamp, 4); |
} |
changed(integer change) { |
if(change & CHANGED_INVENTORY) llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Remove Notecard // |
/////////////////////////////////////////////////////////////////////////// |
state remove { |
state_entry() { |
// get all the inventory notecards |
integer i = llGetInventoryNumber(INVENTORY_NOTECARD)-1; |
do { |
notes += llGetSubString(llGetInventoryName(INVENTORY_NOTECARD, i), 0, 8); |
} while(--i>-1); |
integer channel = (integer)("0x8" + llGetSubString(llGetKey(), 0, 6)); |
llListen(channel, "", "", ""); |
llDialog(agent, "\n Welcome to the Scheduler.\nCreated in 2014 by Wizardry and Steamworks\n 10 September 2014: Version: 1.0\n\n", wasDialogMenu(notes, ["⟵ Back", "⏏ Exit", "Next ⟶"], ""), channel); |
llSetTimerEvent(60); |
} |
listen(integer channel, string name, key id, string message) { |
if(message == "⟵ Back") { |
llSetTimerEvent(60); |
llDialog(id, "\n Welcome to the Scheduler.\nCreated in 2014 by Wizardry and Steamworks\n 10 September 2014: Version: 1.0\n\n", wasDialogMenu(notes, ["⟵ Back", "⏏ Exit", "Next ⟶"], "<"), -10); |
return; |
} |
if(message == "Next ⟶") { |
llSetTimerEvent(60); |
llDialog(id, "\n Welcome to the Scheduler.\nCreated in 2014 by Wizardry and Steamworks\n 10 September 2014: Version: 1.0\n\n", wasDialogMenu(notes, ["⟵ Back", "⏏ Exit", "Next ⟶"], ">"), -10); |
return; |
} |
if(message == "⏏ Exit") { |
llInstantMessage(id, "Resuming operations..."); |
llResetScript(); |
} |
integer i = llGetInventoryNumber(INVENTORY_NOTECARD)-1; |
do { |
string name = llGetInventoryName(INVENTORY_NOTECARD, i); |
if(llSubStringIndex(name, message) == -1) jump continue_inventory; |
llInstantMessage(id, "Deleting notecard: " + message); |
llRemoveInventory(name); |
integer j = llGetListLength(notes)-1; |
do { |
note = llList2String(notes, j); |
if(llSubStringIndex(name, note) == -1) jump continue_notes; |
notes = llDeleteSubList(notes, j, j); |
jump menu; |
@continue_notes; |
} while(--j>-1); |
@continue_inventory; |
} while(--i>-1); |
@menu; |
llSetTimerEvent(60); |
llDialog(id, "\n Welcome to the Scheduler.\nCreated in 2014 by Wizardry and Steamworks\n 10 September 2014: Version: 1.0\n\n", wasDialogMenu(notes, ["⟵ Back", "⏏ Exit", "Next ⟶"], ""), channel); |
} |
timer() { |
llInstantMessage(agent, "Dialog expired, resuming operations..."); |
llResetScript(); |
} |
changed(integer change) { |
if(change & CHANGED_INVENTORY) llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
} |
/select-dialog-options/select-dialog-options/select-dialog-options.lsl |
@@ -0,0 +1,465 @@ |
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// |
// This is a menu selector for the Corrade bot that is capable of replying |
// to dialog requests while supporting several nested levels of menus. You |
// can find out more about the Corrade bot by following the URL: |
// http://was.fm/secondlife/scripted_agents/corrade |
// |
// The sit script works together with a "configuration" notecard that must |
// be placed in the same primitive as this script. The purpose of this |
// script is to demonstrate answering dialogs with Corrade and you are free |
// to use, change, and commercialize it under the GNU/GPLv3 license at: |
// http://www.gnu.org/licenses/gpl.html |
// |
/////////////////////////////////////////////////////////////////////////// |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueGet(string k, string data) { |
if(llStringLength(data) == 0) return ""; |
if(llStringLength(k) == 0) return ""; |
list a = llParseString2List(data, ["&", "="], []); |
integer i = llListFindList(a, [ k ]); |
if(i != -1) return llList2String(a, i+1); |
return ""; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueEncode(list data) { |
list k = llList2ListStrided(data, 0, -1, 2); |
list v = llList2ListStrided(llDeleteSubList(data, 0, 0), 0, -1, 2); |
data = []; |
do { |
data += llList2String(k, 0) + "=" + llList2String(v, 0); |
k = llDeleteSubList(k, 0, 0); |
v = llDeleteSubList(v, 0, 0); |
} while(llGetListLength(k) != 0); |
return llDumpList2String(data, "&"); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
integer wasListCountExclude(list input, list exclude) { |
if(llGetListLength(input) == 0) return 0; |
if(llListFindList(exclude, (list)llList2String(input, 0)) == -1) |
return 1 + wasListCountExclude(llDeleteSubList(input, 0, 0), exclude); |
return wasListCountExclude(llDeleteSubList(input, 0, 0), exclude); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// escapes a string in conformance with RFC1738 |
string wasURLEscape(string i) { |
string o = ""; |
do { |
string c = llGetSubString(i, 0, 0); |
i = llDeleteSubString(i, 0, 0); |
if(c == "") jump continue; |
if(c == " ") { |
o += "+"; |
jump continue; |
} |
if(c == "\n") { |
o += "%0D" + llEscapeURL(c); |
jump continue; |
} |
o += llEscapeURL(c); |
@continue; |
} while(i != ""); |
return o; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// unescapes a string in conformance with RFC1738 |
string wasURLUnescape(string i) { |
return llUnescapeURL( |
llDumpList2String( |
llParseString2List( |
llDumpList2String( |
llParseString2List( |
i, |
["+"], |
[] |
), |
" " |
), |
["%0D%0A"], |
[] |
), |
"\n" |
) |
); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
list wasCSVToList(string csv) { |
list l = []; |
list s = []; |
string m = ""; |
do { |
string a = llGetSubString(csv, 0, 0); |
csv = llDeleteSubString(csv, 0, 0); |
if(a == ",") { |
if(llList2String(s, -1) != "\"") { |
l += m; |
m = ""; |
jump continue; |
} |
m += a; |
jump continue; |
} |
if(a == "\"" && llGetSubString(csv, 0, 0) == a) { |
m += a; |
csv = llDeleteSubString(csv, 0, 0); |
jump continue; |
} |
if(a == "\"") { |
if(llList2String(s, -1) != a) { |
s += a; |
jump continue; |
} |
s = llDeleteSubList(s, -1, -1); |
jump continue; |
} |
m += a; |
@continue; |
} while(csv != ""); |
// postcondition: length(s) = 0 |
return l + m; |
} |
|
// corrade data |
string CORRADE = ""; |
string GROUP = ""; |
string PASSWORD = ""; |
list RESPONSE = []; |
|
// for holding the callback URL |
string callback = ""; |
|
// for notecard reading |
integer line = 0; |
|
// key-value data will be read into this list |
list tuples = []; |
|
default { |
state_entry() { |
if(llGetInventoryType("configuration") != INVENTORY_NOTECARD) { |
llOwnerSay("Sorry, could not find a configuration inventory notecard."); |
return; |
} |
// DEBUG |
llOwnerSay("Reading configuration file..."); |
llGetNotecardLine("configuration", line); |
} |
dataserver(key id, string data) { |
if(data == EOF) { |
// invariant, length(tuples) % 2 == 0 |
if(llGetListLength(tuples) % 2 != 0) { |
llOwnerSay("Error in configuration notecard."); |
return; |
} |
CORRADE = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"corrade" |
] |
) |
+1); |
if(CORRADE == "") { |
llOwnerSay("Error in configuration notecard: corrade"); |
return; |
} |
GROUP = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"group" |
] |
) |
+1); |
if(GROUP == "") { |
llOwnerSay("Error in configuration notecard: password"); |
return; |
} |
PASSWORD = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"password" |
] |
) |
+1); |
if(PASSWORD == "") { |
llOwnerSay("Error in configuration notecard: password"); |
return; |
} |
RESPONSE = wasCSVToList( |
llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"response" |
] |
) |
+1) |
); |
if(llGetListLength(RESPONSE) == 0) { |
llOwnerSay("Error in configuration notecard: response"); |
return; |
} |
// DEBUG |
llOwnerSay("Read configuration notecard..."); |
state url; |
} |
if(data == "") jump continue; |
integer i = llSubStringIndex(data, "#"); |
if(i != -1) data = llDeleteSubString(data, i, -1); |
list o = llParseString2List(data, ["="], []); |
// get rid of starting and ending quotes |
string k = llDumpList2String( |
llParseString2List( |
llStringTrim( |
llList2String( |
o, |
0 |
), |
STRING_TRIM), |
["\""], [] |
), "\""); |
string v = llDumpList2String( |
llParseString2List( |
llStringTrim( |
llList2String( |
o, |
1 |
), |
STRING_TRIM), |
["\""], [] |
), "\""); |
if(k == "" || v == "") jump continue; |
tuples += k; |
tuples += v; |
@continue; |
llGetNotecardLine("configuration", ++line); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state url { |
state_entry() { |
// DEBUG |
llOwnerSay("Requesting URL..."); |
llRequestURL(); |
} |
http_request(key id, string method, string body) { |
if(method != URL_REQUEST_GRANTED) return; |
callback = body; |
// DEBUG |
llOwnerSay("Got URL..."); |
state detect; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state detect { |
state_entry() { |
// DEBUG |
llOwnerSay("Detecting if Corrade is online..."); |
llSetTimerEvent(1); |
} |
timer() { |
llRequestAgentData((key)CORRADE, DATA_ONLINE); |
} |
dataserver(key id, string data) { |
if(data != "1") { |
// DEBUG |
llOwnerSay("Corrade is not online, sleeping..."); |
llSetTimerEvent(5); |
return; |
} |
state notify; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state notify { |
state_entry() { |
// DEBUG |
llOwnerSay("Binding to the dialog notification..."); |
llInstantMessage( |
(key)CORRADE, |
wasKeyValueEncode( |
[ |
"command", "notify", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"action", "set", |
"type", "dialog", |
"URL", wasURLEscape(callback), |
"callback", wasURLEscape(callback) |
] |
) |
); |
llSetTimerEvent(1); |
} |
timer() { |
llRequestAgentData((key)CORRADE, DATA_ONLINE); |
} |
dataserver(key id, string data) { |
if(data != "1") state detect; |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) != "notify" || |
wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Failed to bind to the dialog notification..."); |
state detect; |
} |
// DEBUG |
llOwnerSay("Dialog notification installed..."); |
state main; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state main { |
state_entry() { |
// DEBUG |
llOwnerSay("Waiting for dialog..."); |
llSetTimerEvent(1); |
} |
timer() { |
llRequestAgentData((key)CORRADE, DATA_ONLINE); |
} |
dataserver(key id, string data) { |
if(data != "1") state detect; |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
|
// get the dialog id |
key id = (key)wasURLUnescape( |
wasKeyValueGet( |
"id", |
body |
) |
); |
|
// get the UUID of the object sending the dialog |
key item = (key)wasURLUnescape( |
wasKeyValueGet( |
"item", |
body |
) |
); |
|
// get the channel that the dialog was sent on |
integer channel = (integer)wasURLUnescape( |
wasKeyValueGet( |
"channel", |
body |
) |
); |
|
// get the buttons that the dialog has |
list buttons = llList2ListStrided( |
llDeleteSubList( |
wasCSVToList( |
wasURLUnescape( |
wasKeyValueGet( |
"button", |
body |
) |
) |
), |
0, |
1 |
), |
0, |
-1, |
2 |
); |
|
// now find the index of the button in our list of responses |
integer i = llGetListLength(RESPONSE)-1; |
integer index = -1; |
string button = ""; |
do { |
button = llList2String(RESPONSE, i); |
index = llListFindList(buttons, (list)button); |
if(index != -1) jump found; |
} while(--i>-1); |
return; |
|
@found; |
|
llInstantMessage(CORRADE, |
wasKeyValueEncode( |
[ |
"command", "replytoscriptdialog", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"action", "reply", |
"dialog", id, |
"button", wasURLEscape(button), |
"index", index |
] |
) |
); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
/sit-and-animate/corrade-interface.lsl |
@@ -0,0 +1,396 @@ |
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// |
// This is an automatic teleporter, sitter and animator for the Corrade |
// Second Life / OpenSim bot. You can find more details about the bot |
// by following the URL: http://was.fm/secondlife/scripted_agents/corrade |
// |
// The sit script works together with a "configuration" notecard and an |
// animation that must both be placed in the same primitive as this script. |
// The purpose of this script is to demonstrate sitting with Corrade and |
// you are free to use, change, and commercialize it under the GNU/GPLv3 |
// license at: http://www.gnu.org/licenses/gpl.html |
// |
/////////////////////////////////////////////////////////////////////////// |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueGet(string k, string data) { |
if(llStringLength(data) == 0) return ""; |
if(llStringLength(k) == 0) return ""; |
list a = llParseString2List(data, ["&", "="], []); |
integer i = llListFindList(llList2ListStrided(a, 0, -1, 2), [ k ]); |
if(i != -1) return llList2String(a, 2*i+1); |
return ""; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueEncode(list data) { |
list k = llList2ListStrided(data, 0, -1, 2); |
list v = llList2ListStrided(llDeleteSubList(data, 0, 0), 0, -1, 2); |
data = []; |
do { |
data += llList2String(k, 0) + "=" + llList2String(v, 0); |
k = llDeleteSubList(k, 0, 0); |
v = llDeleteSubList(v, 0, 0); |
} while(llGetListLength(k) != 0); |
return llDumpList2String(data, "&"); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2011 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
vector wasCirclePoint(float radius) { |
float x = llPow(-1, 1 + (integer) llFrand(2)) * llFrand(radius*2); |
float y = llPow(-1, 1 + (integer) llFrand(2)) * llFrand(radius*2); |
if(llPow(x,2) + llPow(y,2) <= llPow(radius,2)) |
return <x, y, 0>; |
return wasCirclePoint(radius); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// escapes a string in conformance with RFC1738 |
string wasURLEscape(string i) { |
string o = ""; |
do { |
string c = llGetSubString(i, 0, 0); |
i = llDeleteSubString(i, 0, 0); |
if(c == "") jump continue; |
if(c == " ") { |
o += "+"; |
jump continue; |
} |
if(c == "\n") { |
o += "%0D" + llEscapeURL(c); |
jump continue; |
} |
o += llEscapeURL(c); |
@continue; |
} while(i != ""); |
return o; |
} |
|
// corrade data |
string CORRADE = ""; |
string GROUP = ""; |
string PASSWORD = ""; |
|
// for holding the callback URL |
string callback = ""; |
|
// for notecard reading |
integer line = 0; |
|
// key-value data will be read into this list |
list tuples = []; |
|
default { |
state_entry() { |
if(llGetInventoryType("configuration") != INVENTORY_NOTECARD) { |
llOwnerSay("Sorry, could not find an inventory notecard."); |
return; |
} |
// DEBUG |
llOwnerSay("Reading configuration file..."); |
llGetNotecardLine("configuration", line); |
} |
dataserver(key id, string data) { |
if(data == EOF) { |
// invariant, length(tuples) % 2 == 0 |
if(llGetListLength(tuples) % 2 != 0) { |
llOwnerSay("Error in configuration notecard."); |
return; |
} |
CORRADE = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"corrade" |
] |
) |
+1); |
if(CORRADE == "") { |
llOwnerSay("Error in configuration notecard: corrade"); |
return; |
} |
GROUP = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"group" |
] |
) |
+1); |
if(GROUP == "") { |
llOwnerSay("Error in configuration notecard: group"); |
return; |
} |
PASSWORD = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"password" |
] |
) |
+1); |
if(PASSWORD == "") { |
llOwnerSay("Error in configuration notecard: password"); |
return; |
} |
// DEBUG |
llOwnerSay("Read configuration file..."); |
state url; |
} |
if(data == "") jump continue; |
integer i = llSubStringIndex(data, "#"); |
if(i != -1) data = llDeleteSubString(data, i, -1); |
list o = llParseString2List(data, ["="], []); |
// get rid of starting and ending quotes |
string k = llDumpList2String( |
llParseString2List( |
llStringTrim( |
llList2String( |
o, |
0 |
), |
STRING_TRIM), |
["\""], [] |
), "\""); |
string v = llDumpList2String( |
llParseString2List( |
llStringTrim( |
llList2String( |
o, |
1 |
), |
STRING_TRIM), |
["\""], [] |
), "\""); |
if(k == "" || v == "") jump continue; |
tuples += k; |
tuples += v; |
@continue; |
llGetNotecardLine("configuration", ++line); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state url { |
state_entry() { |
// DEBUG |
llOwnerSay("Requesting URL..."); |
llRequestURL(); |
} |
http_request(key id, string method, string body) { |
if(method != URL_REQUEST_GRANTED) return; |
callback = body; |
// DEBUG |
llOwnerSay("Got URL..."); |
state detect; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state detect { |
state_entry() { |
// DEBUG |
llOwnerSay("Detecting if Corrade is online..."); |
llSetTimerEvent(5); |
} |
timer() { |
llRequestAgentData((key)CORRADE, DATA_ONLINE); |
} |
dataserver(key id, string data) { |
if(data != "1") { |
// DEBUG |
llOwnerSay("Corrade is not online, sleeping..."); |
llSetTimerEvent(30); |
return; |
} |
llSensorRepeat("", (key)CORRADE, AGENT, 10, TWO_PI, 1); |
} |
no_sensor() { |
// DEBUG |
llOwnerSay("Teleporting Corrade..."); |
llInstantMessage((key)CORRADE, |
wasKeyValueEncode( |
[ |
"command", "teleport", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"entity", "region", |
"region", wasURLEscape(llGetRegionName()), |
"position", wasURLEscape( |
(string)( |
llGetPos() + wasCirclePoint(1) |
) |
), |
"callback", callback |
] |
) |
); |
} |
sensor(integer num) { |
llSetTimerEvent(0); |
state notify; |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) != "teleport" || |
wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Teleport failed..."); |
return; |
} |
llSetTimerEvent(0); |
state main; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state notify { |
state_entry() { |
// DEBUG |
llOwnerSay("Binding to the permission Corrade notification..."); |
llInstantMessage( |
(key)CORRADE, |
wasKeyValueEncode( |
[ |
"command", "notify", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"action", "add", |
"type", "permission", |
"URL", wasURLEscape(callback), |
"callback", wasURLEscape(callback) |
] |
) |
); |
llSetTimerEvent(60); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) != "notify" || |
wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Failed to bind to the permission notification..."); |
state detect; |
} |
// DEBUG |
llOwnerSay("Permission notification installed..."); |
llSetTimerEvent(0); |
state main; |
} |
timer() { |
llSetTimerEvent(0); |
// DEBUG |
llOwnerSay("Timeout binding to permission notification..."); |
state detect; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state main { |
state_entry() { |
// DEBUG |
llOwnerSay("Waiting..."); |
llSensorRepeat("", (key)CORRADE, AGENT, 10, TWO_PI, 1); |
llSetTimerEvent(60); |
} |
sensor(integer num) { |
// Corrade is already sitting. Do nothing. |
if(llAvatarOnSitTarget() == (key)CORRADE) return; |
// DEBUG |
llOwnerSay("Sending sit command..."); |
llInstantMessage((key)CORRADE, |
wasKeyValueEncode( |
[ |
"command", "sit", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"item", wasURLEscape( |
llGetKey() |
), |
"range", 10 |
] |
) |
); |
llSensorRepeat("", (key)CORRADE, AGENT, 10, TWO_PI, 10); |
} |
no_sensor() { |
llSensorRemove(); |
state detect; |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("type", body) != "permission" || |
wasKeyValueGet("permissions", body) != "TriggerAnimation") return; |
// DEBUG |
llOwnerSay("Corrade received the permission request to trigger an animation, replying..."); |
llInstantMessage((key)CORRADE, |
wasKeyValueEncode( |
[ |
"command", "replytoscriptpermissionrequest", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"action", "reply", |
"item", wasURLEscape(wasKeyValueGet("item", body)), |
"task", wasURLEscape(wasKeyValueGet("task", body)), |
"permissions", "TriggerAnimation", |
"region", wasURLEscape(wasKeyValueGet("region", body)) |
] |
) |
); |
} |
timer() { |
if(llAvatarOnSitTarget() == (key)CORRADE) return; |
// DEBUG |
llOwnerSay("Timeout during sit... Restarting..."); |
state detect; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
/texture-changing/texture-changing.lsl |
@@ -0,0 +1,664 @@ |
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// |
// This is a script that uses Corrade to change the texture on the |
// available faces of the primitie. It requires that you have a Corrade bot |
// with the following permissions enabled for the group specified in the |
// configuration notecard inside this primitive: |
// |
// config -> groups -> group [your group] -> permissions -> interact |
// config -> groups -> group [your group] -> permissions -> friendship |
// |
// Additionally, you should be a friend of your bot and have granted modify |
// permissions to the bot by using the viewer interface: |
// |
// Contact -> Friends -> [ Bot ] -> Friend can edit, delete, take objects. |
// |
// The sit script works together with a "configuration" notecard that must |
// be placed in the same primitive as this script. The purpose of this |
// script is to demonstrate changing textures with Corrade and you are free |
// to use, change, and commercialize it under the GNU/GPLv3 license at: |
// http://www.gnu.org/licenses/gpl.html |
// |
/////////////////////////////////////////////////////////////////////////// |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueGet(string k, string data) { |
if(llStringLength(data) == 0) return ""; |
if(llStringLength(k) == 0) return ""; |
list a = llParseString2List(data, ["&", "="], []); |
integer i = llListFindList(llList2ListStrided(a, 0, -1, 2), [ k ]); |
if(i != -1) return llList2String(a, 2*i+1); |
return ""; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueEncode(list data) { |
list k = llList2ListStrided(data, 0, -1, 2); |
list v = llList2ListStrided(llDeleteSubList(data, 0, 0), 0, -1, 2); |
data = []; |
do { |
data += llList2String(k, 0) + "=" + llList2String(v, 0); |
k = llDeleteSubList(k, 0, 0); |
v = llDeleteSubList(v, 0, 0); |
} while(llGetListLength(k) != 0); |
return llDumpList2String(data, "&"); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2011 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
vector wasCirclePoint(float radius) { |
float x = llPow(-1, 1 + (integer) llFrand(2)) * llFrand(radius*2); |
float y = llPow(-1, 1 + (integer) llFrand(2)) * llFrand(radius*2); |
if(llPow(x,2) + llPow(y,2) <= llPow(radius,2)) |
return <x, y, 0>; |
return wasCirclePoint(radius); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// escapes a string in conformance with RFC1738 |
string wasURLEscape(string i) { |
string o = ""; |
do { |
string c = llGetSubString(i, 0, 0); |
i = llDeleteSubString(i, 0, 0); |
if(c == "") jump continue; |
if(c == " ") { |
o += "+"; |
jump continue; |
} |
if(c == "\n") { |
o += "%0D" + llEscapeURL(c); |
jump continue; |
} |
o += llEscapeURL(c); |
@continue; |
} while(i != ""); |
return o; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// unescapes a string in conformance with RFC1738 |
string wasURLUnescape(string i) { |
return llUnescapeURL( |
llDumpList2String( |
llParseString2List( |
llDumpList2String( |
llParseString2List( |
i, |
["+"], |
[] |
), |
" " |
), |
["%0D%0A"], |
[] |
), |
"\n" |
) |
); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
list wasCSVToList(string csv) { |
list l = []; |
list s = []; |
string m = ""; |
do { |
string a = llGetSubString(csv, 0, 0); |
csv = llDeleteSubString(csv, 0, 0); |
if(a == ",") { |
if(llList2String(s, -1) != "\"") { |
l += m; |
m = ""; |
jump continue; |
} |
m += a; |
jump continue; |
} |
if(a == "\"" && llGetSubString(csv, 0, 0) == a) { |
m += a; |
csv = llDeleteSubString(csv, 0, 0); |
jump continue; |
} |
if(a == "\"") { |
if(llList2String(s, -1) != a) { |
s += a; |
jump continue; |
} |
s = llDeleteSubList(s, -1, -1); |
jump continue; |
} |
m += a; |
@continue; |
} while(csv != ""); |
// postcondition: length(s) = 0 |
return l + m; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasListToCSV(list l) { |
list v = []; |
do { |
string a = llDumpList2String( |
llParseStringKeepNulls( |
llList2String( |
l, |
0 |
), |
["\""], |
[] |
), |
"\"\"" |
); |
if(llParseStringKeepNulls( |
a, |
[" ", ",", "\n", "\""], [] |
) != |
(list) a |
) a = "\"" + a + "\""; |
v += a; |
l = llDeleteSubList(l, 0, 0); |
} while(l != []); |
return llDumpList2String(v, ","); |
} |
|
// corrade data |
string CORRADE = ""; |
string GROUP = ""; |
string PASSWORD = ""; |
|
// for holding the callback URL |
string callback = ""; |
|
// for holding the selected face number |
string FACE = ""; |
// for holding the selected texture |
string TEXTURE = ""; |
// for holding the current user |
key TOUCH = NULL_KEY; |
|
// for notecard reading |
integer line = 0; |
|
// key-value data will be read into this list |
list tuples = []; |
|
default { |
state_entry() { |
if(llGetInventoryType("configuration") != INVENTORY_NOTECARD) { |
llOwnerSay("Sorry, could not find an inventory notecard."); |
return; |
} |
// DEBUG |
llOwnerSay("Reading configuration file..."); |
llGetNotecardLine("configuration", line); |
} |
dataserver(key id, string data) { |
if(data == EOF) { |
// invariant, length(tuples) % 2 == 0 |
if(llGetListLength(tuples) % 2 != 0) { |
llOwnerSay("Error in configuration notecard."); |
return; |
} |
CORRADE = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"corrade" |
] |
) |
+1); |
if(CORRADE == "") { |
llOwnerSay("Error in configuration notecard: corrade"); |
return; |
} |
GROUP = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"group" |
] |
) |
+1); |
if(GROUP == "") { |
llOwnerSay("Error in configuration notecard: password"); |
return; |
} |
PASSWORD = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"password" |
] |
) |
+1); |
if(GROUP == "") { |
llOwnerSay("Error in configuration notecard: group"); |
return; |
} |
// DEBUG |
llOwnerSay("Read configuration file..."); |
state url; |
} |
if(data == "") jump continue; |
integer i = llSubStringIndex(data, "#"); |
if(i != -1) data = llDeleteSubString(data, i, -1); |
list o = llParseString2List(data, ["="], []); |
// get rid of starting and ending quotes |
string k = llDumpList2String( |
llParseString2List( |
llStringTrim( |
llList2String( |
o, |
0 |
), |
STRING_TRIM), |
["\""], [] |
), "\""); |
string v = llDumpList2String( |
llParseString2List( |
llStringTrim( |
llList2String( |
o, |
1 |
), |
STRING_TRIM), |
["\""], [] |
), "\""); |
if(k == "" || v == "") jump continue; |
tuples += k; |
tuples += v; |
@continue; |
llGetNotecardLine("configuration", ++line); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state url { |
state_entry() { |
// DEBUG |
llOwnerSay("Requesting URL..."); |
llRequestURL(); |
} |
http_request(key id, string method, string body) { |
if(method != URL_REQUEST_GRANTED) return; |
callback = body; |
// DEBUG |
llOwnerSay("Got URL..."); |
state detect; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state detect { |
state_entry() { |
// DEBUG |
llOwnerSay("Detecting if Corrade is online..."); |
llSetTimerEvent(5); |
} |
timer() { |
llRequestAgentData((key)CORRADE, DATA_ONLINE); |
} |
dataserver(key id, string data) { |
if(data != "1") { |
// DEBUG |
llOwnerSay("Corrade is not online, sleeping..."); |
llSetTimerEvent(30); |
return; |
} |
llSensorRepeat("", (key)CORRADE, AGENT, 10, TWO_PI, 1); |
} |
no_sensor() { |
// DEBUG |
llOwnerSay("Teleporting Corrade..."); |
llInstantMessage((key)CORRADE, |
wasKeyValueEncode( |
[ |
"command", "teleport", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"entity", "region", |
"region", wasURLEscape(llGetRegionName()), |
"position", wasURLEscape( |
(string)( |
llGetPos() + wasCirclePoint(1) |
) |
), |
"callback", callback |
] |
) |
); |
} |
sensor(integer num) { |
llSetTimerEvent(0); |
state check_rights; |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) != "teleport" || |
wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Teleport failed: " + |
wasURLUnescape( |
wasKeyValueGet( |
"error", |
body |
) |
) |
); |
state detect_trampoline; |
} |
llSetTimerEvent(0); |
state check_rights; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
/* |
* Trampoline used for delaying teleport requests sent to Corrade. Once code |
* switches into this state, the script waits for some seconds and then |
* switches to the detect state. |
*/ |
state detect_trampoline { |
state_entry() { |
// DEBUG |
llOwnerSay("Sleeping..."); |
llSetTimerEvent(30); |
} |
timer() { |
llSetTimerEvent(0); |
state detect; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
/* |
* This state checks to see whether Corrade has the necessary rights to |
* modify your objects. It checks by using the "getfrienddata" command |
* which requires "friendship" Corrade permissions and then tests whether |
* the "CanModifyTheirObjects" flag is set that indicates whether Corrade |
* is able to alter your objects. In the end it proceeds to the main state. |
*/ |
state check_rights { |
state_entry() { |
// DEBUG |
llOwnerSay("Checking whether Corrade has rights to modify your objects..."); |
llInstantMessage( |
(key)CORRADE, |
wasKeyValueEncode( |
[ |
"command", "getfrienddata", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"agent", wasURLEscape(llGetOwner()), |
"data", "CanModifyTheirObjects", |
"callback", wasURLEscape(callback) |
] |
) |
); |
llSetTimerEvent(60); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) != "getfrienddata" || |
wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Failed to get friend rights: " + |
wasURLUnescape( |
wasKeyValueGet( |
"error", |
body |
) |
) |
); |
state detect_trampoline; |
} |
// DEBUG |
llOwnerSay("Got friend rights, checking..."); |
list data = wasCSVToList(wasURLUnescape(wasKeyValueGet("data", body))); |
integer i = llListFindList(data, [ "CanModifyTheirObjects" ]); |
if(i == -1) { |
// DEBUG |
llOwnerSay("Friend data not returned by the \"getfrienddata\" command..."); |
state detect_trampoline; |
} |
if(llList2String(data, i+1) != "True") { |
// DEBUG |
llOwnerSay("Corrade cannot modify your objects, please grant Corrade permissions to modify your objects using your viewer..."); |
state detect_trampoline; |
} |
// DEBUG |
llOwnerSay("Corrade has permissions to modify your objects, proceeding..."); |
llSetTimerEvent(0); |
state select_face; |
} |
timer() { |
llSetTimerEvent(0); |
// DEBUG |
llOwnerSay("Timeout checking for friend rights..."); |
state detect_trampoline; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
/* |
* In this state we retrieve a face number from the user. |
*/ |
state select_face { |
state_entry() { |
if(TOUCH == NULL_KEY) { |
//DEBUG |
llOwnerSay("Please touch the primitive for a menu..."); |
return; |
} |
integer comChannel = (integer)("0x8" + llGetSubString(llGetKey(), 0, 6)); |
integer i = llGetNumberOfSides() - 1; |
list buttons = []; |
do { |
buttons += (string)i; |
llListen(comChannel, "", "", (string)i); |
} while(--i > -1); |
buttons += "default"; |
llListen(comChannel, "", "", "default"); |
buttons += "all"; |
llListen(comChannel, "", "", "all"); |
llDialog(TOUCH, "Please select a face to change...", buttons, comChannel); |
} |
touch_start(integer num) { |
TOUCH = llDetectedKey(0); |
integer comChannel = (integer)("0x8" + llGetSubString(llGetKey(), 0, 6)); |
integer i = llGetNumberOfSides() - 1; |
list buttons = []; |
do { |
buttons += (string)i; |
llListen(comChannel, "", "", (string)i); |
} while(--i > -1); |
buttons += "default"; |
llListen(comChannel, "", "", "default"); |
buttons += "all"; |
llListen(comChannel, "", "", "all"); |
llDialog(TOUCH, "Please select a face to change...", buttons, comChannel); |
} |
listen(integer channel, string name, key id, string face) { |
llOwnerSay("Got face: " + face); |
FACE = face; |
state select_texture; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
/* |
* In this state we let the user select a texture from inventory. |
*/ |
state select_texture { |
state_entry() { |
if(TOUCH == NULL_KEY) return; |
integer comChannel = (integer)("0x8" + llGetSubString(llGetKey(), 0, 6)); |
integer i = llGetInventoryNumber(INVENTORY_TEXTURE) -1; |
list buttons = []; |
do { |
string inventoryTexture = llGetInventoryName(INVENTORY_TEXTURE, i); |
buttons += inventoryTexture; |
llListen(comChannel, "", "", inventoryTexture); |
} while(--i > -1); |
llDialog(TOUCH, "Please select from the avilable textures...", buttons, comChannel); |
} |
touch_start(integer num) { |
TOUCH = llDetectedKey(0); |
integer comChannel = (integer)("0x8" + llGetSubString(llGetKey(), 0, 6)); |
integer i = llGetInventoryNumber(INVENTORY_TEXTURE) -1; |
list buttons = []; |
do { |
string inventoryTexture = llGetInventoryName(INVENTORY_TEXTURE, i); |
buttons += inventoryTexture; |
llListen(comChannel, "", "", inventoryTexture); |
} while(--i > -1); |
llDialog(TOUCH, "Please select from the avilable textures...", buttons, comChannel); |
} |
listen(integer channel, string name, key id, string inventoryTexture) { |
llOwnerSay("Got texture: " + inventoryTexture); |
TEXTURE = inventoryTexture; |
state change_texture; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
/* |
* This state is reponsible for setting the texture on the previously |
* selected face of this primitive. It makes a call to Corrade to set the |
* texture on the selected face and then returns to the face selection. |
* This state relies on the "interact" Corrade permission being set |
* for the group that you are using this script with. |
*/ |
state change_texture { |
state_entry() { |
// DEBUG |
llOwnerSay("Setting texture \"" + TEXTURE + "\" on face \"" + FACE + "\"."); |
llInstantMessage(CORRADE, wasKeyValueEncode( |
[ |
// set the texture for the 4th face |
// on a primitive in a 4m range |
// specified by UUID |
"command", "setprimitivetexturedata", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
// the UUID of the primitive |
"item", wasURLEscape(llGetKey()), |
"range", 5, |
// the face |
"face", FACE, |
// just set the texture to a texture UUID |
"data", wasURLEscape( |
wasListToCSV( |
[ |
"TextureID", llGetInventoryKey(TEXTURE) |
] |
) |
), |
"callback", wasURLEscape(callback) |
] |
) |
); |
llSetTimerEvent(60); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) != "setprimitivetexturedata" || |
wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Failed to set texture: " + |
wasURLUnescape( |
wasKeyValueGet( |
"error", |
body |
) |
) |
); |
state detect_trampoline; |
} |
// DEBUG |
llOwnerSay("Texture set successfully..."); |
state select_face; |
} |
timer() { |
llSetTimerEvent(0); |
// DEBUG |
llOwnerSay("Timeout setting texture..."); |
state detect_trampoline; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
/visitor-track-and-record/XyzzyText.lsl |
@@ -0,0 +1,711 @@ |
//////////////////////////////////////////// |
// XyzzyText v2.1(10-Char) by Thraxis Epsilon |
// XyzzyText v2.1 Script (Set Line Color) by Huney Jewell |
// XyzzyText v2.0 Script (5 Face, Single Texture) |
// |
// Edited trivially by Joel Cloquet on 1/2011 to use the (relatively) |
// new llSetLinkPrimitiveParamsFast function, thereby removing the need in |
// some cases to use the slave script. |
// |
// Heavily Modified by Thraxis Epsilon, Gigs Taggart 5/2007 and Strife Onizuka 8/2007 |
// Rewrite to allow one-script-per-object operation w/ optional slaves |
// Enable prim-label functionality |
// Enabled Banking |
// Enabled 10-char per prim |
// |
// Modified by Kermitt Quirk 19/01/2006 |
// To add support for 5 face prim instead of 3 |
// |
// Core XyText Originally Written by Xylor Baysklef |
// |
// |
//////////////////////////////////////////// |
|
/////////////// CONSTANTS /////////////////// |
// XyText Message Map. |
integer DISPLAY_STRING = 204000; |
integer DISPLAY_EXTENDED = 204001; |
integer REMAP_INDICES = 204002; |
integer RESET_INDICES = 204003; |
integer SET_FADE_OPTIONS = 204004; |
integer SET_FONT_TEXTURE = 204005; |
integer SET_LINE_COLOR = 204006; |
integer SET_COLOR = 204007; |
integer RESCAN_LINKSET = 204008; |
|
//internal API |
integer REGISTER_SLAVE = 205000; |
integer SLAVE_RECOGNIZED = 205001; |
integer SLAVE_DISPLAY = 205003; |
integer SLAVE_DISPLAY_EXTENDED = 205004; |
integer SLAVE_RESET = 205005; |
|
// This is an extended character escape sequence. |
string ESCAPE_SEQUENCE = "\\e"; |
|
// This is used to get an index for the extended character. |
string EXTENDED_INDEX = "123456789abcdef"; |
|
|
// Face numbers. |
integer FACE_1 = 3; |
integer FACE_2 = 7; |
integer FACE_3 = 4; |
integer FACE_4 = 6; |
integer FACE_5 = 1; |
|
// Used to hide the text after a fade-out. |
key TRANSPARENT = "701917a8-d614-471f-13dd-5f4644e36e3c"; |
key null_key = NULL_KEY; |
|
// This is a list of textures for all 2-character combinations. |
list CHARACTER_GRID = [ |
"00e9f9f7-0669-181c-c192-7f8e67678c8d", |
"347a5cb6-0031-7ec0-2fcf-f298eebf3c0e", |
"4e7e689e-37f1-9eca-8596-a958bbd23963", |
"19ea9c21-67ba-8f6f-99db-573b1b877eb1", |
"dde7b412-cda1-652f-6fc2-73f4641f96e1", |
"af6fa3bb-3a6c-9c4f-4bf5-d1c126c830da", |
"a201d3a2-364b-43b6-8686-5881c0f82a94", |
"b674dec8-fead-99e5-c28d-2db8e4c51540", |
"366e05f3-be6b-e5cf-c33b-731dff649caa", |
"75c4925c-0427-dc0c-c71c-e28674ff4d27", |
"dcbe166b-6a97-efb2-fc8e-e5bc6a8b1be6", |
"0dca2feb-fc66-a762-db85-89026a4ecd68", |
"a0fca76f-503a-946b-9336-0a918e886f7a", |
"67fb375d-89a1-5a4f-8c7a-0cd1c066ffc4", |
"300470b2-da34-5470-074c-1b8464ca050c", |
"d1f8e91c-ce2b-d85e-2120-930d3b630946", |
"2a190e44-7b29-dadb-0bff-c31adaf5a170", |
"75d55e71-f6f8-9835-e746-a45f189f30a1", |
"300fac33-2b30-3da3-26bc-e2d70428ec19", |
"0747c776-011a-53ce-13ee-8b5bb9e87c1e", |
"85a855c3-a94f-01ca-33e0-7dde92e727e2", |
"cbc1dab2-2d61-2986-1949-7a5235c954e1", |
"f7aef047-f266-9596-16df-641010edd8e1", |
"4c34ebf7-e5e1-2e1a-579f-e224d9d5e71b", |
"4a69e98c-26a5-ad05-e92e-b5b906ad9ef9", |
"462a9226-2a97-91ac-2d89-57ab33334b78", |
"20b24b3a-8c57-82ee-c6ed-555003f5dbcd", |
"9b481daa-9ea8-a9fa-1ee4-ab9a0d38e217", |
"c231dbdc-c842-15b0-7aa6-6da14745cfdc", |
"c97e3cbb-c9a3-45df-a0ae-955c1f4bf9cf", |
"f1e7d030-ff80-a242-cb69-f6951d4eae3b", |
"ed32d6c4-d733-c0f1-f242-6df1d222220d", |
"88f96a30-dccf-9b20-31ef-da0dfeb23c72", |
"252f2595-58b8-4bcc-6515-fa274d0cfb65", |
"f2838c4f-de80-cced-dff8-195dfdf36b2c", |
"cc2594fe-add2-a3df-cdb3-a61711badf53", |
"e0ce2972-da00-955c-129e-3289b3676776", |
"3e0d336d-321f-ddfa-5c1b-e26131766f6a", |
"d43b1dc4-6b51-76a7-8b90-38865b82bf06", |
"06d16cbb-1868-fd1d-5c93-eae42164a37d", |
"dd5d98cf-273e-3fd0-f030-48be58ee3a0b", |
"0e47c89e-de4a-6233-a2da-cb852aad1b00", |
"fb9c4a55-0e13-495b-25c4-f0b459dc06de", |
"e3ce8def-312c-735b-0e48-018b6799c883", |
"2f713216-4e71-d123-03ed-9c8554710c6b", |
"4a417d8a-1f4f-404b-9783-6672f8527911", |
"ca5e21ec-5b20-5909-4c31-3f90d7316b33", |
"06a4fcc3-e1c4-296d-8817-01f88fbd7367", |
"130ac084-6f3c-95de-b5b6-d25c80703474", |
"59d540a0-ae9d-3606-5ae0-4f2842b64cfa", |
"8612ae9a-f53c-5bf4-2899-8174d7abc4fd", |
"12467401-e979-2c49-34e0-6ac761542797", |
"d53c3eaa-0404-3860-0675-3e375596c3e3", |
"9f5b26bd-81d3-b25e-62fe-5b671d1e3e79", |
"f57f0b64-a050-d617-ee00-c8e9e3adc9cb", |
"beff166a-f5f3-f05e-e020-98f2b00e27ed", |
"02278a65-94ba-6d5e-0d2b-93f2e4f4bf70", |
"a707197d-449e-5b58-846c-0c850c61f9d6", |
"021d4b1a-9503-a44f-ee2b-976eb5d80e68", |
"0ae2ffae-7265-524d-cb76-c2b691992706"]; |
list CHARACTER_GRID2 = [ |
"f6e41cf2-1104-bd0b-0190-dffad1bac813", |
"2b4bb15e-956d-56ae-69f5-d26a20de0ce7", |
"f816da2c-51f1-612a-2029-a542db7db882", |
"345fea05-c7be-465c-409f-9dcb3bd2aa07", |
"b3017e02-c063-5185-acd5-1ef5f9d79b89", |
"4dcff365-1971-3c2b-d73c-77e1dc54242a" |
]; |
|
///////////// END CONSTANTS //////////////// |
|
///////////// GLOBAL VARIABLES /////////////// |
// All displayable characters. Default to ASCII order. |
string gCharIndex; |
// This is the channel to listen on while acting |
// as a cell in a larger display. |
integer gCellChannel = -1; |
// This is the starting character position in the cell channel message |
// to render. |
integer gCellCharPosition = 0; |
// This is whether or not to use the fade in/out special effect. |
integer gCellUseFading = FALSE; |
// This is how long to display the text before fading out (if using |
// fading special effect). |
// Note: < 0 means don't fade out. |
float gCellHoldDelay = 1.0; |
|
integer gSlaveRegistered; |
list gSlaveNames; |
|
integer BANK_STRIDE=3; //offset, length, highest_dirty |
list gBankingData; |
|
/////////// END GLOBAL VARIABLES //////////// |
|
ResetCharIndex() { |
gCharIndex = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`"; |
// \" <-- Fixes LSL syntax highlighting bug. |
gCharIndex += "abcdefghijklmnopqrstuvwxyz{|}~"; |
gCharIndex += "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"; |
} |
|
vector GetGridPos(integer index1, integer index2) { |
// There are two ways to use the lookup table... |
integer Col; |
integer Row; |
if (index1 >= index2) { |
// In this case, the row is the index of the first character: |
Row = index1; |
// And the col is the index of the second character (x2) |
Col = index2 * 2; |
} |
else { // Index1 < Index2 |
// In this case, the row is the index of the second character: |
Row = index2; |
// And the col is the index of the first character, x2, offset by 1. |
Col = index1 * 2 + 1; |
} |
return <Col, Row, 0>; |
} |
|
string GetGridTexture(vector grid_pos) { |
// Calculate the texture in the grid to use. |
integer GridCol = llRound(grid_pos.x) / 20; |
integer GridRow = llRound(grid_pos.y) / 10; |
|
// Lookup the texture. |
key Texture = llList2Key(CHARACTER_GRID, GridRow * (GridRow + 1) / 2 + GridCol); |
return Texture; |
} |
|
vector GetGridOffset(vector grid_pos) { |
// Zoom in on the texture showing our character pair. |
integer Col = llRound(grid_pos.x) % 20; |
integer Row = llRound(grid_pos.y) % 10; |
|
// Return the offset in the texture. |
return <-0.45 + 0.05 * Col, 0.45 - 0.1 * Row, 0.0>; |
} |
|
ShowChars(integer link,vector grid_pos1, vector grid_pos2, vector grid_pos3, vector grid_pos4, vector grid_pos5) { |
// Set the primitive textures directly. |
|
|
llSetLinkPrimitiveParamsFast( link , [ |
PRIM_TEXTURE, FACE_1, GetGridTexture(grid_pos1), <0.25, 0.1, 0>, GetGridOffset(grid_pos1) + <0.075, 0, 0>, 0.0, |
PRIM_TEXTURE, FACE_2, GetGridTexture(grid_pos2), <0.1, 0.1, 0>, GetGridOffset(grid_pos2), 0.0, |
PRIM_TEXTURE, FACE_3, GetGridTexture(grid_pos3), <-1.48, 0.1, 0>, GetGridOffset(grid_pos3)+ <0.37, 0, 0>, 0.0, |
PRIM_TEXTURE, FACE_4, GetGridTexture(grid_pos4), <0.1, 0.1, 0>, GetGridOffset(grid_pos4), 0.0, |
PRIM_TEXTURE, FACE_5, GetGridTexture(grid_pos5), <0.25, 0.1, 0>, GetGridOffset(grid_pos5) - <0.075, 0, 0>, 0.0 |
]); |
} |
|
RenderString(integer link, string str) { |
// Get the grid positions for each pair of characters. |
vector GridPos1 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)), |
llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) ); |
vector GridPos2 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)), |
llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) ); |
vector GridPos3 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)), |
llSubStringIndex(gCharIndex, llGetSubString(str, 5, 5)) ); |
vector GridPos4 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 6, 6)), |
llSubStringIndex(gCharIndex, llGetSubString(str, 7, 7)) ); |
vector GridPos5 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 8, 8)), |
llSubStringIndex(gCharIndex, llGetSubString(str, 9, 9)) ); |
|
// Use these grid positions to display the correct textures/offsets. |
ShowChars(link,GridPos1, GridPos2, GridPos3, GridPos4, GridPos5); |
} |
|
//RenderWithEffects(integer link, string str) { |
// // Get the grid positions for each pair of characters. |
// vector GridPos1 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)), |
// llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) ); |
// vector GridPos2 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)), |
// llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) ); |
// vector GridPos3 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)), |
// llSubStringIndex(gCharIndex, llGetSubString(str, 5, 5)) ); |
// vector GridPos4 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 6, 6)), |
// llSubStringIndex(gCharIndex, llGetSubString(str, 7, 7)) ); |
// vector GridPos5 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 8, 8)), |
// llSubStringIndex(gCharIndex, llGetSubString(str, 9, 9)) ); // |
// |
// // First set the alpha to the lowest possible. |
// llSetAlpha(0.05, ALL_SIDES); |
// |
// // Use these grid positions to display the correct textures/offsets. |
// ShowChars(link,GridPos1, GridPos2, GridPos3, GridPos4, GridPos5); |
// |
// float Alpha = 0.10; |
// for (; Alpha <= 1.0; Alpha += 0.05) |
// llSetAlpha(Alpha, ALL_SIDES); |
// // See if we want to fade out as well. |
// if (gCellHoldDelay < 0.0) |
// // No, bail out. (Just keep showing the string at full strength). |
// return; |
// // Hold the text for a while. |
// llSleep(gCellHoldDelay); |
// // Now fade out. |
// for (Alpha = 0.95; Alpha >= 0.05; Alpha -= 0.05) |
// llSetAlpha(Alpha, ALL_SIDES); |
// // Make the text transparent to fully hide it. |
// llSetTexture(TRANSPARENT, ALL_SIDES); |
//} |
|
integer RenderExtended(integer link, string str,integer render) { |
// Look for escape sequences. |
integer length = 0; |
list Parsed = llParseString2List(str, [], (list)ESCAPE_SEQUENCE); |
integer ParsedLen = llGetListLength(Parsed); |
|
// Create a list of index values to work with. |
list Indices; |
// We start with room for 6 indices. |
integer IndicesLeft = 10; |
|
string Token; |
integer Clipped; |
integer LastWasEscapeSequence = FALSE; |
// Work from left to right. |
integer i=0; |
for (; i < ParsedLen && IndicesLeft > 0; ++i) { |
Token = llList2String(Parsed, i); |
|
// If this is an escape sequence, just set the flag and move on. |
if (Token == ESCAPE_SEQUENCE) { |
LastWasEscapeSequence = TRUE; |
} |
else { // Token != ESCAPE_SEQUENCE |
// Otherwise this is a normal token. Check its length. |
Clipped = FALSE; |
integer TokenLength = llStringLength(Token); |
// Clip if necessary. |
if (TokenLength > IndicesLeft) { |
TokenLength = llStringLength(Token = llGetSubString(Token, 0, IndicesLeft - 1)); |
IndicesLeft = 0; |
Clipped = TRUE; |
} |
else |
IndicesLeft -= TokenLength; |
|
// Was the previous token an escape sequence? |
if (LastWasEscapeSequence) { |
// Yes, the first character is an escape character, the rest are normal. |
length += 2 + TokenLength; |
if (render) |
{ |
// This is the extended character. |
Indices += [llSubStringIndex(EXTENDED_INDEX, llGetSubString(Token, 0, 0)) + 95]; |
|
// These are the normal characters. |
integer j=1; |
for (; j < TokenLength; ++j) |
{ |
Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))]; |
} |
} |
} |
else { // Normal string. |
// Just add the characters normally. |
length += TokenLength; |
if(render) |
{ |
integer j=0; |
for (; j < TokenLength; ++j) |
{ |
Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))]; |
} |
} |
} |
|
// Unset this flag, since this was not an escape sequence. |
LastWasEscapeSequence = FALSE; |
} |
} |
|
if(render) |
{ |
// Use the indices to create grid positions. |
vector GridPos1 = GetGridPos( llList2Integer(Indices, 0), llList2Integer(Indices, 1) ); |
vector GridPos2 = GetGridPos( llList2Integer(Indices, 2), llList2Integer(Indices, 3) ); |
vector GridPos3 = GetGridPos( llList2Integer(Indices, 4), llList2Integer(Indices, 5) ); |
vector GridPos4 = GetGridPos( llList2Integer(Indices, 6), llList2Integer(Indices, 7) ); |
vector GridPos5 = GetGridPos( llList2Integer(Indices, 8), llList2Integer(Indices, 9) ); |
|
// Use these grid positions to display the correct textures/offsets. |
ShowChars(link,GridPos1, GridPos2, GridPos3, GridPos4, GridPos5); |
} |
return length; |
} |
|
|
integer ConvertIndex(integer index) { |
// This converts from an ASCII based index to our indexing scheme. |
if (index >= 32) // ' ' or higher |
index -= 32; |
else { // index < 32 |
// Quick bounds check. |
if (index > 15) |
index = 15; |
|
index += 94; // extended characters |
} |
|
return index; |
} |
|
|
PassToRender(integer render,string message, integer bank) |
{ |
float time; |
integer extendedlen = 0; |
integer link; |
|
integer i = 0; |
integer msgLen = llStringLength(message); |
string TextToRender; |
integer num_slaves=llGetListLength(gSlaveNames); |
string slave_name; //avoids unnecessary casts, keeping it as a string |
|
|
//get the bank offset and length |
integer bank_offset=llList2Integer(gBankingData, (bank * BANK_STRIDE)); |
integer bank_length=llList2Integer(gBankingData, (bank * BANK_STRIDE) + 1); |
integer bank_highest_dirty=llList2Integer(gBankingData, (bank * BANK_STRIDE) + 2); |
|
integer x=0; |
for (;x < msgLen;x = x + 10) |
{ |
|
if (i >= bank_length) //we don't want to run off the end of the bank |
{ |
//set the dirty to max, and bail out, we're done |
gBankingData=llListReplaceList(gBankingData, [bank_length], (bank * BANK_STRIDE) + 2, (bank * BANK_STRIDE) + 2); |
return; |
} |
|
link = unpack(gXyTextPrims,(i + bank_offset)); |
TextToRender = llGetSubString(message, x, x + 20); |
|
if(gSlaveRegistered && (link % (num_slaves +1))) |
{ |
slave_name=llList2String(gSlaveNames, (link % (num_slaves + 1)) - 1); |
if (render == 1) |
llMessageLinked(LINK_THIS, SLAVE_DISPLAY, TextToRender, (key)((string)link + "," + slave_name)); |
if (render == 2) |
{ |
if(llSubStringIndex(TextToRender,"\e")>x+10) |
{ |
extendedlen = 10; |
} |
else |
{ |
extendedlen = RenderExtended(link,TextToRender,0); |
} |
|
if(extendedlen>10) |
{ |
x += extendedlen-10; |
} |
|
llMessageLinked(LINK_THIS,SLAVE_DISPLAY_EXTENDED,TextToRender,(key)((string)link+","+slave_name)); |
} |
} |
else |
{ |
if (render == 1) |
RenderString(link,TextToRender); |
if (render == 2) |
{ |
extendedlen = RenderExtended(link,TextToRender,1); |
if(extendedlen>10) |
{ |
x += extendedlen-10; |
} |
} |
|
// if (render == 3) |
// RenderWithEffects(link,TextToRender); |
} |
++i; |
} |
|
if (bank_highest_dirty==0) |
bank_highest_dirty=bank_length; |
|
integer current_highest_dirty=i; |
while (i < bank_highest_dirty) |
{ |
link = unpack(gXyTextPrims,(i + bank_offset)); |
|
if(gSlaveRegistered && (link % (num_slaves+1) != 0)) |
{ |
slave_name=llList2String(gSlaveNames, (link % (num_slaves + 1)) - 1); |
llMessageLinked(LINK_THIS, SLAVE_DISPLAY, " ", (key)((string)link + "," + slave_name)); |
//sorry, no fade effect with slave |
} |
else |
{ |
RenderString(link," "); |
} |
++i; |
} |
gBankingData=llListReplaceList(gBankingData, [current_highest_dirty], (bank * BANK_STRIDE) + 2, (bank * BANK_STRIDE) + 2); |
} |
|
// Bitwise Voodoo by Gigs Taggart and optimized by Strife Onizuka |
list gXyTextPrims; |
|
|
integer get_number_of_prims() |
{//ignores avatars. |
integer a = llGetNumberOfPrims(); |
while(llGetAgentSize(llGetLinkKey(a))) |
--a; |
return a; |
} |
|
//functions to pack 8-bit shorts into ints |
list pack_and_insert(list in_list, integer pos, integer value) |
{ |
// //figure out the bitpack position |
// integer pack = pos & 3; //4 bytes per int |
// pos=pos >> 2; |
// integer shifted = value << (pack << 3); |
// integer old_value = llList2Integer(in_list, pos); |
// shifted = old_value | shifted; |
// in_list = llListReplaceList(in_list, (list)shifted, pos, pos); |
// return in_list; |
//Safe optimized version |
integer index = pos >> 2; |
return llListReplaceList(in_list, (list)(llList2Integer(in_list, index) | (value << ((pos & 3) << 3))), index, index); |
} |
|
integer unpack(list in_list, integer pos) |
{ |
return (llList2Integer(in_list, pos >> 2) >> ((pos & 3) << 3)) & 0x000000FF;//unsigned |
// return (llList2Integer(in_list, pos >> 2) << (((~pos) & 3) << 3)) >> 24;//signed |
} |
|
|
change_color(vector color) |
{ |
integer num_prims=llGetListLength(gXyTextPrims) << 2; |
|
integer i = 0; |
|
for (; i<=num_prims; ++i) |
{ |
integer link = unpack(gXyTextPrims,i); |
if (!link) |
return; |
|
llSetLinkPrimitiveParamsFast( link,[ |
PRIM_COLOR, FACE_1, color, 1.0, |
PRIM_COLOR, FACE_2, color, 1.0, |
PRIM_COLOR, FACE_3, color, 1.0, |
PRIM_COLOR, FACE_4, color, 1.0, |
PRIM_COLOR, FACE_5, color, 1.0 |
]); |
} |
} |
|
change_line_color(integer bank, vector color) |
{ |
|
//get the bank offset and length |
integer i = llList2Integer(gBankingData, (bank * BANK_STRIDE)); |
integer bank_end = i + llList2Integer(gBankingData, (bank * BANK_STRIDE) + 1); |
|
for (; i < bank_end; ++i) |
{ |
integer link = unpack(gXyTextPrims,i); |
if (!link) |
return; |
|
llSetLinkPrimitiveParamsFast( link,[ |
PRIM_COLOR, FACE_1, color, 1.0, |
PRIM_COLOR, FACE_2, color, 1.0, |
PRIM_COLOR, FACE_3, color, 1.0, |
PRIM_COLOR, FACE_4, color, 1.0, |
PRIM_COLOR, FACE_5, color, 1.0 |
]); |
} |
} |
|
init() |
{ |
integer num_prims=get_number_of_prims(); |
|
string link_name; |
integer bank=0; |
integer bank_empty=FALSE; |
integer prims_pointer=0; //"pointer" to the next entry to be used in the gXyTextPrims list. |
|
list temp_bank=[]; |
integer temp_bank_stride=2; |
|
// moving this before the prim scan so that the slaves properly configure themseves before |
// any requests to display |
llMessageLinked(LINK_THIS, SLAVE_RESET, "" , null_key); |
|
gXyTextPrims=[]; |
integer x=0; |
for (;x<64;++x) |
{ |
gXyTextPrims= (gXyTextPrims = []) + gXyTextPrims + [0]; //we need to pad out the list to make it easier to add things in any order later |
} |
|
gBankingData = []; |
@loop; |
{ |
//loop over all prims, looking for ones in the current bank |
for(x=0;x<=num_prims;++x) |
{ |
link_name=llGetLinkName(x); |
list tmp = llParseString2List(link_name, ["-"], []); |
if(llList2String(tmp,0)== "xyzzytext") |
{ |
integer prims_bank=llList2Integer(tmp,1); |
if (llList2Integer(tmp,1)==bank) |
{ |
temp_bank+=llList2Integer(tmp,2) + (list)x; |
} |
} |
|
} |
|
|
if (temp_bank!=[]) |
{ |
//sort the current bank |
temp_bank=llListSort(temp_bank, temp_bank_stride, TRUE); |
|
integer temp_len=llGetListLength(temp_bank); |
|
//store metadata |
gBankingData+=[prims_pointer,temp_len/temp_bank_stride,0]; |
|
//repack the bank into the prim list |
for (x=0; x < temp_len; x+=temp_bank_stride) |
{ |
gXyTextPrims = pack_and_insert(gXyTextPrims, prims_pointer, llList2Integer(temp_bank, x+1)); |
++prims_pointer; |
} |
++bank; |
temp_bank=[]; |
jump loop; |
} |
} |
} |
|
default { |
state_entry() { |
// Initialize the character index. |
ResetCharIndex(); |
init(); |
} |
|
on_rez(integer num) |
{ |
llResetScript(); |
} |
|
link_message(integer sender, integer channel, string data, key id) { |
if(id == null_key) |
id="0"; |
|
if (channel == DISPLAY_STRING) { |
PassToRender(1,data, (integer)((string)id)); |
return; |
} |
else if (channel == DISPLAY_EXTENDED) { |
PassToRender(2,data, (integer)((string)id)); |
return; |
} |
else if (channel == REMAP_INDICES) { |
// Parse the message, splitting it up into index values. |
list Parsed = llCSV2List(data); |
integer i; |
// Go through the list and swap each pair of indices. |
for (i = 0; i < llGetListLength(Parsed); i += 2) { |
integer Index1 = ConvertIndex( llList2Integer(Parsed, i) ); |
integer Index2 = ConvertIndex( llList2Integer(Parsed, i + 1) ); |
|
// Swap these index values. |
string Value1 = llGetSubString(gCharIndex, Index1, Index1); |
string Value2 = llGetSubString(gCharIndex, Index2, Index2); |
|
gCharIndex = llDeleteSubString(gCharIndex, Index1, Index1); |
gCharIndex = llInsertString(gCharIndex, Index1, Value2); |
|
gCharIndex = llDeleteSubString(gCharIndex, Index2, Index2); |
gCharIndex = llInsertString(gCharIndex, Index2, Value1); |
} |
return; |
} |
else if (channel == RESET_INDICES) { |
// Restore the character index back to default settings. |
ResetCharIndex(); |
return; |
} |
else if (channel == RESCAN_LINKSET) |
{ |
init(); |
} |
else if (channel == SET_COLOR) { |
change_color((vector)data); |
} |
else if (channel == SET_LINE_COLOR) { |
change_line_color((integer)((string)id), (vector)data); |
} |
else if (channel == REGISTER_SLAVE) |
{ |
if(!~llListFindList(gSlaveNames, (list)data)) |
{//isn't registered yet |
gSlaveNames += data; |
gSlaveRegistered=TRUE; |
//llOwnerSay((string)llGetListLength(gSlaveNames) + " Slave(s) Recognized: " + data); |
} |
// else |
// {//it already exists |
// llOwnerSay((string)llGetListLength(gSlaveNames) + " Slave, Existing Slave Recognized: " + data); |
// } |
llMessageLinked(LINK_THIS, SLAVE_RECOGNIZED, data , null_key); |
} |
} |
|
changed(integer change) |
{ |
if(change&CHANGED_INVENTORY) |
{ |
if(gSlaveRegistered) |
{ |
//by using negative indexes they don't need to be adjusted when an entry is deleted. |
integer x = ~llGetListLength(gSlaveNames); |
while(++x) |
{ |
if (!~llGetInventoryType(llList2String(gSlaveNames, x))) |
{ |
//llOwnerSay("Slave Removed: " + llList2String(gSlaveNames, x)); |
gSlaveNames = llDeleteSubList(gSlaveNames, x, x); |
} |
} |
gSlaveRegistered = !(gSlaveNames == []); |
} |
} |
} |
} |
/visitor-track-and-record/visitor-track-and-record.lsl |
@@ -0,0 +1,729 @@ |
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// |
// This project makes Corrade, the Second Life / OpenSim bot track all the |
// visitors on a region and record information to the group database. More |
// information on the Corrade Second Life / OpenSim scripted agent can be |
// found at the URL: http://grimore.org/secondlife/scripted_agents/corrade |
// |
// The script works in combination with a "configuration" notecard that |
// must be placed in the same primitive as this script. The purpose of this |
// script is to illustrate the Corrade built-in group database features |
// and you are free to use, change, and commercialize it under the terms |
// of the GNU/GPLv3 license at: http://www.gnu.org/licenses/gpl.html |
// |
/////////////////////////////////////////////////////////////////////////// |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueGet(string k, string data) { |
if(llStringLength(data) == 0) return ""; |
if(llStringLength(k) == 0) return ""; |
list a = llParseString2List(data, ["&", "="], []); |
integer i = llListFindList(a, [ k ]); |
if(i != -1) return llList2String(a, i+1); |
return ""; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueEncode(list data) { |
list k = llList2ListStrided(data, 0, -1, 2); |
list v = llList2ListStrided(llDeleteSubList(data, 0, 0), 0, -1, 2); |
data = []; |
do { |
data += llList2String(k, 0) + "=" + llList2String(v, 0); |
k = llDeleteSubList(k, 0, 0); |
v = llDeleteSubList(v, 0, 0); |
} while(llGetListLength(k) != 0); |
return llDumpList2String(data, "&"); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// escapes a string in conformance with RFC1738 |
string wasURLEscape(string i) { |
string o = ""; |
do { |
string c = llGetSubString(i, 0, 0); |
i = llDeleteSubString(i, 0, 0); |
if(c == "") jump continue; |
if(c == " ") { |
o += "+"; |
jump continue; |
} |
if(c == "\n") { |
o += "%0D" + llEscapeURL(c); |
jump continue; |
} |
o += llEscapeURL(c); |
@continue; |
} while(i != ""); |
return o; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// unescapes a string in conformance with RFC1738 |
string wasURLUnescape(string i) { |
return llUnescapeURL( |
llDumpList2String( |
llParseString2List( |
llDumpList2String( |
llParseString2List( |
i, |
["+"], |
[] |
), |
" " |
), |
["%0D%0A"], |
[] |
), |
"\n" |
) |
); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
list wasCSVToList(string csv) { |
list l = []; |
list s = []; |
string m = ""; |
do { |
string a = llGetSubString(csv, 0, 0); |
csv = llDeleteSubString(csv, 0, 0); |
if(a == ",") { |
if(llList2String(s, -1) != "\"") { |
l += m; |
m = ""; |
jump continue; |
} |
m += a; |
jump continue; |
} |
if(a == "\"" && llGetSubString(csv, 0, 0) == a) { |
m += a; |
csv = llDeleteSubString(csv, 0, 0); |
jump continue; |
} |
if(a == "\"") { |
if(llList2String(s, -1) != a) { |
s += a; |
jump continue; |
} |
s = llDeleteSubList(s, -1, -1); |
jump continue; |
} |
m += a; |
@continue; |
} while(csv != ""); |
// postcondition: length(s) = 0 |
return l + m; |
} |
|
// corrade data |
key CORRADE = NULL_KEY; |
string GROUP = ""; |
string PASSWORD = ""; |
|
// for holding the callback URL |
string callback = ""; |
|
// for notecard reading |
integer line = 0; |
|
// key-value data will be read into this list |
list tuples = []; |
|
|
default { |
state_entry() { |
if(llGetInventoryType("configuration") != INVENTORY_NOTECARD) { |
llOwnerSay("Sorry, could not find a configuration inventory notecard."); |
return; |
} |
// DEBUG |
llOwnerSay("Reading configuration file..."); |
llGetNotecardLine("configuration", line); |
} |
dataserver(key id, string data) { |
if(data == EOF) { |
// invariant, length(tuples) % 2 == 0 |
if(llGetListLength(tuples) % 2 != 0) { |
llOwnerSay("Error in configuration notecard."); |
return; |
} |
CORRADE = llList2Key( |
tuples, |
llListFindList( |
tuples, |
[ |
"corrade" |
] |
) |
+1 |
); |
if(CORRADE == NULL_KEY) { |
llOwnerSay("Error in configuration notecard: corrade"); |
return; |
} |
GROUP = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"group" |
] |
) |
+1 |
); |
if(GROUP == "") { |
llOwnerSay("Error in configuration notecard: group"); |
return; |
} |
PASSWORD = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"password" |
] |
) |
+1 |
); |
if(PASSWORD == "") { |
llOwnerSay("Error in configuration notecard: password"); |
return; |
} |
// DEBUG |
llOwnerSay("Read configuration notecard..."); |
tuples = []; |
state url; |
} |
if(data == "") jump continue; |
integer i = llSubStringIndex(data, "#"); |
if(i != -1) data = llDeleteSubString(data, i, -1); |
list o = llParseString2List(data, ["="], []); |
// get rid of starting and ending quotes |
string k = llDumpList2String( |
llParseString2List( |
llStringTrim( |
llList2String( |
o, |
0 |
), |
STRING_TRIM), |
["\""], [] |
), "\""); |
string v = llDumpList2String( |
llParseString2List( |
llStringTrim( |
llList2String( |
o, |
1 |
), |
STRING_TRIM), |
["\""], [] |
), "\""); |
if(k == "" || v == "") jump continue; |
tuples += k; |
tuples += v; |
@continue; |
llGetNotecardLine("configuration", ++line); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state url { |
state_entry() { |
// DEBUG |
llOwnerSay("Requesting URL..."); |
llRequestURL(); |
} |
http_request(key id, string method, string body) { |
if(method != URL_REQUEST_GRANTED) return; |
callback = body; |
// DEBUG |
llOwnerSay("Got URL..."); |
state detect; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state detect { |
state_entry() { |
// DEBUG |
llOwnerSay("Detecting if Corrade is online..."); |
llSetTimerEvent(5); |
} |
timer() { |
llRequestAgentData((key)CORRADE, DATA_ONLINE); |
} |
dataserver(key id, string data) { |
if(data != "1") { |
// DEBUG |
llOwnerSay("Corrade is not online, sleeping..."); |
llSetTimerEvent(30); |
return; |
} |
state initialize; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state initialize { |
state_entry() { |
// DEBUG |
llOwnerSay("Creating the database if it does not exist..."); |
llInstantMessage( |
(key)CORRADE, |
wasKeyValueEncode( |
[ |
"command", "database", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"SQL", wasURLEscape( |
"CREATE TABLE IF NOT EXISTS visitors ( |
'firstname' TEXT NOT NULL, |
'lastname' TEXT NOT NULL, |
'lastseen' TEXT NOT NULL, |
'time' INTEGER NOT NULL, |
'memory' INTEGER NOT NULL, |
PRIMARY KEY ('firstname', 'lastname') |
)" |
), |
"callback", wasURLEscape(callback) |
] |
) |
); |
// alarm 60 |
llSetTimerEvent(60); |
} |
timer() { |
// DEBUG |
llOwnerSay("Timeout creating table..."); |
llResetScript(); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) != "database") return; |
if(wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Failed to create the table: " + |
wasURLUnescape( |
wasKeyValueGet( |
"error", |
body |
) |
) |
); |
return; |
} |
// DEBUG |
llOwnerSay("Table created..."); |
state show; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
|
state show { |
state_entry() { |
// DEBUG |
llOwnerSay("Updating display with the number of recorded visitors..."); |
llInstantMessage( |
(key)CORRADE, |
wasKeyValueEncode( |
[ |
"command", "database", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"SQL", wasURLEscape( |
"SELECT |
COUNT(*) AS 'Visits', |
AVG(time) AS 'Time', |
AVG(memory) AS 'Memory' |
FROM visitors" |
), |
"callback", wasURLEscape(callback) |
] |
) |
); |
// alarm 60 |
llSetTimerEvent(60); |
} |
timer() { |
// DEBUG |
llOwnerSay("Timeout reading rows from visitors table..."); |
llResetScript(); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) != "database") return; |
if(wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Failed to enumerate visitors: " + |
wasURLUnescape( |
wasKeyValueGet( |
"error", |
body |
) |
) |
); |
llResetScript(); |
} |
list data = wasCSVToList( |
wasURLUnescape( |
wasKeyValueGet( |
"data", |
body |
) |
) |
); |
integer visits = llList2Integer( |
data, |
llListFindList( |
data, |
(list)"Visits" |
) + 1 |
); |
integer time = llList2Integer( |
data, |
llListFindList( |
data, |
(list)"Time" |
) + 1 |
); |
integer memory = llList2Integer( |
data, |
llListFindList( |
data, |
(list)"Memory" |
) + 1 |
); |
llMessageLinked(LINK_ROOT, 204000, "V:" + (string)visits, "0"); |
llMessageLinked(LINK_ROOT, 204000, "T:" + (string)time + "m", "1"); |
llMessageLinked(LINK_ROOT, 204000, "M:" + (string)memory + "k", "2"); |
state scan; |
} |
link_message(integer sender_num, integer num, string str, key id) { |
if(str == "reset") |
state reset; |
if(str == "display") { |
line = 0; |
state display; |
} |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
|
state scan { |
state_entry() { |
// DEBUG |
llOwnerSay("Scanning for visitors..."); |
// Scan for visitors every 60 seconds. |
llSetTimerEvent(60); |
} |
timer() { |
// Check if Corrade is online. |
llRequestAgentData((key)CORRADE, DATA_ONLINE); |
// Get agents |
list as = llGetAgentList(AGENT_LIST_REGION, []); |
do { |
key a = llList2Key(as, 0); |
as = llDeleteSubList(as, 0, 0); |
list name = llParseString2List(llKey2Name(a), [" "], []); |
if(llGetListLength(name) != 2) return; |
string fn = llList2String(name, 0); |
string ln = llList2String(name, 1); |
// The command sent to Corrade responsible for adding a visitor |
// or updating the data for the visitor in case the visitor is |
// already entered into the visitors table. This is performed |
// with an INSER OR REPLACE sqlite command given the first name |
// and the last name of the avatar are unique primary keys. |
llInstantMessage( |
(key)CORRADE, |
wasKeyValueEncode( |
[ |
"command", "database", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"SQL", wasURLEscape( |
"INSERT OR REPLACE INTO visitors ( |
firstname, |
lastname, |
lastseen, |
time, |
memory |
) VALUES( |
'" + fn + "', '" + ln + "', '" + llGetTimestamp() + "', |
COALESCE( |
( |
SELECT time FROM visitors WHERE |
firstname='" + fn + "' AND lastname='" + ln + "' |
) + 1 |
, |
1 |
), " + |
(string)( |
(integer)( |
llList2Float( |
llGetObjectDetails( |
a, |
[OBJECT_SCRIPT_MEMORY] |
), |
0 |
) |
/ |
1024 /*in kib, to mib 1048576*/ |
) |
|
) + |
")" |
) |
] |
) |
); |
} while(llGetListLength(as)); |
state show; |
} |
link_message(integer sender_num, integer num, string str, key id) { |
if(str == "reset") |
state reset; |
if(str == "display") { |
line = 0; |
state display; |
} |
} |
dataserver(key id, string data) { |
if(data == "1") return; |
// DEBUG |
llOwnerSay("Corrade is not online, sleeping..."); |
// Switch to detect loop and wait there for Corrade to come online. |
state detect; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
|
state display_trampoline { |
state_entry() { |
++line; |
state display; |
} |
link_message(integer sender_num, integer num, string str, key id) { |
if(str == "reset") |
state reset; |
} |
} |
|
state display { |
state_entry() { |
llInstantMessage( |
(key)CORRADE, |
wasKeyValueEncode( |
[ |
"command", "database", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"SQL", wasURLEscape( |
"SELECT * FROM visitors |
ORDER BY lastseen DESC |
LIMIT 1 |
OFFSET " + (string)line |
), |
"callback", wasURLEscape(callback) |
] |
) |
); |
// alarm 60 |
llSetTimerEvent(60); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) != "database") return; |
if(wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Failed to query the table: " + |
wasURLUnescape( |
wasKeyValueGet( |
"error", |
body |
) |
) |
); |
return; |
} |
// Grab the data key if it exists. |
string dataKey = wasURLUnescape( |
wasKeyValueGet( |
"data", |
body |
) |
); |
|
// We got no more rows, so switch back to scanning. |
if(dataKey == "") |
state scan; |
|
list data = wasCSVToList(dataKey); |
|
string firstname = llList2String( |
data, |
llListFindList( |
data, |
(list)"firstname" |
) + 1 |
); |
string lastname = llList2String( |
data, |
llListFindList( |
data, |
(list)"lastname" |
) + 1 |
); |
string lastseen = llList2String( |
data, |
llListFindList( |
data, |
(list)"lastseen" |
) + 1 |
); |
|
llOwnerSay(firstname + " " + lastname + " @ " + lastseen); |
state display_trampoline; |
} |
link_message(integer sender_num, integer num, string str, key id) { |
if(str == "reset") |
state reset; |
} |
timer() { |
// DEBUG |
llOwnerSay("Timeout reading rows from visitors table..."); |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
|
state reset { |
state_entry() { |
// DEBUG |
llOwnerSay("Resetting all visitors..."); |
llInstantMessage( |
(key)CORRADE, |
wasKeyValueEncode( |
[ |
"command", "database", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"SQL", "DROP TABLE visitors", |
"callback", wasURLEscape(callback) |
] |
) |
); |
// alarm 60 |
llSetTimerEvent(60); |
} |
timer() { |
// DEBUG |
llOwnerSay("Timeout deleting database..."); |
llResetScript(); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) != "database") return; |
if(wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Failed to drop the visitors table: " + |
wasURLUnescape( |
wasKeyValueGet( |
"error", |
body |
) |
) |
); |
llResetScript(); |
} |
// DEBUG |
llOwnerSay("Table dropped..."); |
llResetScript(); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
state_exit() { |
llSetTimerEvent(0); |
} |
} |
/whitelist-accept-inventory-offers/whitelist-accept-inventory-offers.lsl |
@@ -0,0 +1,493 @@ |
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// All inventory offers made to Corrade can be processed for rejection // |
// approval by scripts. Corrade by default accepts inventory offers from // |
// masters defined in Corrade.ini - however, it would be useful to write // |
// an example script that takes a list of avatars and automatically // |
// accepts all the items. More information on the Corrade scripted agent // |
// can be found at: http://was.fm/secondlife/scripted_agents/corrade // |
// |
// The script works in combination with a "configuration" notecard that // |
// must be placed in the same primitive as this script. The purpose of // |
// this script is to demonstrate accepting inventory offers with the // |
// Corrade scripted agent and you are free to use, and commercialize it // |
// under the terms of the GNU/GPLv3 license which can be found at: // |
// http://www.gnu.org/licenses/gpl.html // |
/////////////////////////////////////////////////////////////////////////// |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueGet(string k, string data) { |
if(llStringLength(data) == 0) return ""; |
if(llStringLength(k) == 0) return ""; |
list a = llParseString2List(data, ["&", "="], []); |
integer i = llListFindList(a, [ k ]); |
if(i != -1) return llList2String(a, i+1); |
return ""; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
string wasKeyValueEncode(list data) { |
list k = llList2ListStrided(data, 0, -1, 2); |
list v = llList2ListStrided(llDeleteSubList(data, 0, 0), 0, -1, 2); |
data = []; |
do { |
data += llList2String(k, 0) + "=" + llList2String(v, 0); |
k = llDeleteSubList(k, 0, 0); |
v = llDeleteSubList(v, 0, 0); |
} while(llGetListLength(k) != 0); |
return llDumpList2String(data, "&"); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// escapes a string in conformance with RFC1738 |
string wasURLEscape(string i) { |
string o = ""; |
do { |
string c = llGetSubString(i, 0, 0); |
i = llDeleteSubString(i, 0, 0); |
if(c == "") jump continue; |
if(c == " ") { |
o += "+"; |
jump continue; |
} |
if(c == "\n") { |
o += "%0D" + llEscapeURL(c); |
jump continue; |
} |
o += llEscapeURL(c); |
@continue; |
} while(i != ""); |
return o; |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
// unescapes a string in conformance with RFC1738 |
string wasURLUnescape(string i) { |
return llUnescapeURL( |
llDumpList2String( |
llParseString2List( |
llDumpList2String( |
llParseString2List( |
i, |
["+"], |
[] |
), |
" " |
), |
["%0D%0A"], |
[] |
), |
"\n" |
) |
); |
} |
|
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
list wasCSVToList(string csv) { |
list l = []; |
list s = []; |
string m = ""; |
do { |
string a = llGetSubString(csv, 0, 0); |
csv = llDeleteSubString(csv, 0, 0); |
if(a == ",") { |
if(llList2String(s, -1) != "\"") { |
l += m; |
m = ""; |
jump continue; |
} |
m += a; |
jump continue; |
} |
if(a == "\"" && llGetSubString(csv, 0, 0) == a) { |
m += a; |
csv = llDeleteSubString(csv, 0, 0); |
jump continue; |
} |
if(a == "\"") { |
if(llList2String(s, -1) != a) { |
s += a; |
jump continue; |
} |
s = llDeleteSubList(s, -1, -1); |
jump continue; |
} |
m += a; |
@continue; |
} while(csv != ""); |
// postcondition: length(s) = 0 |
return l + m; |
} |
|
// corrade data and configuration parameters |
key CORRADE = NULL_KEY; |
string GROUP = ""; |
string PASSWORD = ""; |
list WHITELIST = []; |
|
// for holding the callback URL |
string callback = ""; |
|
// for notecard reading |
integer line = 0; |
|
// key-value data will be read into this list |
list tuples = []; |
|
default { |
state_entry() { |
if(llGetInventoryType("configuration") != INVENTORY_NOTECARD) { |
llOwnerSay("Sorry, could not find a configuration inventory notecard."); |
return; |
} |
// DEBUG |
llOwnerSay("Reading configuration file..."); |
llGetNotecardLine("configuration", line); |
} |
dataserver(key id, string data) { |
if(data == EOF) { |
// invariant, length(tuples) % 2 == 0 |
if(llGetListLength(tuples) % 2 != 0) { |
llOwnerSay("Error in configuration notecard."); |
return; |
} |
CORRADE = llList2Key( |
tuples, |
llListFindList( |
tuples, |
[ |
"corrade" |
] |
) |
+1 |
); |
if(CORRADE == NULL_KEY) { |
llOwnerSay("Error in configuration notecard: corrade"); |
return; |
} |
GROUP = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"group" |
] |
) |
+1 |
); |
if(GROUP == "") { |
llOwnerSay("Error in configuration notecard: group"); |
return; |
} |
PASSWORD = llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"password" |
] |
) |
+1 |
); |
if(PASSWORD == "") { |
llOwnerSay("Error in configuration notecard: password"); |
return; |
} |
WHITELIST = wasCSVToList( |
llToUpper( |
llList2String( |
tuples, |
llListFindList( |
tuples, |
[ |
"whitelist" |
] |
) |
+1 |
) |
) |
); |
if(WHITELIST == []) { |
llOwnerSay("Error in configuration notecard: whitelist"); |
return; |
} |
// DEBUG |
llOwnerSay("Read configuration notecard..."); |
state url; |
} |
if(data == "") jump continue; |
integer i = llSubStringIndex(data, "#"); |
if(i != -1) data = llDeleteSubString(data, i, -1); |
list o = llParseString2List(data, ["="], []); |
// get rid of starting and ending quotes |
string k = llDumpList2String( |
llParseString2List( |
llStringTrim( |
llList2String( |
o, |
0 |
), |
STRING_TRIM), |
["\""], [] |
), "\""); |
string v = llDumpList2String( |
llParseString2List( |
llStringTrim( |
llList2String( |
o, |
1 |
), |
STRING_TRIM), |
["\""], [] |
), "\""); |
if(k == "" || v == "") jump continue; |
tuples += k; |
tuples += v; |
@continue; |
llGetNotecardLine("configuration", ++line); |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state url { |
state_entry() { |
// DEBUG |
llOwnerSay("Requesting URL..."); |
llRequestURL(); |
} |
http_request(key id, string method, string body) { |
if(method != URL_REQUEST_GRANTED) return; |
callback = body; |
// DEBUG |
llOwnerSay("Got URL..."); |
state detect; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state detect { |
state_entry() { |
// DEBUG |
llOwnerSay("Detecting if Corrade is online..."); |
llSetTimerEvent(5); |
} |
timer() { |
llRequestAgentData((key)CORRADE, DATA_ONLINE); |
} |
dataserver(key id, string data) { |
if(data != "1") { |
// DEBUG |
llOwnerSay("Corrade is not online, sleeping..."); |
llSetTimerEvent(30); |
return; |
} |
state notify; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state notify { |
state_entry() { |
// DEBUG |
llOwnerSay("Binding to the inventory notification..."); |
llInstantMessage( |
(key)CORRADE, |
wasKeyValueEncode( |
[ |
"command", "notify", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"action", "set", |
"type", "inventory", |
"URL", wasURLEscape(callback), |
"callback", wasURLEscape(callback) |
] |
) |
); |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
if(wasKeyValueGet("command", body) != "notify" || |
wasKeyValueGet("success", body) != "True") { |
// DEBUG |
llOwnerSay("Failed to bind to the inventory notification: " + |
wasURLUnescape( |
wasKeyValueGet( |
"error", |
body |
) |
) |
); |
state detect; |
} |
// DEBUG |
llOwnerSay("Inventory notification installed..."); |
state accept; |
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |
|
state accept { |
state_entry() { |
// DEBUG |
llOwnerSay("Waiting for inventory offers..."); |
} |
timer() { |
llRequestAgentData((key)CORRADE, DATA_ONLINE); |
} |
dataserver(key id, string data) { |
if(data == "1") return; |
// DEBUG |
llOwnerSay("Corrade is not online, sleeping..."); |
// Switch to detect loop and wait there for Corrade to come online. |
state detect; |
} |
http_request(key id, string method, string body) { |
llHTTPResponse(id, 200, "OK"); |
|
string direction = wasURLUnescape( |
wasKeyValueGet( |
"direction", |
body |
) |
); |
|
// Only care about offers being sent to Corrade. |
if(direction != "offer") return; |
|
// Search the whitelist for the full name in case |
// an agent is sending the inventory item. |
string firstname = llToUpper( |
wasURLUnescape( |
wasKeyValueGet( |
"firstname", |
body |
) |
) |
); |
string lastname = llToUpper( |
wasURLUnescape( |
wasKeyValueGet( |
"lastname", |
body |
) |
) |
); |
|
if(firstname != "" && |
lastname != "" && |
llListFindList(WHITELIST, (list)(firstname + " " + lastname)) != -1) |
jump accept; |
|
// Search the whitelist for the UUID of the owner |
// of the object offering the item in case an object |
// is offering the inventory item |
string agent = llToUpper( |
wasURLUnescape( |
wasKeyValueGet( |
"agent", |
body |
) |
) |
); |
|
// Also search by UUID in case one was provided |
if(agent != NULL_KEY && |
llListFindList(WHITELIST, (list) agent) != -1) |
jump accept; |
|
// No matches so return. |
return; |
|
@accept; |
|
key session = (key)wasURLUnescape( |
wasKeyValueGet( |
"session", |
body |
) |
); |
|
// Oops, this should not happen! |
if(session == NULL_KEY) return; |
|
// Accept the inventory item. |
llInstantMessage(CORRADE, |
wasKeyValueEncode( |
[ |
|
"command", "replytoinventoryoffer", |
"group", wasURLEscape(GROUP), |
"password", wasURLEscape(PASSWORD), |
"action", "accept", // or decline |
// can be retrieved from the inventory notification |
"session", (string)session |
// "callback", wasURLEscape(URL) // we do not care |
] |
) |
); |
|
// DEBUG |
string item = wasURLUnescape( |
wasKeyValueGet( |
"item", |
body |
) |
); |
string asset = wasURLUnescape( |
wasKeyValueGet( |
"asset", |
body |
) |
); |
llOwnerSay("Accepted: " + asset + " " + item); |
|
} |
on_rez(integer num) { |
llResetScript(); |
} |
changed(integer change) { |
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) { |
llResetScript(); |
} |
} |
} |