corrade-lsl-templates

Subversion Repositories:
Compare Path: Rev
With Path: Rev
?path1? @ 3  →  ?path2? @ 4
/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();
}
}
}
/accept-animation-from-agent/configuration.txt
@@ -0,0 +1,14 @@
####################### START CONFIGURATION ##################################
 
# All these settings must correspond to the settings in Corrade.ini.
 
# This is the UUID of the Corrade bot.
corrade = "119be621-73c2-43b7-a070-dc5d8ad8e70b"
 
# The name of the group - it can also be the UUID of the group.
group = "My Group"
 
# The password for the group.
password = "mypassword"
 
####################### END CONFIGURATION ###################################
/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();
}
}
}
/automated-hunt-system/configuration.txt
@@ -0,0 +1,24 @@
######################### START CONFIGURATION #############################
 
# All these settings must correspond to the settings in Corrade.ini.
 
# This is the UUID of the Corrade bot.
corrade = "a6c43ad8-9086-46ee-9683-76e047062f05"
 
# The name of the group - it can also be the UUID of the group.
group = "My Group"
 
# The password for the group.
password = "mypassword"
 
# The hunt item to rez.
item = "[WaS-K] Coin Bag"
 
# At each of these points of interest (POI), Corrade will rez a hunt item.
# The POIs must be named in sequence, ie: POI_1, POI_2, etc... and you can
# add as many POIs as you like.
POI_1 = <166.024597, 145.024246, 3455.350342>
POI_2 = <155.617996, 144.363434, 3455.368896>
POI_3 = <160.74292, 155.446198, 3455.368896>
 
######################### END CONFIGURATION ###############################
/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();
}
}
}
/avatar-shape-gender-detector/configuration.txt
@@ -0,0 +1,29 @@
############################# START CONFIGURATION ############################
 
# This is a configuration notecard based on the key-value data Wizardry and Steamworks reader.
# Everything following the "#" character is ignored along with blank lines. Values can be set for various
# parameters in a simple and understandable format with the following syntax:
# KEY = "VALUE"
# Every time you change this configuration notecard, the script will reset itself and the new configuration
# will be read from this notecard.
 
# The "corrade", "group" and "password" settings must correspond to your settings in Corrade.ini
 
# This is the UUID of the Corrade bot.
corrade = "1272a407-1af3-47f7-abcd-79f81d28637d"
 
# The name of the group - it can also be the UUID of the group.
group = "My Group"
 
# The password for the group.
password = "mypassword"
 
# This represents the scan interval to detect avatars. Note that this script does not rescan avatars.
interval = "10"
 
# This represents the amount of free memory measured in bytes that the script will attempt to maintain.
# After the memory usage of this script exceeds this amount, the script will start culling the list of
# detected avatars in an attempt to not overflow and keep the script running.
memory = "1024"
 
############################# END CONFIGURATION #############################
/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();
}
}
}
 
 
/batch-change-region-covenant/configuration.txt
@@ -0,0 +1,14 @@
####################### START CONFIGURATION ##################################
 
# All these settings must correspond to the settings in Corrade.ini.
 
# This is the UUID of the Corrade bot.
corrade = "874b3f63-662c-4ab0-9cf3-1c972084811d"
 
# The name of the group - it can also be the UUID of the group.
group = "My Group"
 
# The password for the group.
password = "mypassword"
 
####################### END CONFIGURATION ###################################
/batch-change-region-covenant/covenant.txt
@@ -0,0 +1,3 @@
Fun Games Beach (Virtual Fishing & Virtual Farming)
 
For more information & TOS please visit our website.
/batch-change-region-covenant/regions.txt
@@ -0,0 +1,2 @@
Puguet Sound
Milkyway Island
/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();
}
}
}
/circular-patrol-movement/configuration.txt
@@ -0,0 +1,20 @@
####################### START CONFIGURATION ##################################
 
# All these settings must correspond to the settings in Corrade.ini.
 
# This is the UUID of the Corrade bot.
corrade = "68599e43-b1e8-4c76-aa8c-a09af6613451"
 
# The name of the group - it can also be the UUID of the group.
group = "My Group"
 
# The password for the group.
password = "mypassword"
 
# The distance from this box that Corrade is allowed to wander.
radius = 5
 
# The maximal time in seconds allowed for Corrade to remain stationary.
wait = 60
 
####################### END CONFIGURATION ###################################
/greet-invite-track-group-members/configuration.txt
@@ -0,0 +1,61 @@
############################# START CONFIGURATION ############################
 
# This is a configuration notecard based on the key-value data Wizardry and Steamworks reader.
# Everything following the "#" character is ignored along with blank lines. Values can be set for various
# parameters in a simple and understandable format with the following syntax:
#
# key = "value"
#
# Every time you change this configuration notecard, the script will reset itself and red this notecard.
 
# The "corrade", "group" and "password" settings must correspond to your settings in Corrade.ini
 
# This is the UUID of the Corrade bot.
corrade = "c5a04cf0-4336-4ca7-9cdf-b19a38d2e920"
 
# The name of the group - it can also be the UUID of the group.
group = "My Group"
 
# The password for the group.
password = "mypassword"
 
# The scanning range in meters.
range = "10"
 
# The messae that will be displayed on the dialog pop-up when a new visitor is detected.
welcome = "Hello, welcome to my store! Please choose from the options below. Have a nice stay!"
 
# Buttons!
# Here you can configure the button labels that will be displayed on the dialog pop-up.
# The join group button label.
join = "Join Group"
# The info button label.
info = "Info"
# The landmark button label.
mark = "Landmark"
# The exit button label.
exit = "Exit"
# The gift button label.
gift = "Free Gift"
# The website button label.
site = "Website"
# The page-owner button.
page = "Help!"
 
# The website that the button website takes the user to.
website = "http://grimore.org"
# The message displayed when prompting the user to open a website.
loadurl = "Thank you for visiting our site!"
 
# A comma-separated list of people to be paged when an avatar clicks the page-owner button.
help = "Kira Komarov, Some Resident"
# The message that will be sent to the person clicking the page-owner button that help has been called.
assist = "A manager has been paged and will be with you as soon as possible..."
 
# A comma-separated list of people to to be notified when an avatar joins or leaves the group.
notify = "Kira Komarov, Some Resident"
 
# The message to send when an item is not available.
n/a = "Sorry, that item is not yet available..."
 
