corrade-lsl-templates

Subversion Repositories:
Compare Path: Rev
With Path: Rev
?path1? @ 5  →  ?path2? @ 6
/source/automated-hunt-system/automated-hunt-system.lsl
@@ -0,0 +1,755 @@
///////////////////////////////////////////////////////////////////////////
// Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3 //
///////////////////////////////////////////////////////////////////////////
//
// This is an automated hunt system template that illustrates various
// Corrade commands. You can find out more about the Corrade bot by
// following the URL: http://was.fm/secondlife/scripted_agents/corrade
//
// This script requires the following Corrade permissions:
// - movement
// - notifications
// - interact
// - inventory
// - economy
// It also requires the following Corrade notifications:
// - permission
//
// The template uses the "[WaS-K] Coin Bag" hunt item and Corrade must have
// the "[WaS-K] Coin Bag" object in its inventory. Other hunt objects are
// possible by setting "item" in the "configuration" notecard but the item
// must be sent to Corrade such that it is in the bot's inventory.
//
// The "configuration" notecard inside the primitive must be changed to
// reflect your settings.
//
// In case of panic, please see the full instructions on the project page:
// http://grimore.org/secondlife/scripted_agents/corrade/projects/in_world/automated_hunt_system
// or ask for help in the [Wizardry and Steamworks]:Support group or contact
// Kira Komarov in-world directly.
//
// This script works together with a "configuration" notecard that must be
// placed in the same primitive as this script. The purpose of this script
// to demonstrate an automated hunt system using Corrade and you are free
// to use, change, and commercialize it under the GNU/GPLv3 license at:
// http://www.gnu.org/licenses/gpl.html
//
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
// Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 //
///////////////////////////////////////////////////////////////////////////
string wasKeyValueGet(string k, string data) {
if(llStringLength(data) == 0) return "";
if(llStringLength(k) == 0) return "";
list a = llParseString2List(data, ["&", "="], []);
integer i = llListFindList(a, [ k ]);
if(i != -1) return llList2String(a, i+1);
return "";
}
///////////////////////////////////////////////////////////////////////////
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 //
///////////////////////////////////////////////////////////////////////////
string wasKeyValueEncode(list data) {
list k = llList2ListStrided(data, 0, -1, 2);
list v = llList2ListStrided(llDeleteSubList(data, 0, 0), 0, -1, 2);
data = [];
do {
data += llList2String(k, 0) + "=" + llList2String(v, 0);
k = llDeleteSubList(k, 0, 0);
v = llDeleteSubList(v, 0, 0);
} while(llGetListLength(k) != 0);
return llDumpList2String(data, "&");
}
///////////////////////////////////////////////////////////////////////////
// Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 //
///////////////////////////////////////////////////////////////////////////
integer wasListCountExclude(list input, list exclude) {
if(llGetListLength(input) == 0) return 0;
if(llListFindList(exclude, (list)llList2String(input, 0)) == -1)
return 1 + wasListCountExclude(llDeleteSubList(input, 0, 0), exclude);
return wasListCountExclude(llDeleteSubList(input, 0, 0), exclude);
}
///////////////////////////////////////////////////////////////////////////
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 //
///////////////////////////////////////////////////////////////////////////
// escapes a string in conformance with RFC1738
string wasURLEscape(string i) {
string o = "";
do {
string c = llGetSubString(i, 0, 0);
i = llDeleteSubString(i, 0, 0);
if(c == "") jump continue;
if(c == " ") {
o += "+";
jump continue;
}
if(c == "\n") {
o += "%0D" + llEscapeURL(c);
jump continue;
}
o += llEscapeURL(c);
@continue;
} while(i != "");
return o;
}
///////////////////////////////////////////////////////////////////////////
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 //
///////////////////////////////////////////////////////////////////////////
// unescapes a string in conformance with RFC1738
string wasURLUnescape(string i) {
return llUnescapeURL(
llDumpList2String(
llParseString2List(
llDumpList2String(
llParseString2List(
i,
["+"],
[]
),
" "
),
["%0D%0A"],
[]
),
"\n"
)
);
}
///////////////////////////////////////////////////////////////////////////
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 //
///////////////////////////////////////////////////////////////////////////
string wasListToCSV(list l) {
list v = [];
do {
string a = llDumpList2String(
llParseStringKeepNulls(
llList2String(
l,
0
),
["\""],
[]
),
"\"\""
);
if(llParseStringKeepNulls(
a,
[" ", ",", "\n", "\""], []
) !=
(list) a
) a = "\"" + a + "\"";
v += a;
l = llDeleteSubList(l, 0, 0);
} while(l != []);
return llDumpList2String(v, ",");
}
///////////////////////////////////////////////////////////////////////////
// Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 //
///////////////////////////////////////////////////////////////////////////
list wasCSVToList(string csv) {
list l = [];
list s = [];
string m = "";
do {
string a = llGetSubString(csv, 0, 0);
csv = llDeleteSubString(csv, 0, 0);
if(a == ",") {
if(llList2String(s, -1) != "\"") {
l += m;
m = "";
jump continue;
}
m += a;
jump continue;
}
if(a == "\"" && llGetSubString(csv, 0, 0) == a) {
m += a;
csv = llDeleteSubString(csv, 0, 0);
jump continue;
}
if(a == "\"") {
if(llList2String(s, -1) != a) {
s += a;
jump continue;
}
s = llDeleteSubList(s, -1, -1);
jump continue;
}
m += a;
@continue;
} while(csv != "");
// postcondition: length(s) = 0
return l + m;
}
// corrade data
string CORRADE = "";
string GROUP = "";
string PASSWORD = "";
// holds the name of the item to rez
string ITEM = "";
// for holding the callback URL
string callback = "";
// for notecard reading
integer line = 0;
// key-value data will be read into this list
list tuples = [];
// holds the location of hunt items
list POI = [];
list poi = [];
default {
state_entry() {
llSetText("", <1, 1, 1>, 1.0);
if(llGetInventoryType("configuration") != INVENTORY_NOTECARD) {
llOwnerSay("Sorry, could not find a configuration inventory notecard.");
return;
}
// DEBUG
llOwnerSay("Reading configuration file...");
llGetNotecardLine("configuration", line);
}
dataserver(key id, string data) {
if(data == EOF) {
// invariant, length(tuples) % 2 == 0
if(llGetListLength(tuples) % 2 != 0) {
llOwnerSay("Error in configuration notecard.");
return;
}
CORRADE = llList2String(
tuples,
llListFindList(
tuples,
[
"corrade"
]
)
+1
);
if(CORRADE == "") {
llOwnerSay("Error in configuration notecard: corrade");
return;
}
GROUP = llList2String(
tuples,
llListFindList(
tuples,
[
"group"
]
)
+1
);
if(GROUP == "") {
llOwnerSay("Error in configuration notecard: group");
return;
}
PASSWORD = llList2String(
tuples,
llListFindList(
tuples,
[
"password"
]
)
+1
);
if(PASSWORD == "") {
llOwnerSay("Error in configuration notecard: password");
return;
}
ITEM = llList2String(
tuples,
llListFindList(
tuples,
[
"item"
]
)
+1
);
if(ITEM == "") {
llOwnerSay("Error in configuration notecard: item");
return;
}
// BEGIN POI
integer i = llGetListLength(tuples)-1;
do {
string n = llList2String(tuples, i);
if(llSubStringIndex(n, "POI_") != -1) {
list l = llParseString2List(n, ["_"], []);
if(llList2String(l, 0) == "POI") {
integer x = llList2Integer(
l,
1
)-1;
// extend the polygon to the number of points
while(llGetListLength(POI) < x)
POI += "";
// and insert the point at the location
POI = llListReplaceList(
POI,
(list)(
(vector)(
"<" + llList2CSV(
llParseString2List(
llList2String(
tuples,
llListFindList(
tuples,
(list)n
)
+1
),
["<", ",", ">"],
[]
)
) + ">")
),
x,
x
);
}
}
} while(--i>-1);
// now clean up any empty slots
i = llGetListLength(POI)-1;
do {
if(llList2String(POI, i) == "")
POI = llDeleteSubList(POI, i, i);
} while(--i > -1);
// END POI
// DEBUG
llOwnerSay("Read configuration notecard...");
state url;
}
if(data == "") jump continue;
integer i = llSubStringIndex(data, "#");
if(i != -1) data = llDeleteSubString(data, i, -1);
list o = llParseString2List(data, ["="], []);
// get rid of starting and ending quotes
string k = llDumpList2String(
llParseString2List(
llStringTrim(
llList2String(
o,
0
),
STRING_TRIM),
["\""], []
), "\"");
string v = llDumpList2String(
llParseString2List(
llStringTrim(
llList2String(
o,
1
),
STRING_TRIM),
["\""], []
), "\"");
if(k == "" || v == "") jump continue;
tuples += k;
tuples += v;
@continue;
llGetNotecardLine("configuration", ++line);
}
on_rez(integer num) {
llResetScript();
}
changed(integer change) {
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
llResetScript();
}
}
}
state url {
state_entry() {
// DEBUG
llOwnerSay("Requesting URL...");
llRequestURL();
}
http_request(key id, string method, string body) {
if(method != URL_REQUEST_GRANTED) return;
callback = body;
// DEBUG
llOwnerSay("Got URL...");
state detect;
}
on_rez(integer num) {
llResetScript();
}
changed(integer change) {
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
llResetScript();
}
}
}
state detect {
state_entry() {
// DEBUG
llOwnerSay("Detecting if Corrade is online...");
llSetTimerEvent(1);
}
timer() {
llRequestAgentData((key)CORRADE, DATA_ONLINE);
}
dataserver(key id, string data) {
if(data != "1") {
// DEBUG
llOwnerSay("Corrade is not online, sleeping...");
llSetTimerEvent(5);
return;
}
state permission;
}
on_rez(integer num) {
llResetScript();
}
changed(integer change) {
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
llResetScript();
}
}
state_exit() {
llSetTimerEvent(0);
}
}
state permission {
state_entry() {
// DEBUG
llOwnerSay("Binding to the permission notification...");
llInstantMessage(CORRADE,
wasKeyValueEncode(
[
"command", "notify",
"group", wasURLEscape(GROUP),
"password", wasURLEscape(PASSWORD),
"action", "set",
"type", "permission",
"URL", wasURLEscape(callback),
"callback", wasURLEscape(callback)
]
)
);
llSetTimerEvent(60);
}
http_request(key id, string method, string body) {
llHTTPResponse(id, 200, "OK");
if(wasKeyValueGet("command", body) != "notify" ||
wasKeyValueGet("success", body) != "True") {
// DEBUG
llOwnerSay("Failed to bind to the permission notification: " +
wasURLUnescape(
wasKeyValueGet(
"error",
body
)
)
);
return;
}
// DEBUG
llOwnerSay("Permission notification installed...");
state menu;
}
timer() {
// alarm hit, permission notification not installed
llResetScript();
}
on_rez(integer num) {
llResetScript();
}
changed(integer change) {
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
llResetScript();
}
}
state_exit() {
llSetTimerEvent(0);
}
}
state menu {
state_entry() {
llSetText("Touch me for menu!", <0, 1, 0>, 1.0);
}
touch_start(integer num) {
if(llDetectedKey(0) != llGetOwner()) return;
integer comChannel = (integer)("0x8" + llGetSubString(llGetKey(), 0, 6));
llListen(comChannel, "", llGetOwner(), "");
llDialog(llGetOwner(), "The menu will allow you to cast and dispel the hunt items.", [ "Cast", "Dispel" ], comChannel);
}
listen(integer channel, string name, key id, string message) {
/* Copy the POI list to recurse over. */
poi = POI;
/* Process the dialog messages. */
if(message == "Cast") state rez;
if(message == "Dispel") state derez;
}
changed(integer change) {
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
llResetScript();
}
}
state_exit() {
llSetTimerEvent(0);
}
}
/*
* In order to rez all the items we permute the "poi" list by recursing over states.
* The "rez_trampoline" provides a trampoline for the "rez" state re-entry.
*/
state rez_trampoline {
state_entry() {
llSetTimerEvent(1);
}
timer() {
state rez;
}
state_exit() {
llSetTimerEvent(0);
}
}
/*
* Rez the hunt item from inventory, grant debit permission and trampoline for the
* next item in the POI list.
*/
state rez {
state_entry() {
// If we have rezzed all the objects, then stop rezzing.
if(llGetListLength(poi) == 0) state menu;
llSetText("Hunt items left to set-up: " +
(string)
llGetListLength(
poi
),
<0, 1, 1>,
1.0
);
// Permute POIs
string head = llList2String(poi, 0);
poi = llDeleteSubList(poi, 0, 0);
// DEBUG
llOwnerSay("Rezzing @ " + head);
llInstantMessage(CORRADE,
wasKeyValueEncode(
[
"command", "rez",
"group", wasURLEscape(GROUP),
"password", wasURLEscape(PASSWORD),
"position", wasURLEscape(head),
"item", wasURLEscape(ITEM),
"callback", wasURLEscape(callback)
]
)
);
}
http_request(key id, string method, string body) {
llHTTPResponse(id, 200, "OK");
// Get the result of rezzing the object.
if(wasKeyValueGet("command", body) == "rez") {
if(wasKeyValueGet("success", body) != "True") {
// DEBUG
llOwnerSay("Failed to rez the object: " +
wasURLUnescape(
wasKeyValueGet(
"error",
body
)
)
);
return;
}
llOwnerSay("Item rezzed...");
return;
}
// Grant debit permissions to the rezzed object.
if(wasKeyValueGet("type", body) == "permission" &&
wasKeyValueGet("permissions", body) == "Debit") {
llInstantMessage(CORRADE,
wasKeyValueEncode(
[
"command", "replytoscriptpermissionrequest",
"group", wasURLEscape(GROUP),
"password", wasURLEscape(PASSWORD),
"task", wasKeyValueGet("task", body),
"item", wasKeyValueGet("item", body),
"region", wasKeyValueGet("region", body),
"action", "reply",
"permissions", "Debit",
"callback", wasURLEscape(callback)
]
)
);
// DEBUG
llOwnerSay("Replying to permission request...");
return;
}
// Get the result of granting script permissions.
if(wasKeyValueGet("command", body) == "replytoscriptpermissionrequest") {
if(wasKeyValueGet("success", body) != "True") {
// DEBUG
llOwnerSay("Failed to grant permissions to the object: " +
wasURLUnescape(
wasKeyValueGet(
"error",
body
)
)
);
return;
}
llOwnerSay("Permissions granted...");
// Go for the next item in the POI list.
state rez_trampoline;
}
}
on_rez(integer num) {
llResetScript();
}
changed(integer change) {
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
llResetScript();
}
}
}
/*
* In order to de-rez the hunt items we first teleport Corrade in the vicinity
* of the POI and then issue a "derez" command to Corrade.
* Symmetrically to "rez", the "derez_trampoline" state provides a trampoline
* for the "derez" state re-entry.
*/
state derez_trampoline {
state_entry() {
llSetTimerEvent(1);
}
timer() {
state derez;
}
state_exit() {
llSetTimerEvent(0);
}
}
state derez {
state_entry() {
// If we have derezzed all the objects, then stop rezzing.
if(llGetListLength(poi) == 0) state menu;
llSetText("Hunt items left to remove: " +
(string)
llGetListLength(
poi
),
<0, 1, 1>,
1.0
);
// Permute POIs
string head = llList2String(poi, 0);
poi = llDeleteSubList(poi, 0, 0);
// DEBUG
llOwnerSay("Teleporting to: " + (string)head);
llInstantMessage((key)CORRADE,
wasKeyValueEncode(
[
"command", "teleport",
"group", wasURLEscape(GROUP),
"password", wasURLEscape(PASSWORD),
"region", wasURLEscape(llGetRegionName()),
"position", wasURLEscape(head),
"entity", "region",
"fly", "True",
"callback", wasURLEscape(callback)
]
)
);
}
http_request(key id, string method, string body) {
llHTTPResponse(id, 200, "OK");
// Get the result of teleporting to the POI.
if(wasKeyValueGet("command", body) == "teleport") {
// If the teleport did not succeed and the error was not that the destination
// was too close, then print the error and stop; otherwise, continue.
if(wasKeyValueGet("success", body) != "True" &&
wasKeyValueGet("status", body) != "37559") {
// DEBUG
llOwnerSay("Failed to teleport: " +
wasURLUnescape(
wasKeyValueGet(
"error",
body
)
)
);
return;
}
// DEBUG
llOwnerSay("Teleport succeeded...");
// If the teleport succeeded, request to derez the item.
llInstantMessage((key)CORRADE,
wasKeyValueEncode(
[
"command", "derez",
"group", wasURLEscape(GROUP),
"password", wasURLEscape(PASSWORD),
"item", wasURLEscape(ITEM),
"range", 5,
"callback", wasURLEscape(callback)
]
)
);
return;
}
// Get the result of the derez request.
if(wasKeyValueGet("command", body) == "derez") {
// If removing the item because the item was not found, then it was
// probably consumed during the hunt so carry on to the next destination.
if(wasKeyValueGet("success", body) != "True" &&
wasKeyValueGet("status", body) != "22693") {
// DEBUG
llOwnerSay("Failed to derez: " +
wasURLUnescape(
wasKeyValueGet(
"error",
body
)
)
);
return;
}
// DEBUG
llOwnerSay("Derez succeeded...");
state derez_trampoline;
}
}
on_rez(integer num) {
llResetScript();
}
changed(integer change) {
if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
llResetScript();
}
}
}