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 2016 - License: CC BY 2.0 //
4 office 3 ///////////////////////////////////////////////////////////////////////////
4 //
5 // This project makes Corrade, the Second Life / OpenSim bot evade region
6 // restarts by teleporting to other regions. More details about Corrade
7 // can be found at the URL:
8 //
9 // http://grimore.org/secondlife/scripted_agents/corrade
10 //
11 // The script works in combination with a "configuration" notecard that
12 // must be placed in the same primitive as this script. The purpose of this
13 // script is to illustrate how region restarts can be evaded with Corrade
14 // and you are free to use, change, and commercialize it under the terms
29 office 15 // of the CC BY 2.0 license at: https://creativecommons.org/licenses/by/2.0
4 office 16 //
17 ///////////////////////////////////////////////////////////////////////////
18  
19 ///////////////////////////////////////////////////////////////////////////
29 office 20 // Copyright (C) 2014 Wizardry and Steamworks - License: CC BY 2.0 //
4 office 21 ///////////////////////////////////////////////////////////////////////////
22 string wasKeyValueGet(string k, string data) {
23 if(llStringLength(data) == 0) return "";
24 if(llStringLength(k) == 0) return "";
25 list a = llParseString2List(data, ["&", "="], []);
26 integer i = llListFindList(a, [ k ]);
27 if(i != -1) return llList2String(a, i+1);
28 return "";
29 }
30  
31 ///////////////////////////////////////////////////////////////////////////
29 office 32 // Copyright (C) 2013 Wizardry and Steamworks - License: CC BY 2.0 //
4 office 33 ///////////////////////////////////////////////////////////////////////////
34 string wasKeyValueEncode(list data) {
35 list k = llList2ListStrided(data, 0, -1, 2);
36 list v = llList2ListStrided(llDeleteSubList(data, 0, 0), 0, -1, 2);
37 data = [];
38 do {
39 data += llList2String(k, 0) + "=" + llList2String(v, 0);
40 k = llDeleteSubList(k, 0, 0);
41 v = llDeleteSubList(v, 0, 0);
42 } while(llGetListLength(k) != 0);
43 return llDumpList2String(data, "&");
44 }
45  
46 ///////////////////////////////////////////////////////////////////////////
29 office 47 // Copyright (C) 2015 Wizardry and Steamworks - License: CC BY 2.0 //
4 office 48 ///////////////////////////////////////////////////////////////////////////
49 // escapes a string in conformance with RFC1738
50 string wasURLEscape(string i) {
51 string o = "";
52 do {
53 string c = llGetSubString(i, 0, 0);
54 i = llDeleteSubString(i, 0, 0);
55 if(c == "") jump continue;
56 if(c == " ") {
57 o += "+";
58 jump continue;
59 }
60 if(c == "\n") {
61 o += "%0D" + llEscapeURL(c);
62 jump continue;
63 }
64 o += llEscapeURL(c);
65 @continue;
66 } while(i != "");
67 return o;
68 }
69  
70 ///////////////////////////////////////////////////////////////////////////
29 office 71 // Copyright (C) 2015 Wizardry and Steamworks - License: CC BY 2.0 //
4 office 72 ///////////////////////////////////////////////////////////////////////////
73 // unescapes a string in conformance with RFC1738
74 string wasURLUnescape(string i) {
75 return llUnescapeURL(
76 llDumpList2String(
77 llParseString2List(
78 llDumpList2String(
79 llParseString2List(
80 i,
81 ["+"],
82 []
83 ),
84 " "
85 ),
86 ["%0D%0A"],
87 []
88 ),
89 "\n"
90 )
91 );
92 }
93  
94 ///////////////////////////////////////////////////////////////////////////
29 office 95 // Copyright (C) 2015 Wizardry and Steamworks - License: CC BY 2.0 //
4 office 96 ///////////////////////////////////////////////////////////////////////////
97 list wasCSVToList(string csv) {
98 list l = [];
99 list s = [];
100 string m = "";
101 do {
102 string a = llGetSubString(csv, 0, 0);
103 csv = llDeleteSubString(csv, 0, 0);
104 if(a == ",") {
105 if(llList2String(s, -1) != "\"") {
106 l += m;
107 m = "";
108 jump continue;
109 }
110 m += a;
111 jump continue;
112 }
113 if(a == "\"" && llGetSubString(csv, 0, 0) == a) {
114 m += a;
115 csv = llDeleteSubString(csv, 0, 0);
116 jump continue;
117 }
118 if(a == "\"") {
119 if(llList2String(s, -1) != a) {
120 s += a;
121 jump continue;
122 }
123 s = llDeleteSubList(s, -1, -1);
124 jump continue;
125 }
126 m += a;
127 @continue;
128 } while(csv != "");
129 // postcondition: length(s) = 0
130 return l + m;
131 }
132  
133 // corrade data
134 key CORRADE = NULL_KEY;
135 string GROUP = "";
136 string PASSWORD = "";
137  
138 // hold all the escape regions
139 list REGIONS = [];
140 // the home region name
141 string HOME_REGION = "";
142 vector HOME_POSITION = ZERO_VECTOR;
143 // holds the restat delay dynamically
144 integer RESTART_DELAY = 0;
145  
146 // for holding the callback URL
147 string callback = "";
148  
149 // for notecard reading
150 integer line = 0;
151  
152 // key-value data will be read into this list
153 list tuples = [];
154  
155 // jump label
156 string setjmp = "";
157  
158 default {
159 state_entry() {
160 if(llGetInventoryType("configuration") != INVENTORY_NOTECARD) {
161 llOwnerSay("Sorry, could not find a configuration inventory notecard.");
162 return;
163 }
164 // DEBUG
165 llOwnerSay("Reading configuration file...");
166 llGetNotecardLine("configuration", line);
167 }
168 dataserver(key id, string data) {
169 if(data == EOF) {
170 // invariant, length(tuples) % 2 == 0
171 if(llGetListLength(tuples) % 2 != 0) {
172 llOwnerSay("Error in configuration notecard.");
173 return;
174 }
175 CORRADE = llList2Key(
176 tuples,
177 llListFindList(
178 tuples,
179 [
180 "corrade"
181 ]
182 )
183 +1
184 );
185 if(CORRADE == NULL_KEY) {
186 llOwnerSay("Error in configuration notecard: corrade");
187 return;
188 }
189 GROUP = llList2String(
190 tuples,
191 llListFindList(
192 tuples,
193 [
194 "group"
195 ]
196 )
197 +1
198 );
199 if(GROUP == "") {
200 llOwnerSay("Error in configuration notecard: group");
201 return;
202 }
203 PASSWORD = llList2String(
204 tuples,
205 llListFindList(
206 tuples,
207 [
208 "password"
209 ]
210 )
211 +1
212 );
213 if(PASSWORD == "") {
214 llOwnerSay("Error in configuration notecard: password");
215 return;
216 }
217 REGIONS = wasCSVToList(
218 llList2String(
219 tuples,
220 llListFindList(
221 tuples,
222 [
223 "regions"
224 ]
225 )
226 +1
227 )
228 );
229 if(REGIONS == []) {
230 llOwnerSay("Error in configuration notecard: regions");
231 return;
232 }
233 HOME_REGION = llList2String(
234 tuples,
235 llListFindList(
236 tuples,
237 [
238 "home"
239 ]
240 )
241 +1
242 );
243 if(HOME_REGION == "") {
244 llOwnerSay("Error in configuration notecard: home");
245 return;
246 }
247 HOME_POSITION = (vector)llList2String(
248 tuples,
249 llListFindList(
250 tuples,
251 [
252 "position"
253 ]
254 )
255 +1
256 );
257 if(HOME_POSITION == ZERO_VECTOR) {
258 llOwnerSay("Error in configuration notecard: position");
259 return;
260 }
261 // DEBUG
262 llOwnerSay("Read configuration notecard...");
263  
264 // The notecard has been read, so get and URL and switch into detect.
265 setjmp = "detect";
266 state url;
267 }
268 if(data == "") jump continue;
269 integer i = llSubStringIndex(data, "#");
270 if(i != -1) data = llDeleteSubString(data, i, -1);
271 list o = llParseString2List(data, ["="], []);
272 // get rid of starting and ending quotes
273 string k = llDumpList2String(
274 llParseString2List(
275 llStringTrim(
276 llList2String(
277 o,
278  
279 ),
280 STRING_TRIM),
281 ["\""], []
282 ), "\"");
283 string v = llDumpList2String(
284 llParseString2List(
285 llStringTrim(
286 llList2String(
287 o,
288 1
289 ),
290 STRING_TRIM),
291 ["\""], []
292 ), "\"");
293 if(k == "" || v == "") jump continue;
294 tuples += k;
295 tuples += v;
296 @continue;
297 llGetNotecardLine("configuration", ++line);
298 }
299 on_rez(integer num) {
300 llResetScript();
301 }
302 changed(integer change) {
303 if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
304 llResetScript();
305 }
306 }
307 }
308  
309 state url {
310 state_entry() {
311 // DEBUG
312 llOwnerSay("Requesting URL...");
313 llRequestURL();
314 }
315 http_request(key id, string method, string body) {
316 if(method != URL_REQUEST_GRANTED) return;
317 callback = body;
318  
319 // DEBUG
320 llOwnerSay("Got URL...");
321  
322 // Jump table
323 if(setjmp == "detect") state detect;
324 if(setjmp == "recall") state recall;
325  
326 // Here be HALT.
327 llResetScript();
328 }
329 on_rez(integer num) {
330 llResetScript();
331 }
332 changed(integer change) {
333 if((change & CHANGED_INVENTORY)) {
334 llResetScript();
335 }
336 }
337 }
338  
339 state detect {
340 state_entry() {
341 // DEBUG
342 llOwnerSay("Detecting if Corrade is online...");
343 llSetTimerEvent(5);
344 }
345 timer() {
346 llRequestAgentData((key)CORRADE, DATA_ONLINE);
347 }
348 dataserver(key id, string data) {
349 if(data != "1") {
350 // DEBUG
351 llOwnerSay("Corrade is not online, sleeping...");
352 llSetTimerEvent(30);
353 return;
354 }
355 state notify;
356 }
357 on_rez(integer num) {
358 llResetScript();
359 }
360 changed(integer change) {
361 if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
362 llResetScript();
363 }
364 }
365 }
366  
367 state notify {
368 state_entry() {
369 // DEBUG
370 llOwnerSay("Binding to the alert notification...");
371 llInstantMessage(
372 (key)CORRADE,
373 wasKeyValueEncode(
374 [
375 "command", "notify",
376 "group", wasURLEscape(GROUP),
377 "password", wasURLEscape(PASSWORD),
378 "action", "set",
379 "type", "alert",
380 "URL", wasURLEscape(callback),
381 "callback", wasURLEscape(callback)
382 ]
383 )
384 );
385 }
386 http_request(key id, string method, string body) {
387 llHTTPResponse(id, 200, "OK");
388 if(wasKeyValueGet("command", body) != "notify") return;
389 if(wasKeyValueGet("success", body) != "True") {
390 // DEBUG
391 llOwnerSay("Failed to bind to the alert notification...");
392 state sense;
393 }
394 // DEBUG
395 llOwnerSay("Alert notification installed...");
396 state sense;
397 }
398 on_rez(integer num) {
399 llResetScript();
400 }
401 changed(integer change) {
402 if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
403 llResetScript();
404 }
405 }
406 }
407  
408 state sense {
409 state_entry() {
410 // DEBUG
411 llOwnerSay("Waiting for alert messages for region restarts...");
412 }
413 timer() {
414 llRequestAgentData((key)CORRADE, DATA_ONLINE);
415 }
416 dataserver(key id, string data) {
417 if(data == "1") return;
418 // DEBUG
419 llOwnerSay("Corrade is not online, sleeping...");
420 // Switch to detect loop and wait there for Corrade to come online.
421 state detect;
422 }
423 http_request(key id, string method, string body) {
424 llHTTPResponse(id, 200, "OK");
425 // Get the number of minutes after which the region will go down.
426 RESTART_DELAY = llList2Integer(
427 llParseString2List(
428 wasURLUnescape(
429 wasKeyValueGet(
430 "message",
431 body
432 )
433 ),
434 [
435 " will restart in ",
436 " minutes."
437 ],
438 []
439 ),
440 1
441 );
442  
443 if(RESTART_DELAY == 0) return;
444  
445 // DEBUG
446 llOwnerSay("Attempting to evade region restart...");
447  
448 // Evade!
449 state evade;
450 }
451 on_rez(integer num) {
452 llResetScript();
453 }
454 changed(integer change) {
455 if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
456 llResetScript();
457 }
458 }
459 }
460  
461 state evade_trampoline {
462 state_entry() {
463 state evade;
464 }
465 }
466  
467 state evade {
468 state_entry() {
469 // DEBUG
470 llOwnerSay("Teleporting Corrade out of the region...");
471 // Alarm 60
472 llSetTimerEvent(60);
473 // Shuffle regions.
474 string region = llList2String(REGIONS, 0);
475 REGIONS = llDeleteSubList(REGIONS, 0, 0);
476 REGIONS += region;
477 llInstantMessage(
478 (key)CORRADE, wasKeyValueEncode(
479 [
480 "command", "teleport",
481 "group", wasURLEscape(GROUP),
482 "password", wasURLEscape(PASSWORD),
483 "entity", "region",
484 "region", wasURLEscape(region),
485 "callback", wasURLEscape(callback)
486 ]
487 )
488 );
489 }
490 timer() {
491 state evade_trampoline;
492 }
493 http_request(key id, string method, string body) {
494 llHTTPResponse(id, 200, "OK");
495 // This message was most likely not for us.
496 if(wasKeyValueGet("command", body)
497 != "teleport") return;
498 if(wasKeyValueGet("success", body) != "True") {
499 // DEBUG
500 llOwnerSay("Failed teleport, retrying...");
501 state evade_trampoline;
502 }
503  
504 // DEBUG
505 llOwnerSay("Corrade evaded region restart...");
506  
507 state confront;
508 }
509 on_rez(integer num) {
510 llResetScript();
511 }
512 changed(integer change) {
513 if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
514 llResetScript();
515 }
516 }
517 state_exit() {
518 llSetTimerEvent(0);
519 }
520 }
521  
522 /*
523 * This state is suspended for the duration of the simulator downtime.
524 * Effects:
525 * - callback URL is lost
526 * - timer is resumed
527 * - CHANGED_REGION_RESTART raised
528 */
529 state confront {
530 state_entry() {
531 // Marty! The future is in the past.
532 // The past is in the future! - Doc Brown, Back To The Future
533 //
534 // Schedule an event after the scheduled restart delay
535 // sent to the region plus some convenience offset (60s).
536 //
537 // Even if the region is not back up after the restart
538 // delay and the convenience offset, the script will not
539 // be running anyway since the sim will be offline and
540 // will be suspended.
541 //
542 // Instead, when the region is back up, the timer will
543 // resume and the script will eventually raise the event.
544 llSetTimerEvent(RESTART_DELAY * 60 + 60);
545 }
546 timer() {
547 // Ok, either the region has restarted or the event was raised.
548 //
549 // Refresh the URL and then recall.
550 setjmp = "recall";
551 state url;
552 }
553 on_rez(integer num) {
554 llResetScript();
555 }
556 changed(integer change) {
557 if(change & CHANGED_INVENTORY) {
558 llResetScript();
559 }
560 }
561 state_exit() {
562 llSetTimerEvent(0);
563 }
564 }
565  
566 state recall_trampoline {
567 state_entry() {
568 state recall;
569 }
570 }
571  
572 state recall {
573 state_entry() {
574 // DEBUG
575 llOwnerSay("Teleporting Corrade back to the home region...");
576 // Alarm 60
577 llSetTimerEvent(60);
578 llInstantMessage(
579 (key)CORRADE, wasKeyValueEncode(
580 [
581 "command", "teleport",
582 "group", wasURLEscape(GROUP),
583 "password", wasURLEscape(PASSWORD),
584 "entity", "region",
585 "region", wasURLEscape(HOME_REGION),
586 "position", wasURLEscape((string)HOME_POSITION),
587 "callback", wasURLEscape(callback)
588 ]
589 )
590 );
591 }
592 timer() {
593 state recall_trampoline;
594 }
595 http_request(key id, string method, string body) {
596 llHTTPResponse(id, 200, "OK");
597 // This message was most likely not for us.
598 if(wasKeyValueGet("command", body)
599 != "teleport") return;
600 if(wasKeyValueGet("success", body) != "True") {
601 // DEBUG
602 llOwnerSay("Failed teleport, retrying...");
603 state recall_trampoline;
604 }
605  
606 // DEBUG
607 llOwnerSay("Corrade teleported to home region...");
608  
609 // We are back to the home region now.
610 state detect;
611 }
612 on_rez(integer num) {
613 llResetScript();
614 }
615 changed(integer change) {
616 if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
617 llResetScript();
618 }
619 }
620 state_exit() {
621 llSetTimerEvent(0);
622 }
623 }