############################# END CONFIGURATION #############################
/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/configuration.txt
@@ -0,0 +1,17 @@
####################### START CONFIGURATION ##################################
 
# All these settings must correspond to the settings in Corrade.ini.
 
# This is the UUID of the Corrade bot.
corrade = "0f4de75b-8292-4a12-86f2-31a55879e161"
 
# The name of the group - it can also be the UUID of the group.
group = "My Group"
 
# The password for the group.
password = "mypassword"
 
# The range in meters to follow the wearer of the follower at.
range = 5
 
####################### END CONFIGURATION ###################################
/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/configuration.txt
@@ -0,0 +1,17 @@
####################### START CONFIGURATION ##################################
 
# All these settings must correspond to the settings in Corrade.ini.
 
# This is the UUID of the Corrade bot.
corrade = "fbde0a44-37e2-4f80-893d-8748788768f7"
 
# The name of the group - it can also be the UUID of the group.
group = "My Group"
 
# The password for the group.
password = "mypassword"
 
# The upper limit in seconds to post a message.
interval = 86400
 
####################### END CONFIGURATION ###################################
/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!"
/group-fortune/group-fortune.lsl
@@ -0,0 +1,253 @@
///////////////////////////////////////////////////////////////////////////
// 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) 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) 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, "&");
}
 
// Temporary storage for notecard lines.
integer f;
string say;
 
// Corrade data
string CORRADE = "";
string GROUP = "";
string PASSWORD = "";
integer INTERVAL = 21600;
 
// 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;
}
INTERVAL = llList2Integer(
tuples,
llListFindList(
tuples,
[
"interval"
]
)
+1);
if(INTERVAL == 0) {
llOwnerSay("Error in configuration notecard: interval");
return;
}
// DEBUG
llOwnerSay("Read configuration file...");
state fortune;
}
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 fortune {
state_entry() {
if(llGetInventoryType("Fortunes") == INVENTORY_NOTECARD) jump schedule;
llSay(DEBUG_CHANNEL, "Fortunes notecard not found.");
return;
@schedule;
llGetNumberOfNotecardLines("Fortunes");
}
dataserver(key requested, string data) {
f=(integer)data;
llOwnerSay("Ready, sleeping...");
state quote;
}
on_rez(integer num) {
llResetScript();
}
changed(integer change) {
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
llResetScript();
}
}
}
state quote {
state_entry() {
llGetNotecardLine("Fortunes", (integer)llFrand(f));
}
timer() {
state speak;
}
dataserver(key requested, string data) {
say = data;
llSetTimerEvent(1 + llFrand(INTERVAL));
 
}
on_rez(integer num) {
llResetScript();
}
changed(integer change) {
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
llResetScript();
}
}
state_exit() {
llSetTimerEvent(0);
}
}
 
state speak {
state_entry() {
llInstantMessage(CORRADE,
wasKeyValueEncode(
[
"command", "tell",
"group", wasURLEscape(GROUP),
"password", wasURLEscape(PASSWORD),
"entity", "group",
"message", wasURLEscape(say)
]
)
);
llSetTimerEvent(1);
}
timer() {
state quote;
}
on_rez(integer num) {
llResetScript();
}
changed(integer change) {
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
llResetScript();
}
}
state_exit() {
llSetTimerEvent(0);
}
}
File deleted
/heads-up-display/source/fly/fly.lsl
File deleted
/heads-up-display/source/point-at/point-at.lsl
File deleted
/heads-up-display/source/go-home/go-home.lsl
File deleted
/heads-up-display/source/permission-passthrough/permission-passthrough.lsl
File deleted
/heads-up-display/source/message-relay/message-relay.lsl
File deleted
\ No newline at end of file
/heads-up-display/source/teleport/teleport.lsl
File deleted
/heads-up-display/source/sit/sit.lsl
File deleted
\ No newline at end of file
/heads-up-display/source/joystick/joystick.lsl
File deleted
/heads-up-display/source/tell/tell.lsl
File deleted
/heads-up-display/source/patrol/patrol.lsl
File deleted
/heads-up-display/source/heads-up-display.lsl
File deleted
\ No newline at end of file
/heads-up-display/source/recall/recall.lsl
File deleted
/heads-up-display/source/dialog-passthrough/dialog-passthrough.lsl
File deleted
\ No newline at end of file
/heads-up-display/source/stand/stand.lsl
File deleted
\ No newline at end of file
/heads-up-display/source/relax/relax.lsl
File deleted
/heads-up-display/source/touch/touch.lsl
File deleted
/heads-up-display/source/grid-follow/grid-follow.lsl
File deleted
/heads-up-display/source/configuration.txt
/heads-up-display/configuration.txt
@@ -0,0 +1,39 @@
########################## START CONFIGURATION ############################
 
# This is a configuration notecard based on the key-value data Wizardry and
# Steamworks reader. Everything following the "#" character is ignored
# along with blank lines. Values can be set for various parameters in a
# simple and understandable format with the following syntax:
# KEY = "VALUE"
# Every time you change this configuration notecard, the script will reset
# itself and the new configuration will be read from this notecard.
 
# The "corrade", "group" and "password" settings must correspond to your
# settings in Corrade.ini otherwise the script will not work at all.
 
# Set this to the UUID of the Corrade bot.
# Corrade Resident
corrade = "f5418c55-0e51-41bc-8ba3-c9c986047bb6"
 
# The name of the group - it can also be the UUID of the group.
group = "My Group"
 
# The password for the group.
password = "mypassword"
 
# The range in meters to follow the wearer of the follower at.
range = 5
 
# The distance from this box that Corrade is allowed to wander.
radius = 10
 
# The maximal time in seconds allowed for Corrade to remain stationary.
wait = 60
 
# The range in which to search for primitives and avatars.
radar = 5
 
# The version that this HUD is compatible with.
version = 9.143
 
