corrade-lsl-templates

Subversion Repositories:
Compare Path: Rev
With Path: Rev
?path1? @ 2  →  ?path2? @ 3
File deleted
/heads-up-display/scripts/grid-follow/grid-follow.lsl
File deleted
/heads-up-display/scripts/fly/fly.lsl
File deleted
/heads-up-display/scripts/point-at/point-at.lsl
File deleted
/heads-up-display/scripts/go-home/go-home.lsl
File deleted
/heads-up-display/scripts/permission-passthrough/permission-passthrough.lsl
File deleted
/heads-up-display/scripts/message-relay/message-relay.lsl
File deleted
\ No newline at end of file
/heads-up-display/scripts/teleport/teleport.lsl
File deleted
/heads-up-display/scripts/sit/sit.lsl
File deleted
\ No newline at end of file
/heads-up-display/scripts/joystick/joystick.lsl
File deleted
/heads-up-display/scripts/tell/tell.lsl
File deleted
/heads-up-display/scripts/patrol/patrol.lsl
File deleted
\ No newline at end of file
/heads-up-display/scripts/recall/recall.lsl
File deleted
/heads-up-display/scripts/heads-up-display.lsl
File deleted
/heads-up-display/scripts/dialog-passthrough/dialog-passthrough.lsl
File deleted
\ No newline at end of file
/heads-up-display/scripts/stand/stand.lsl
File deleted
\ No newline at end of file
/heads-up-display/scripts/relax/relax.lsl
File deleted
/heads-up-display/scripts/touch/touch.lsl
/heads-up-display/source/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/source/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/source/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/source/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/source/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/source/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/source/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/source/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/source/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/source/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/source/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/source/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/source/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/source/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/source/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/source/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/source/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/source/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);
}
}