corrade-lsl-templates – Blame information for rev 29

Subversion Repositories:
Rev:
Rev Author Line No. Line
4 office 1 ///////////////////////////////////////////////////////////////////////////
29 office 2 // Copyright (C) Wizardry and Steamworks 2014 - License: CC BY 2.0 //
4 office 3 ///////////////////////////////////////////////////////////////////////////
4 //
5 // This is an automated hunt system template that illustrates various
6 // Corrade commands. You can find out more about the Corrade bot by
7 // following the URL: http://was.fm/secondlife/scripted_agents/corrade
8 //
9 // This script requires the following Corrade permissions:
10 // - movement
11 // - notifications
12 // - interact
13 // - inventory
14 // - economy
15 // It also requires the following Corrade notifications:
16 // - permission
17 //
18 // The template uses the "[WaS-K] Coin Bag" hunt item and Corrade must have
19 // the "[WaS-K] Coin Bag" object in its inventory. Other hunt objects are
20 // possible by setting "item" in the "configuration" notecard but the item
21 // must be sent to Corrade such that it is in the bot's inventory.
22 //
23 // The "configuration" notecard inside the primitive must be changed to
24 // reflect your settings.
25 //
26 // In case of panic, please see the full instructions on the project page:
27 // http://grimore.org/secondlife/scripted_agents/corrade/projects/in_world/automated_hunt_system
28 // or ask for help in the [Wizardry and Steamworks]:Support group or contact
29 // Kira Komarov in-world directly.
30 //
31 // This script works together with a "configuration" notecard that must be
32 // placed in the same primitive as this script. The purpose of this script
33 // to demonstrate an automated hunt system using Corrade and you are free
29 office 34 // to use, change, and commercialize it under the CC BY 2.0 license at:
35 // https://creativecommons.org/licenses/by/2.0
4 office 36 //
37 ///////////////////////////////////////////////////////////////////////////
38  
39 ///////////////////////////////////////////////////////////////////////////
29 office 40 // Copyright (C) 2014 Wizardry and Steamworks - License: CC BY 2.0 //
4 office 41 ///////////////////////////////////////////////////////////////////////////
42 string wasKeyValueGet(string k, string data) {
43 if(llStringLength(data) == 0) return "";
44 if(llStringLength(k) == 0) return "";
45 list a = llParseString2List(data, ["&", "="], []);
46 integer i = llListFindList(a, [ k ]);
47 if(i != -1) return llList2String(a, i+1);
48 return "";
49 }
50  
51 ///////////////////////////////////////////////////////////////////////////
29 office 52 // Copyright (C) 2013 Wizardry and Steamworks - License: CC BY 2.0 //
4 office 53 ///////////////////////////////////////////////////////////////////////////
54 string wasKeyValueEncode(list data) {
55 list k = llList2ListStrided(data, 0, -1, 2);
56 list v = llList2ListStrided(llDeleteSubList(data, 0, 0), 0, -1, 2);
57 data = [];
58 do {
59 data += llList2String(k, 0) + "=" + llList2String(v, 0);
60 k = llDeleteSubList(k, 0, 0);
61 v = llDeleteSubList(v, 0, 0);
62 } while(llGetListLength(k) != 0);
63 return llDumpList2String(data, "&");
64 }
65  
66 ///////////////////////////////////////////////////////////////////////////
29 office 67 // Copyright (C) 2013 Wizardry and Steamworks - License: CC BY 2.0 //
4 office 68 ///////////////////////////////////////////////////////////////////////////
69 integer wasListCountExclude(list input, list exclude) {
70 if(llGetListLength(input) == 0) return 0;
71 if(llListFindList(exclude, (list)llList2String(input, 0)) == -1)
72 return 1 + wasListCountExclude(llDeleteSubList(input, 0, 0), exclude);
73 return wasListCountExclude(llDeleteSubList(input, 0, 0), exclude);
74 }
75  
76 ///////////////////////////////////////////////////////////////////////////
29 office 77 // Copyright (C) 2015 Wizardry and Steamworks - License: CC BY 2.0 //
4 office 78 ///////////////////////////////////////////////////////////////////////////
79 // escapes a string in conformance with RFC1738
80 string wasURLEscape(string i) {
81 string o = "";
82 do {
83 string c = llGetSubString(i, 0, 0);
84 i = llDeleteSubString(i, 0, 0);
85 if(c == "") jump continue;
86 if(c == " ") {
87 o += "+";
88 jump continue;
89 }
90 if(c == "\n") {
91 o += "%0D" + llEscapeURL(c);
92 jump continue;
93 }
94 o += llEscapeURL(c);
95 @continue;
96 } while(i != "");
97 return o;
98 }
99  
100 ///////////////////////////////////////////////////////////////////////////
29 office 101 // Copyright (C) 2015 Wizardry and Steamworks - License: CC BY 2.0 //
4 office 102 ///////////////////////////////////////////////////////////////////////////
103 // unescapes a string in conformance with RFC1738
104 string wasURLUnescape(string i) {
105 return llUnescapeURL(
106 llDumpList2String(
107 llParseString2List(
108 llDumpList2String(
109 llParseString2List(
110 i,
111 ["+"],
112 []
113 ),
114 " "
115 ),
116 ["%0D%0A"],
117 []
118 ),
119 "\n"
120 )
121 );
122 }
123  
124 ///////////////////////////////////////////////////////////////////////////
29 office 125 // Copyright (C) 2015 Wizardry and Steamworks - License: CC BY 2.0 //
4 office 126 ///////////////////////////////////////////////////////////////////////////
127 string wasListToCSV(list l) {
128 list v = [];
129 do {
130 string a = llDumpList2String(
131 llParseStringKeepNulls(
132 llList2String(
133 l,
134  
135 ),
136 ["\""],
137 []
138 ),
139 "\"\""
140 );
141 if(llParseStringKeepNulls(
142 a,
143 [" ", ",", "\n", "\""], []
144 ) !=
145 (list) a
146 ) a = "\"" + a + "\"";
147 v += a;
148 l = llDeleteSubList(l, 0, 0);
149 } while(l != []);
150 return llDumpList2String(v, ",");
151 }
152  
153 ///////////////////////////////////////////////////////////////////////////
29 office 154 // Copyright (C) 2015 Wizardry and Steamworks - License: CC BY 2.0 //
4 office 155 ///////////////////////////////////////////////////////////////////////////
156 list wasCSVToList(string csv) {
157 list l = [];
158 list s = [];
159 string m = "";
160 do {
161 string a = llGetSubString(csv, 0, 0);
162 csv = llDeleteSubString(csv, 0, 0);
163 if(a == ",") {
164 if(llList2String(s, -1) != "\"") {
165 l += m;
166 m = "";
167 jump continue;
168 }
169 m += a;
170 jump continue;
171 }
172 if(a == "\"" && llGetSubString(csv, 0, 0) == a) {
173 m += a;
174 csv = llDeleteSubString(csv, 0, 0);
175 jump continue;
176 }
177 if(a == "\"") {
178 if(llList2String(s, -1) != a) {
179 s += a;
180 jump continue;
181 }
182 s = llDeleteSubList(s, -1, -1);
183 jump continue;
184 }
185 m += a;
186 @continue;
187 } while(csv != "");
188 // postcondition: length(s) = 0
189 return l + m;
190 }
191  
192  
193 // corrade data
194 string CORRADE = "";
195 string GROUP = "";
196 string PASSWORD = "";
197 // holds the name of the item to rez
198 string ITEM = "";
199  
200 // for holding the callback URL
201 string callback = "";
202  
203 // for notecard reading
204 integer line = 0;
205  
206 // key-value data will be read into this list
207 list tuples = [];
208  
209 // holds the location of hunt items
210 list POI = [];
211 list poi = [];
212  
213 default {
214 state_entry() {
215 llSetText("", <1, 1, 1>, 1.0);
216 if(llGetInventoryType("configuration") != INVENTORY_NOTECARD) {
217 llOwnerSay("Sorry, could not find a configuration inventory notecard.");
218 return;
219 }
220 // DEBUG
221 llOwnerSay("Reading configuration file...");
222 llGetNotecardLine("configuration", line);
223 }
224 dataserver(key id, string data) {
225 if(data == EOF) {
226 // invariant, length(tuples) % 2 == 0
227 if(llGetListLength(tuples) % 2 != 0) {
228 llOwnerSay("Error in configuration notecard.");
229 return;
230 }
231 CORRADE = llList2String(
232 tuples,
233 llListFindList(
234 tuples,
235 [
236 "corrade"
237 ]
238 )
239 +1
240 );
241 if(CORRADE == "") {
242 llOwnerSay("Error in configuration notecard: corrade");
243 return;
244 }
245 GROUP = llList2String(
246 tuples,
247 llListFindList(
248 tuples,
249 [
250 "group"
251 ]
252 )
253 +1
254 );
255 if(GROUP == "") {
256 llOwnerSay("Error in configuration notecard: group");
257 return;
258 }
259 PASSWORD = llList2String(
260 tuples,
261 llListFindList(
262 tuples,
263 [
264 "password"
265 ]
266 )
267 +1
268 );
269 if(PASSWORD == "") {
270 llOwnerSay("Error in configuration notecard: password");
271 return;
272 }
273 ITEM = llList2String(
274 tuples,
275 llListFindList(
276 tuples,
277 [
278 "item"
279 ]
280 )
281 +1
282 );
283 if(ITEM == "") {
284 llOwnerSay("Error in configuration notecard: item");
285 return;
286 }
287  
288 // BEGIN POI
289 integer i = llGetListLength(tuples)-1;
290 do {
291 string n = llList2String(tuples, i);
292 if(llSubStringIndex(n, "POI_") != -1) {
293 list l = llParseString2List(n, ["_"], []);
294 if(llList2String(l, 0) == "POI") {
295 integer x = llList2Integer(
296 l,
297 1
298 )-1;
299 // extend the polygon to the number of points
300 while(llGetListLength(POI) < x)
301 POI += "";
302 // and insert the point at the location
303 POI = llListReplaceList(
304 POI,
305 (list)(
306 (vector)(
307 "<" + llList2CSV(
308 llParseString2List(
309 llList2String(
310 tuples,
311 llListFindList(
312 tuples,
313 (list)n
314 )
315 +1
316 ),
317 ["<", ",", ">"],
318 []
319 )
320 ) + ">")
321 ),
322 x,
323 x
324 );
325 }
326 }
327 } while(--i>-1);
328 // now clean up any empty slots
329 i = llGetListLength(POI)-1;
330 do {
331 if(llList2String(POI, i) == "")
332 POI = llDeleteSubList(POI, i, i);
333 } while(--i > -1);
334 // END POI
335  
336 // DEBUG
337 llOwnerSay("Read configuration notecard...");
338 state url;
339 }
340 if(data == "") jump continue;
341 integer i = llSubStringIndex(data, "#");
342 if(i != -1) data = llDeleteSubString(data, i, -1);
343 list o = llParseString2List(data, ["="], []);
344 // get rid of starting and ending quotes
345 string k = llDumpList2String(
346 llParseString2List(
347 llStringTrim(
348 llList2String(
349 o,
350  
351 ),
352 STRING_TRIM),
353 ["\""], []
354 ), "\"");
355 string v = llDumpList2String(
356 llParseString2List(
357 llStringTrim(
358 llList2String(
359 o,
360 1
361 ),
362 STRING_TRIM),
363 ["\""], []
364 ), "\"");
365 if(k == "" || v == "") jump continue;
366 tuples += k;
367 tuples += v;
368 @continue;
369 llGetNotecardLine("configuration", ++line);
370 }
371 on_rez(integer num) {
372 llResetScript();
373 }
374 changed(integer change) {
375 if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
376 llResetScript();
377 }
378 }
379 }
380  
381 state url {
382 state_entry() {
383 // DEBUG
384 llOwnerSay("Requesting URL...");
385 llRequestURL();
386 }
387 http_request(key id, string method, string body) {
388 if(method != URL_REQUEST_GRANTED) return;
389 callback = body;
390 // DEBUG
391 llOwnerSay("Got URL...");
392 state detect;
393 }
394 on_rez(integer num) {
395 llResetScript();
396 }
397 changed(integer change) {
398 if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
399 llResetScript();
400 }
401 }
402 }
403  
404 state detect {
405 state_entry() {
406 // DEBUG
407 llOwnerSay("Detecting if Corrade is online...");
408 llSetTimerEvent(1);
409 }
410 timer() {
411 llRequestAgentData((key)CORRADE, DATA_ONLINE);
412 }
413 dataserver(key id, string data) {
414 if(data != "1") {
415 // DEBUG
416 llOwnerSay("Corrade is not online, sleeping...");
417 llSetTimerEvent(5);
418 return;
419 }
420 state permission;
421 }
422 on_rez(integer num) {
423 llResetScript();
424 }
425 changed(integer change) {
426 if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
427 llResetScript();
428 }
429 }
430 state_exit() {
431 llSetTimerEvent(0);
432 }
433 }
434  
435 state permission {
436 state_entry() {
437 // DEBUG
438 llOwnerSay("Binding to the permission notification...");
439 llInstantMessage(CORRADE,
440 wasKeyValueEncode(
441 [
442 "command", "notify",
443 "group", wasURLEscape(GROUP),
444 "password", wasURLEscape(PASSWORD),
445 "action", "set",
446 "type", "permission",
447 "URL", wasURLEscape(callback),
448 "callback", wasURLEscape(callback)
449 ]
450 )
451 );
452 llSetTimerEvent(60);
453 }
454 http_request(key id, string method, string body) {
455 llHTTPResponse(id, 200, "OK");
456 if(wasKeyValueGet("command", body) != "notify" ||
457 wasKeyValueGet("success", body) != "True") {
458 // DEBUG
459 llOwnerSay("Failed to bind to the permission notification: " +
460 wasURLUnescape(
461 wasKeyValueGet(
462 "error",
463 body
464 )
465 )
466 );
467 return;
468 }
469 // DEBUG
470 llOwnerSay("Permission notification installed...");
471 state menu;
472 }
473 timer() {
474 // alarm hit, permission notification not installed
475 llResetScript();
476 }
477 on_rez(integer num) {
478 llResetScript();
479 }
480 changed(integer change) {
481 if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
482 llResetScript();
483 }
484 }
485 state_exit() {
486 llSetTimerEvent(0);
487 }
488 }
489  
490 state menu {
491 state_entry() {
492 llSetText("Touch me for menu!", <0, 1, 0>, 1.0);
493 }
494 touch_start(integer num) {
495 if(llDetectedKey(0) != llGetOwner()) return;
496 integer comChannel = (integer)("0x8" + llGetSubString(llGetKey(), 0, 6));
497 llListen(comChannel, "", llGetOwner(), "");
498 llDialog(llGetOwner(), "The menu will allow you to cast and dispel the hunt items.", [ "Cast", "Dispel" ], comChannel);
499 }
500 listen(integer channel, string name, key id, string message) {
501 /* Copy the POI list to recurse over. */
502 poi = POI;
503 /* Process the dialog messages. */
504 if(message == "Cast") state rez;
505 if(message == "Dispel") state derez;
506 }
507 changed(integer change) {
508 if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
509 llResetScript();
510 }
511 }
512 state_exit() {
513 llSetTimerEvent(0);
514 }
515 }
516  
517 /*
518 * In order to rez all the items we permute the "poi" list by recursing over states.
519 * The "rez_trampoline" provides a trampoline for the "rez" state re-entry.
520 */
521 state rez_trampoline {
522 state_entry() {
523 llSetTimerEvent(1);
524 }
525 timer() {
526 state rez;
527 }
528 state_exit() {
529 llSetTimerEvent(0);
530 }
531 }
532  
533 /*
534 * Rez the hunt item from inventory, grant debit permission and trampoline for the
535 * next item in the POI list.
536 */
537 state rez {
538 state_entry() {
539 // If we have rezzed all the objects, then stop rezzing.
540 if(llGetListLength(poi) == 0) state menu;
541 llSetText("Hunt items left to set-up: " +
542 (string)
543 llGetListLength(
544 poi
545 ),
546 <0, 1, 1>,
547 1.0
548 );
549 // Permute POIs
550 string head = llList2String(poi, 0);
551 poi = llDeleteSubList(poi, 0, 0);
552  
553 // DEBUG
554 llOwnerSay("Rezzing @ " + head);
555  
556 llInstantMessage(CORRADE,
557 wasKeyValueEncode(
558 [
559 "command", "rez",
560 "group", wasURLEscape(GROUP),
561 "password", wasURLEscape(PASSWORD),
562 "position", wasURLEscape(head),
563 "item", wasURLEscape(ITEM),
564 "callback", wasURLEscape(callback)
565 ]
566 )
567 );
568 }
569 http_request(key id, string method, string body) {
570 llHTTPResponse(id, 200, "OK");
571 // Get the result of rezzing the object.
572 if(wasKeyValueGet("command", body) == "rez") {
573 if(wasKeyValueGet("success", body) != "True") {
574 // DEBUG
575 llOwnerSay("Failed to rez the object: " +
576 wasURLUnescape(
577 wasKeyValueGet(
578 "error",
579 body
580 )
581 )
582 );
583 return;
584 }
585 llOwnerSay("Item rezzed...");
586 return;
587 }
588 // Grant debit permissions to the rezzed object.
589 if(wasKeyValueGet("type", body) == "permission" &&
590 wasKeyValueGet("permissions", body) == "Debit") {
591 llInstantMessage(CORRADE,
592 wasKeyValueEncode(
593 [
594 "command", "replytoscriptpermissionrequest",
595 "group", wasURLEscape(GROUP),
596 "password", wasURLEscape(PASSWORD),
597 "task", wasKeyValueGet("task", body),
598 "item", wasKeyValueGet("item", body),
599 "region", wasKeyValueGet("region", body),
600 "action", "reply",
601 "permissions", "Debit",
602 "callback", wasURLEscape(callback)
603 ]
604 )
605 );
606 // DEBUG
607 llOwnerSay("Replying to permission request...");
608 return;
609 }
610 // Get the result of granting script permissions.
611 if(wasKeyValueGet("command", body) == "replytoscriptpermissionrequest") {
612 if(wasKeyValueGet("success", body) != "True") {
613 // DEBUG
614 llOwnerSay("Failed to grant permissions to the object: " +
615 wasURLUnescape(
616 wasKeyValueGet(
617 "error",
618 body
619 )
620 )
621 );
622 return;
623 }
624 llOwnerSay("Permissions granted...");
625 // Go for the next item in the POI list.
626 state rez_trampoline;
627 }
628 }
629 on_rez(integer num) {
630 llResetScript();
631 }
632 changed(integer change) {
633 if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
634 llResetScript();
635 }
636 }
637 }
638  
639 /*
640 * In order to de-rez the hunt items we first teleport Corrade in the vicinity
641 * of the POI and then issue a "derez" command to Corrade.
642 * Symmetrically to "rez", the "derez_trampoline" state provides a trampoline
643 * for the "derez" state re-entry.
644 */
645 state derez_trampoline {
646 state_entry() {
647 llSetTimerEvent(1);
648 }
649 timer() {
650 state derez;
651 }
652 state_exit() {
653 llSetTimerEvent(0);
654 }
655 }
656  
657 state derez {
658 state_entry() {
659 // If we have derezzed all the objects, then stop rezzing.
660 if(llGetListLength(poi) == 0) state menu;
661 llSetText("Hunt items left to remove: " +
662 (string)
663 llGetListLength(
664 poi
665 ),
666 <0, 1, 1>,
667 1.0
668 );
669 // Permute POIs
670 string head = llList2String(poi, 0);
671 poi = llDeleteSubList(poi, 0, 0);
672 // DEBUG
673 llOwnerSay("Teleporting to: " + (string)head);
674 llInstantMessage((key)CORRADE,
675 wasKeyValueEncode(
676 [
677 "command", "teleport",
678 "group", wasURLEscape(GROUP),
679 "password", wasURLEscape(PASSWORD),
680 "region", wasURLEscape(llGetRegionName()),
681 "position", wasURLEscape(head),
682 "entity", "region",
683 "fly", "True",
684 "callback", wasURLEscape(callback)
685 ]
686 )
687 );
688 }
689 http_request(key id, string method, string body) {
690 llHTTPResponse(id, 200, "OK");
691 // Get the result of teleporting to the POI.
692 if(wasKeyValueGet("command", body) == "teleport") {
693 // If the teleport did not succeed and the error was not that the destination
694 // was too close, then print the error and stop; otherwise, continue.
695 if(wasKeyValueGet("success", body) != "True" &&
696 wasKeyValueGet("status", body) != "37559") {
697 // DEBUG
698 llOwnerSay("Failed to teleport: " +
699 wasURLUnescape(
700 wasKeyValueGet(
701 "error",
702 body
703 )
704 )
705 );
706 return;
707 }
708 // DEBUG
709 llOwnerSay("Teleport succeeded...");
710 // If the teleport succeeded, request to derez the item.
711 llInstantMessage((key)CORRADE,
712 wasKeyValueEncode(
713 [
714 "command", "derez",
715 "group", wasURLEscape(GROUP),
716 "password", wasURLEscape(PASSWORD),
717 "item", wasURLEscape(ITEM),
718 "range", 5,
719 "callback", wasURLEscape(callback)
720 ]
721 )
722 );
723 return;
724 }
725 // Get the result of the derez request.
726 if(wasKeyValueGet("command", body) == "derez") {
727 // If removing the item because the item was not found, then it was
728 // probably consumed during the hunt so carry on to the next destination.
729 if(wasKeyValueGet("success", body) != "True" &&
730 wasKeyValueGet("status", body) != "22693") {
731 // DEBUG
732 llOwnerSay("Failed to derez: " +
733 wasURLUnescape(
734 wasKeyValueGet(
735 "error",
736 body
737 )
738 )
739 );
740 return;
741 }
742 // DEBUG
743 llOwnerSay("Derez succeeded...");
744 state derez_trampoline;
745 }
746 }
747 on_rez(integer num) {
748 llResetScript();
749 }
750 changed(integer change) {
751 if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
752 llResetScript();
753 }
754 }
755 }