########################### END CONFIGURATION #############################
/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/fly/fly.lsl
@@ -0,0 +1,393 @@
///////////////////////////////////////////////////////////////////////////
// Copyright (C) Wizardry and Steamworks 2016 - License: GNU GPLv3 //
///////////////////////////////////////////////////////////////////////////
//
// This script can be used to make Corrade toggle flight mode on and off.
//
// 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 = "";
 
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 check;
}
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 check {
state_entry() {
llInstantMessage(
wasKeyValueGet(
"corrade",
configuration
),
wasKeyValueEncode(
[
"command", "getmovementdata",
"group", wasURLEscape(
wasKeyValueGet(
"group",
configuration
)
),
"password", wasURLEscape(
wasKeyValueGet(
"password",
configuration
)
),
"data", "Fly",
"callback", wasURLEscape(callback)
]
)
);
llSetTimerEvent(60);
}
timer() {
// DEBUG
llOwnerSay("Timeout requesting self data...");
llResetScript();
}
http_request(key id, string method, string body) {
llHTTPResponse(id, 200, "OK");
if(wasKeyValueGet("command", body) != "getmovementdata" ||
wasKeyValueGet("success", body) != "True") {
// DEBUG
llOwnerSay("Unable to query self data...");
llResetScript();
}
// DEBUG
llOwnerSay("State: " +
llList2String(
wasCSVToList(
wasKeyValueGet(
"data",
wasURLUnescape(body)
)
),
1
)
);
if(llList2String(
wasCSVToList(
wasKeyValueGet(
"data",
wasURLUnescape(body)
)
),
1
) != "True") state start;
state stop;
}
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 start {
state_entry() {
// DEBUG
llOwnerSay("Starting...");
llInstantMessage(
wasKeyValueGet(
"corrade",
configuration
),
wasKeyValueEncode(
[
"command", "fly",
"group", wasURLEscape(
wasKeyValueGet(
"group",
configuration
)
),
"password", wasURLEscape(
wasKeyValueGet(
"password",
configuration
)
),
"action", "start"
]
)
);
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 stop {
state_entry() {
// DEBUG
llOwnerSay("Stoping...");
llInstantMessage(
wasKeyValueGet(
"corrade",
configuration
),
wasKeyValueEncode(
[
"command", "fly",
"group", wasURLEscape(
wasKeyValueGet(
"group",
configuration
)
),
"password", wasURLEscape(
wasKeyValueGet(
"password",
configuration
)
),
"action", "stop"
]
)
);
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/go-home/go-home.lsl
@@ -0,0 +1,278 @@
///////////////////////////////////////////////////////////////////////////
// Copyright (C) Wizardry and Steamworks 2016 - License: GNU GPLv3 //
///////////////////////////////////////////////////////////////////////////
//
// This button will send Corrade home to its set home location.
//
// 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 = "";
 
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);
}
on_rez(integer num) {
llResetScript();
}
attach(key id) {
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 home;
}
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 home {
state_entry() {
llInstantMessage(
wasKeyValueGet(
"corrade",
configuration
),
wasKeyValueEncode(
[
"command", "gohome",
"group", wasURLEscape(
wasKeyValueGet(
"group",
configuration
)
),
"password", wasURLEscape(
wasKeyValueGet(
"password",
configuration
)
),
"callback", wasURLEscape(callback)
]
)
);
}
http_request(key id, string method, string body) {
llHTTPResponse(id, 200, "OK");
if(wasKeyValueGet("command", body) != "gohome" ||
wasKeyValueGet("success", body) != "True") {
// DEBUG
llOwnerSay("Go home failed...");
llResetScript();
}
// DEBUG
llOwnerSay("Go home succeeded...");
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/joystick/joystick.lsl
@@ -0,0 +1,374 @@
///////////////////////////////////////////////////////////////////////////
// Copyright (C) Wizardry and Steamworks 2016 - License: GNU GPLv3 //
///////////////////////////////////////////////////////////////////////////
//
// This script can be used to control Corrade's movement manually using the
// WSAD and arrow keys on the keyboard.
//
// 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;
}
 
// 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 detect;
}
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 detect {
state_entry() {
// DEBUG
llOwnerSay("Detecting if Corrade is on the region...");
if(llListFindList(
llGetAgentList(
AGENT_LIST_REGION,
[]
),
(list)(
(key)wasKeyValueGet(
"corrade",
configuration
)
)
) == -1) {
// DEBUG
llOwnerSay("Corrade is not in the current region.");
llResetScript();
}
state joy;
}
touch_start(integer num) {
llSetColor(<.5,0,0>, ALL_SIDES);
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 joy {
state_entry() {
llRequestPermissions(llGetOwner(), PERMISSION_TAKE_CONTROLS);
llSetColor(<0,0.5,0>, ALL_SIDES);
// DEBUG
llOwnerSay("Joystick on...");
}
touch_start(integer num) {
llSetColor(<.5,0,0>, ALL_SIDES);
llResetScript();
}
control(key id, integer level, integer edge) {
if (level & CONTROL_UP) {
llRegionSayTo(
wasKeyValueGet(
"corrade",
configuration
),
0,
wasKeyValueEncode(
[
// nudges Corrade up
"command", "nudge",
"group", wasURLEscape(
wasKeyValueGet(
"group",
configuration
)
),
"password", wasURLEscape(
wasKeyValueGet(
"password",
configuration
)
),
"direction", "up"
]
)
);
}
if (level & CONTROL_DOWN) {
llRegionSayTo(
wasKeyValueGet(
"corrade",
configuration
),
0,
wasKeyValueEncode(
[
// nudges Corrade down
"command", "nudge",
"group", wasURLEscape(
wasKeyValueGet(
"group",
configuration
)
),
"password", wasURLEscape(
wasKeyValueGet(
"password",
configuration
)
),
"direction", "down"
]
)
);
}
if (level & CONTROL_FWD) {
llRegionSayTo(
wasKeyValueGet(
"corrade",
configuration
),
0,
wasKeyValueEncode(
[
// nudges Corrade forward
"command", "nudge",
"group", wasURLEscape(
wasKeyValueGet(
"group",
configuration
)
),
"password", wasURLEscape(
wasKeyValueGet(
"password",
configuration
)
),
"direction", "forward"
]
)
);
}
if (level & CONTROL_BACK) {
llRegionSayTo(
wasKeyValueGet(
"corrade",
configuration
),
0,
wasKeyValueEncode(
[
// nudges Corrade back
"command", "nudge",
"group", wasURLEscape(
wasKeyValueGet(
"group",
configuration
)
),
"password", wasURLEscape(
wasKeyValueGet(
"password",
configuration
)
),
"direction", "back"
]
)
);
}
if ((level & CONTROL_LEFT) || (level & CONTROL_ROT_LEFT)) {
llRegionSayTo(
wasKeyValueGet(
"corrade",
configuration
),
0,
wasKeyValueEncode(
[
// rotates Corrade left
"command", "turn",
"group", wasURLEscape(
wasKeyValueGet(
"group",
configuration
)
),
"password", wasURLEscape(
wasKeyValueGet(
"password",
configuration
)
),
"direction", "left",
"degrees", 0.15
]
)
);
}
if ((level & CONTROL_RIGHT) || (level & CONTROL_ROT_RIGHT)) {
llRegionSayTo(
wasKeyValueGet(
"corrade",
configuration
),
0,
wasKeyValueEncode(
[
// rotates Corrade right
"command", "turn",
"group", wasURLEscape(
wasKeyValueGet(
"group",
configuration
)
),
"password", wasURLEscape(
wasKeyValueGet(
"password",
configuration
)
),
"direction", "right",
"degrees", 0.15
]
)
);
}
}
run_time_permissions(integer permissions) {
if (permissions & PERMISSION_TAKE_CONTROLS) {
llTakeControls(CONTROL_FWD | CONTROL_BACK | CONTROL_LEFT | CONTROL_ROT_LEFT | CONTROL_RIGHT | CONTROL_ROT_RIGHT | CONTROL_UP | CONTROL_DOWN, TRUE, FALSE);
// DEBUG
llOwnerSay("Controls seized...");
}
}
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/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/recall/recall.lsl
@@ -0,0 +1,236 @@
///////////////////////////////////////////////////////////////////////////
// Copyright (C) Wizardry and Steamworks 2016 - License: GNU GPLv3 //
///////////////////////////////////////////////////////////////////////////
//
// This is a recall device that will make Corrade send you a teleport lure
// such that you can teleport to Corrade's location.
//
// 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;
}
 
// 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 recall;
}
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 recall {
state_entry() {
// DEBUG
llOwnerSay("Recalling...");
llInstantMessage(
(key)wasKeyValueGet(
"corrade",
configuration
),
wasKeyValueEncode(
[
"command", "lure",
"group", wasURLEscape(
wasKeyValueGet(
"group",
configuration
)
),
"password", wasURLEscape(
wasKeyValueGet(
"password",
configuration
)
),
"agent", llGetOwner(),
"callback", wasURLEscape(callback)
]
)
);
llSetTimerEvent(30);
}
timer() {
// DEBUG
llOwnerSay("Recall failed...");
llSetColor(<.5,0,0>, ALL_SIDES);
llResetScript();
}
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) != "lure" ||
wasKeyValueGet("success", body) != "True") {
// DEBUG
llOwnerSay("Recall failed...");
llSetColor(<.5,0,0>, ALL_SIDES);
llResetScript();
}
// DEBUG
llOwnerSay("Recall succeeded...");
llSetColor(<.5,0,0>, ALL_SIDES);
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/relax/relax.lsl
@@ -0,0 +1,234 @@
///////////////////////////////////////////////////////////////////////////
// Copyright (C) Wizardry and Steamworks 2016 - License: GNU GPLv3 //
///////////////////////////////////////////////////////////////////////////
//
// This script makes Corrade sit on the ground.
//
// 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;
}
 
// 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 relax;
}
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 relax {
state_entry() {
// DEBUG
llOwnerSay("Relaxing...");
llInstantMessage(
(key)wasKeyValueGet(
"corrade",
configuration
),
wasKeyValueEncode(
[
"command", "relax",
"group", wasURLEscape(
wasKeyValueGet(
"group",
configuration
)
),
"password", wasURLEscape(
wasKeyValueGet(
"password",
configuration
)
),
"callback", wasURLEscape(callback)
]
)
);
llSetTimerEvent(60);
}
timer() {
// DEBUG
llOwnerSay("Relax failed...");
llSetColor(<.5,0,0>, ALL_SIDES);
llResetScript();
}
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) != "relax" ||
wasKeyValueGet("success", body) != "True") {
// DEBUG
llOwnerSay("Relax failed...");
llSetColor(<.5,0,0>, ALL_SIDES);
llResetScript();
}
// DEBUG
llOwnerSay("Relax succeeded...");
llSetColor(<.5,0,0>, ALL_SIDES);
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/stand/stand.lsl
@@ -0,0 +1,235 @@
///////////////////////////////////////////////////////////////////////////
// Copyright (C) Wizardry and Steamworks 2016 - License: GNU GPLv3 //
///////////////////////////////////////////////////////////////////////////
//
// This script makes Corrade stand.
//
// 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;
}
 
// 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 stand;
}
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 stand {
state_entry() {
// DEBUG
llOwnerSay("Standing...");
llInstantMessage(
(key)wasKeyValueGet(
"corrade",
configuration
),
wasKeyValueEncode(
[
"command", "stand",
"group", wasURLEscape(
wasKeyValueGet(
"group",
configuration
)
),
"password", wasURLEscape(
wasKeyValueGet(
"password",
configuration
)
),
"deanimate", "True",
"callback", wasURLEscape(callback)
]
)
);
llSetTimerEvent(60);
}
timer() {
// DEBUG
llOwnerSay("Stand failed...");
llSetColor(<.5,0,0>, ALL_SIDES);
llResetScript();
}
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) != "stand" ||
wasKeyValueGet("success", body) != "True") {
// DEBUG
llOwnerSay("Stand failed...");
llSetColor(<.5,0,0>, ALL_SIDES);
llResetScript();
}
// DEBUG
llOwnerSay("Stand succeeded...");
llSetColor(<.5,0,0>, ALL_SIDES);
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/teleport/teleport.lsl
@@ -0,0 +1,262 @@
///////////////////////////////////////////////////////////////////////////
// Copyright (C) Wizardry and Steamworks 2016 - License: GNU GPLv3 //
///////////////////////////////////////////////////////////////////////////
//
// This script is a simple teleport button that makes Corrade teleport to
// your current location.
//
// 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 //
///////////////////////////////////////////////////////////////////////////
// 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)) {
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();
}
}
 
state on {
state_entry() {
llSetColor(<0,.5,0>, ALL_SIDES);
state url;
}
attach(key id) {
llResetScript();
}
on_rez(integer num) {
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 teleport;
}
attach(key id) {
llResetScript();
}
on_rez(integer num) {
llResetScript();
}
changed(integer change) {
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
llResetScript();
}
}
}
 
state teleport {
state_entry() {
// DEBUG
llOwnerSay("Teleporting...");
llInstantMessage(
(key)wasKeyValueGet(
"corrade",
configuration
),
wasKeyValueEncode(
[
"command", "teleport",
"group", wasURLEscape(
wasKeyValueGet(
"group",
configuration
)
),
"password", wasURLEscape(
wasKeyValueGet(
"password",
configuration
)
),
"entity", "region",
"region", wasURLEscape(
llGetRegionName()
),
"deanimate", "True",
"position", llGetPos(),
"callback", wasURLEscape(callback)
]
)
);
llSetTimerEvent(60);
}
timer() {
// DEBUG
llOwnerSay("Teleport failed...");
llSetColor(<.5,0,0>, ALL_SIDES);
llResetScript();
}
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) != "teleport" ||
wasKeyValueGet("success", body) != "True") {
// DEBUG
llOwnerSay("Teleport failed with failure message: " +
wasURLUnescape(
wasKeyValueGet(
"error",
body
)
)
);
llSetColor(<.5,0,0>, ALL_SIDES);
llResetScript();
}
// DEBUG
llOwnerSay("Teleport succeeded...");
llSetColor(<.5,0,0>, ALL_SIDES);
llResetScript();
}
attach(key id) {
llResetScript();
}
on_rez(integer num) {
llResetScript();
}
changed(integer change) {
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
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/configuration.txt
@@ -0,0 +1,21 @@
############################# START CONFIGURATION ############################
 
# This is a configuration notecard based on the key-value data Wizardry and Steamworks reader.
# Everything following the "#" character is ignored along with blank lines. Values can be set for various
# parameters in a simple and understandable format with the following syntax:
# KEY = "VALUE"
# Every time you change this configuration notecard, the script will reset itself and the new configuration
# will be read from this notecard.
 
# The "corrade", "group" and "password" settings must correspond to your settings in Corrade.ini
 
# Set this to the UUID of the Corrade bot.
corrade = "11322b38-388a-47d0-8586-6462c4d32bd9"
 
# The name of the group - it can also be the UUID of the group.
group = "My Group"
 
# The password for the group.
password = "mypassword"
 
############################# END CONFIGURATION #############################
/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/configuration.txt
@@ -0,0 +1,21 @@
############################# START CONFIGURATION ############################
 
# This is a configuration notecard based on the key-value data Wizardry and Steamworks reader.
# Everything following the "#" character is ignored along with blank lines. Values can be set for various
# parameters in a simple and understandable format with the following syntax:
# KEY = "VALUE"
# Every time you change this configuration notecard, the script will reset itself and the new configuration
# will be read from this notecard.
 
# The "corrade", "group" and "password" settings must correspond to your settings in Corrade.ini
 
# Set this to the UUID of the Corrade bot.
corrade = "5f2a0ebf-0115-4682-b066-2df5724c910c"
 
# The name of the group - it can also be the UUID of the group.
group = "My Group"
 
# The password for the group.
password = "mypassword"
 
############################# END CONFIGURATION #############################
/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/configuration.txt
@@ -0,0 +1,24 @@
############################# START CONFIGURATION ############################
 
# This is a configuration notecard based on the key-value data Wizardry and Steamworks reader.
# Everything following the "#" character is ignored along with blank lines. Values can be set for various
# parameters in a simple and understandable format with the following syntax:
# KEY = "VALUE"
# Every time you change this configuration notecard, the script will reset itself and the new configuration
# will be read from this notecard.
 
# The "corrade", "group" and "password" settings must correspond to your settings in Corrade.ini
 
# This is the UUID of the Corrade bot.
corrade = "be68a48a-4a30-4b9e-9e1b-a809de814b17"
 
# The name of the group - it can also be the UUID of the group.
group = "My Group"
 
# The password for the group.
password = "mypassword"
 
# The avatar to whom local chat will be relayed to.
destination = "45a3482d-4c63-4fd1-9cd6-5005b2b49c52"
 
############################# END CONFIGURATION #############################
/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/configuration.txt
@@ -0,0 +1,32 @@
############################# START CONFIGURATION ############################
 
# This is a configuration notecard based on the key-value data Wizardry and Steamworks reader.
# Everything following the "#" character is ignored along with blank lines. Values can be set for various
# parameters in a simple and understandable format with the following syntax:
# KEY = "VALUE"
# Every time you change this configuration notecard, the script will reset itself and the new configuration
# will be read from this notecard.
 
# The "corrade", "group" and "password" settings must correspond to your settings in Corrade.ini
 
# This is the UUID of the Corrade bot.
corrade = "e8377ccf-6f51-4a78-ad2d-bdf5f37f278f"
 
# The name of the group - it can also be the UUID of the group.
group = "My Group"
 
# The password for the group.
password = "mypassword"
 
# A random set of UUIDs to use for both point and look effects. You can set these to any UUID you like.
lookUUID = "f73f91b4-67f5-4bd1-ac4f-428c5d9def95"
pointUUID = "0977f6dc-fb98-487b-8186-b36f83172444"
 
# The effect type. Can be either "look" or "point". The former makes Corrade turn its head toward the
# agent whilst the latter will also make Corrade point with its hand towards the agent.
effect = "look"
 
# The time in seconds to look or point at the avatar currently typing on local chat.
duration = 10
 
############################# END CONFIGURATION #############################
/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/configuration.txt
@@ -0,0 +1,25 @@
####################### START CONFIGURATION #################################
 
# All these settings must correspond to the settings in Corrade.ini.
 
# This is the UUID of the Corrade bot.
corrade = "d6eea54f-37ab-400f-baa5-ab160ebab5b0"
 
# Any number of groups is allowed but they have to be named sequentially following the pattern:
# group_1, group_2, group_3, ...
# Similarly, the passwords must be created in sequence:
# password_1, password_2, password_3, ...
# Such that password_1 is the password for group_1, password_2 is the password for group_2, etc...
# Instead of group names, you can also specify group UUIDs.
group_1 = "My Cool Group"
password_1 = "coolgrouppass"
group_2 = "Small Group"
password_2 = "smallgrouppass"
 
# The subject of the notice.
subject = "Test notification!"
 
# The message to send.
message = "Ignore me!"
 
####################### END CONFIGURATION ###################################
/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/configuration.txt
@@ -0,0 +1,42 @@
####################### START CONFIGURATION ##################################
 
# All these settings must correspond to the settings in Corrade.ini.
 
# This is the UUID of the Corrade bot.
corrade = "97515305-5f46-4521-9568-a879b8c1f1c9"
 
# The name of the group - it can also be the UUID of the group.
group = "My Group"
 
# The password for the group.
password = "mypassword"
 
# The contour of a polygon represented by regional point-vectors, for example:
#
# 1 +----+ 2
# | |
# 5 + + 3
# \ /
# + 4
#
# The points must be named in sequence, ie: point_1, point_2, etc... By following
# the contour of the polygon that you wish Corrade to wander within.
#
# The following values represent the contour of a polygon with 12 points:
point_1 = <90.140633, 195.659195, 0>
point_2 = <93.146393, 162.897415, 0>
point_3 = <90.38028, 156.655594, 0>
point_4 = <79.35807, 138.610291, 0>
point_5 = <93.638596, 124.921394, 0>
point_6 = <110.910965, 120.750427, 0>
point_7 = <125.259796, 116.540215, 0>
point_8 = <140.697433, 151.190826, 0>
point_9 = <133.573517, 163.03215, 0>
point_10 = <122.473473, 161.892914, 0>
point_11 = <118.320236, 167.628906, 0>
point_12 = <107.47805, 198.410461, 0>
 
# The maximal time in seconds allowed for Corrade to remain stationary.
wait = 60
 
####################### END CONFIGURATION ###################################
/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/badwords.txt
@@ -0,0 +1,5 @@
fuck
piss
shit
cum
dick
/punish-bad-words/configuration.txt
@@ -0,0 +1,24 @@
####################### START CONFIGURATION ##################################
 
# All these settings must correspond to the settings in Corrade.ini.
 
# This is the UUID of the Corrade bot.
corrade = "4fab7014-5343-4f87-877d-0b5bdf0dbd41"
 
# The name of the group - it can also be the UUID of the group.
group = "My Group"
 
# The password for the group.
password = "My Password"
 
# The punishment for uttering a bad word in group chat. This can be set to "mute" which takes away the
# typing capabilities of the offender or "eject" which ejects the offender from the group.
punishment = "eject"
 
# The characters to split the messages on as a list of characters.
split = " .,"
 
# A list of avatars to notify when Corrade punishes an avatar.
announce = "Gonor Mana, Stuff Resident"
 
####################### END CONFIGURATION ###################################
/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/configuration.txt
@@ -0,0 +1,30 @@
############################# START CONFIGURATION ############################
 
# This is a configuration notecard based on the key-value data Wizardry and Steamworks reader.
# Everything following the "#" character is ignored along with blank lines. Values can be set for various
# parameters in a simple and understandable format with the following syntax:
# KEY := "VALUE"
# Every time you change this configuration notecard, the script will reset itself and the new configuration
# will be read from this notecard.
 
# The "corrade", "group" and "password" settings must correspond to your settings in Corrade.ini
 
# This is the UUID of the Corrade bot.
corrade = "0b20a4ee-89d8-43ce-ad5b-39a7fb81f5c3"
 
# The name of the group - it can also be the UUID of the group.
group = "My Group"
 
# The password for the group.
password = "mypassword"
 
# The avatar to whom local chat will be relayed to.
path = "<199.007645, 69.56356, 23.839432>, <199.007645, 66.018173, 23.839432>, <194.149078, 66.018173, 23.839432>, <194.149078, 69.623375, 23.839432>"
 
# The amount of seconds to wait between path points.
pause = "5"
 
# Whether to randomize the pause delay.
randomize = "true"
 
############################# END CONFIGURATION #############################
/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/configuration.txt
@@ -0,0 +1,30 @@
############################# START CONFIGURATION ############################
 
# This is a configuration notecard based on the key-value data Wizardry and Steamworks reader.
# Everything following the "#" character is ignored along with blank lines. Values can be set for various
# parameters in a simple and understandable format with the following syntax:
# KEY = "VALUE"
# Every time you change this configuration notecard, the script will reset itself and the new configuration
# will be read from this notecard.
 
# The "corrade", "group" and "password" settings must correspond to your settings in Corrade.ini
 
# This is the UUID of the Corrade bot.
corrade = "a87202c7-1b95-4d56-b099-53b6cb1517f2"
 
# The name of the group - it can also be the UUID of the group.
group = "My Group"
 
# The password for the group.
password = "mypassword"
 
# A CSV list of region names to escape to in case of a region restart.
# For example's sake, this example lists several infohubs that may be safe during a region restart.
regions = "Tahoe Springs,Castle Valeria,Barbarossa,Calleta"
 
# The name of the home region where Corrade will be teleported back after a region restart.
home = "Puguet Sound"
# The position on the home region to teleport back to as a local 3-vector.
position = "<130.588501, 169.906158, 3402.433838>"
 
############################# END CONFIGURATION #############################
/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/configuration.txt
@@ -0,0 +1,14 @@
####################### START CONFIGURATION ##################################
 
# All these settings must correspond to the settings in Corrade.ini.
 
# This is the UUID of the Corrade bot.
corrade = "e2187210-79de-4b26-8e46-41e23be0c487"
 
# The name of the group - it can also be the UUID of the group.
group = "My Group"
 
# The password for the group.
password = "mypassword"
 
####################### END CONFIGURATION ###################################
/remote-region-scanning-device/regions.txt
@@ -0,0 +1,2 @@
Puguet Sound
Milkyway Island
/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/configuration.txt
@@ -0,0 +1,37 @@
####################### START CONFIGURATION ##################################
 
# The UUID of the owner of the rental box.
owner = "ccad6083-cb3b-44e0-b1ba-26c640d1236d"
 
##
# Corrade settings.
##
##
# All these settings must correspond to the settings in Corrade.ini.
##
# This is the UUID of the Corrade bot.
corrade = "0afe54ca-93fb-4c2f-b7da-0623aec25a63"
# The name of the group - it can also be the UUID of the group.
group = "My Group"
# The password for the group.
password = "mypassword"
##
 
##
# Rental settings.
##
# The rental settings define a price for an amount of time such that the renter may pay any amount and
# the script is designed to compute the time that will be added to their rent.
##
# The price for renting this place in L$.
price = 100
# The time that this place can be rented for in seconds.
# Cheatsheet:
# 1 day is 86400 seconds (default)
# 1 week is 604800 seconds
rent = 120
# The name of the role to invite renters to.
role = "Ecto"
##
 
####################### END CONFIGURATION ###################################
/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();
}
}
/scheduled-group-notices/scheduled-group-notices.lsl
@@ -0,0 +1,188 @@
///////////////////////////////////////////////////////////////////////////
// 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. //
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
// CONFIGURATION //
///////////////////////////////////////////////////////////////////////////
// The UUID / Key of the scripted agent.
string CORRADE = "63c44c23-9f46-4f0d-b00a-5b0e3180a015";
// The name of the group to invite to.
string GROUP = "My Group";
// The password for that group in Corrade.ini.
string PASSWORD = "mypassword";
 
///////////////////////////////////////////////////////////////////////////
// END CONFIGURATION //
///////////////////////////////////////////////////////////////////////////
 
///////////////////////////////////////////////////////////////////////////
// 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) 2014 Wizardry and Steamworks - License: GNU GPLv3 //
///////////////////////////////////////////////////////////////////////////
string wasKeyValueSet(string k, string v, string data) {
if(llStringLength(k) == 0) return "";
if(llStringLength(v) == 0) return "";
if(llStringLength(data) == 0) return k + "=" + v;
integer i = llListFindList(
llList2ListStrided(
llParseString2List(data, ["&", "="], []),
0, -1, 2
),
[ k ]);
if(i != -1) return llDumpList2String(
llListReplaceList(
llParseString2List(data, ["&"], []),
[ k + "=" + v ],
i, i),
"&");
return data + "&" + k + "=" + v;
}
 
///////////////////////////////////////////////////////////////////////////
// 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"
)
);
}
 
// Store the URL for the callback.
string callback = "";
 
default {
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 sleep;
}
on_rez(integer num) {
llResetScript();
}
changed(integer change) {
if(change & CHANGED_REGION_START) {
llResetScript();
}
}
}
 
state sleep {
state_entry() {
// DEBUG
llOwnerSay("Waiting for linked-message...");
}
link_message(integer sender, integer num, string message, key id) {
list data = llCSV2List(message);
// Build a partial key-value pair string with the data we know.
string kvp = wasKeyValueEncode(
[
"command", "notice",
"group", wasURLEscape(GROUP),
"password", wasURLEscape(PASSWORD),
"callback", wasURLEscape(callback)
]
);
// Add the subject if there is one.
string subject = llList2String(data, 0);
if(subject != "") kvp = wasKeyValueSet("subject", wasURLEscape(subject), kvp);
// Add the message if there is one.
message = llList2String(data, 1);
if(message != "") kvp = wasKeyValueSet("message", wasURLEscape(message), kvp);
// Add the attachment if there is one.
string item = llList2String(data, 2);
if(item != "") kvp = wasKeyValueSet("item", wasURLEscape(item), kvp);
// Send off the notice to Corrade.
llInstantMessage(CORRADE, kvp);
}
http_request(key id, string method, string body) {
llHTTPResponse(id, 200, "OK");
// If sending the notice succeeded, do not complain...
if(wasKeyValueGet("command", body) == "notice" &&
wasKeyValueGet("success", body) == "True") return;
// Announce the owner that sending the notice failed
llInstantMessage(llGetOwner(), "Failed to send the notice: " + wasURLUnescape(wasKeyValueGet("error", body)));
}
on_rez(integer num) {
llResetScript();
}
changed(integer change) {
if(change & CHANGED_REGION_START) {
llResetScript();
}
}
}
/scheduled-group-notices/schedules.txt
@@ -0,0 +1,28 @@
###########################################################################
# The crontab script can send a linked message to the link set periodically
# at a specified time. The syntax is similar to the Unix crontab where
# asterisks * represent any possible value. Contrasted to some crontab
# implementations, this crontab does not support ranges (1-5) or
# enumerations (3,4,5) as you can find in some Unix implementation of
# crontab. However, you can always add as many crontab lines as you wish to
# be executed at a specified time. The file supports comments starting with
# the hash character "#" which can be placed in-line or at the start. Any
# text after the hash sign will be ignored.
###########################################################################
###########################################################################
# Syntax:
###########################################################################
# * * * * * message to send to the link-set
# | | | | |
# | | | | +-- day of week (0-7) (Sunday=0 or 7)
# | | | +----- month (1-12)
# | | +-------- day of month (1-31)
# | +----------- hour (0-23)
# +-------------- minute (0-59)
 
# ALL TIMES ARE IN SLT!
 
# at minute 10, hour 6, on the 7th day of the 9th month on a Sunday
# send a notice with the subject "Hello" and the message "Good day!"
10 6 7 9 7 Hello|Good day!
12 14 8 4 3 Test|Working!|Testcard
/select-dialog-options/menu-harness/configuration.txt
@@ -0,0 +1,17 @@
####################### START CONFIGURATION ##################################
 
# All these settings must correspond to the settings in Corrade.ini.
 
# This is the UUID of the Corrade bot.
corrade = "3ade3b74-378d-4fa1-88e6-96ea45d942bb"
 
# The name of the group - it can also be the UUID of the group.
group = "My Group"
 
# The password for the group.
password = "mypassword"
 
# The menu items to send via llDialog to Corrade as a CSV list of strings.
buttons = "AUTO"
 
####################### END CONFIGURATION ###################################
/select-dialog-options/menu-harness/menu-harness.lsl
@@ -0,0 +1,208 @@
///////////////////////////////////////////////////////////////////////////
// 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);
}
 
// corrade data
string CORRADE = "";
string GROUP = "";
string PASSWORD = "";
list BUTTONS = [];
 
// for notecard reading
integer line = 0;
// key-value data will be read into this list
list tuples = [];
 
default {
state_entry() {
// DEBUG
llSetText("Dialog harness: touch to\n send dialog to Corrade.", <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: password");
return;
}
PASSWORD = llList2String(
tuples,
llListFindList(
tuples,
[
"password"
]
)
+1);
if(PASSWORD == "") {
llOwnerSay("Error in configuration notecard: password");
return;
}
BUTTONS = llCSV2List(
llList2String(
tuples,
llListFindList(
tuples,
[
"buttons"
]
)
+1)
);
if(llGetListLength(BUTTONS) == 0) {
llOwnerSay("Error in configuration notecard: buttons");
return;
}
// DEBUG
llOwnerSay("Read configuration notecard...");
state dialog;
}
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 dialog {
state_entry() {
// DEBUG
llOwnerSay("Touch to send dialog to Corrade...");
}
touch_start(integer num) {
integer channel = -(integer)llFrand(100)-1;
llListen(channel, "", (key)CORRADE, "");
// DEBUG
llOwnerSay("Sending dialog...");
llDialog((key)CORRADE, "Please select from the options below: ", BUTTONS, channel);
}
listen(integer channel, string name, key id, string message) {
llOwnerSay("Corrade pressed button: " + message);
}
on_rez(integer num) {
llResetScript();
}
changed(integer change) {
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
llResetScript();
}
}
}
/select-dialog-options/select-dialog-options/configuration.txt
@@ -0,0 +1,20 @@
####################### START CONFIGURATION ##################################
 
# All these settings must correspond to the settings in Corrade.ini.
 
# This is the UUID of the Corrade bot.
corrade = "3ade3b74-378d-4fa1-88e6-96ea45d942bb"
 
# The name of the group - it can also be the UUID of the group.
group = "My Group"
 
# The password for the group.
password = "mypassword"
 
# The dialog menu items to press as a CSV list: this must be a list of exact button names that Corrade
# will press. If multiple buttons share the same name, only the first match will be chosen.
#
# This CSV string follows RFC4180 and read with wasListToCSV instead of llCSV2List (!)
response = "AUTO,60,-->>"
 
####################### END CONFIGURATION ###################################
/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/configuration.txt
@@ -0,0 +1,14 @@
####################### START CONFIGURATION ##################################
 
# All these settings must correspond to the settings in Corrade.ini.
 
# This is the UUID of the Corrade bot.
corrade = "c74590e1-b8a1-495a-aa96-1f4c2bc3b15a"
 
# The name of the group - it can also be the UUID of the group.
group = "My Group"
 
# The password for the group.
password = "mypassword"
 
####################### END CONFIGURATION ###################################
/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();
}
}
}
/sit-and-animate/set-sit-target.lsl
@@ -0,0 +1,6 @@
default {
state_entry()
{
llSitTarget(<0,0,0.1>, ZERO_ROTATION);
}
}
/sit-and-animate/sit-and-animate.lsl
@@ -0,0 +1,50 @@
///////////////////////////////////////////////////////////////////////////
// Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3 //
///////////////////////////////////////////////////////////////////////////
 
default {
state_entry() {
llSetTimerEvent(1);
}
timer() {
// If someone is sitting, hide the poseball.
key a = llAvatarOnSitTarget();
if(a == NULL_KEY) {
llSetAlpha(1, ALL_SIDES);
return;
}
llSetAlpha(0, ALL_SIDES);
}
run_time_permissions(integer perm) {
if(perm & PERMISSION_TRIGGER_ANIMATION) {
string o = llGetInventoryName(INVENTORY_ANIMATION, 0);
if(llGetInventoryType(o) != INVENTORY_ANIMATION) return;
key a = llAvatarOnSitTarget();
if(a == NULL_KEY) {
if(perm & PERMISSION_TRIGGER_ANIMATION) {
// DEBUG
llOwnerSay("Animation stopped...");
llSetAlpha(1, ALL_SIDES);
llStopAnimation(o);
}
return;
}
if(perm & PERMISSION_TRIGGER_ANIMATION) {
// DEBUG
llOwnerSay("Animation started...");
llSetAlpha(0, ALL_SIDES);
llStartAnimation(o);
}
}
}
changed(integer change) {
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
llResetScript();
}
if(change & CHANGED_LINK) {
key a = llAvatarOnSitTarget();
if(a == NULL_KEY) return;
llRequestPermissions(a, PERMISSION_TRIGGER_ANIMATION);
}
}
}
/texture-changing/configuration.txt
@@ -0,0 +1,14 @@
####################### START CONFIGURATION ##################################
 
# All these settings must correspond to the settings in Corrade.ini.
 
# This is the UUID of the Corrade bot.
corrade = "428cceb6-10ab-4fb8-b8a5-4e2c0650443a"
 
# The name of the group - it can also be the UUID of the group.
group = "My Group"
 
# The password for the group.
password = "mypassword"
 
####################### END CONFIGURATION ###################################
/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/configuration.txt
@@ -0,0 +1,21 @@
############################# START CONFIGURATION ############################
 
# This is a configuration notecard based on the key-value data Wizardry and Steamworks reader.
# Everything following the "#" character is ignored along with blank lines. Values can be set for various
# parameters in a simple and understandable format with the following syntax:
# KEY = "VALUE"
# Every time you change this configuration notecard, the script will reset itself and the new configuration
# will be read from this notecard.
 
# The "corrade", "group" and "password" settings must correspond to your settings in Corrade.ini
 
# This is the UUID of the Corrade bot.
corrade = "68505484-0699-4bc4-8f61-d74cd0c1146c"
 
# The name of the group - it can also be the UUID of the group.
group = "My Group"
 
# The password for the group.
password = "mypassword"
 
############################# END CONFIGURATION #############################
/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/configuration.txt
@@ -0,0 +1,27 @@
############## START CONFIGURATION ##############
 
# This is a configuration notecard based on the key-value data
# Wizardry and Steamworks reader. Everything following the "#"
# character is ignored along with blank lines. Values can be set
# for various parameters in a simple and understandable format
# with the following syntax: KEY = "VALUE"
# Every time you change this configuration notecard, the script
# will reset itself and the new configuration will be read.
 
# The "corrade", "group" and "password" settings must
# correspond to your settings in Corrade.ini
 
# This is the UUID of the Corrade bot.
corrade = "036558c9-df5e-4806-bd79-f7ef33eb6202"
 
# The name of the group - it can also be the UUID of the group.
group = "My Group"
 
# The password for the group.
password = "mypassword"
 
# An RFC4180 CSV list of avatar names or UUIDs from whom
# Corrade will automatically accept inventory offers
whitelist = "ebd52638-efec-4a39-8c08-f602a74314f1,"Big Lebowski","MrGonads Resident",81bec9b3-5544-460f-83d9-257f2fe97f70"
 
############## END CONFIGURATION ###############
/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();
}
}
}