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 a puppeteer script for the Corrade Second Life / OpenSim bot
6 // that, given a set of local coordinates, will make the bot traverse a
7 // path while also minding collisions with object. You can find more
8 // details about the Corrade bot and how to get it to work on your machine
9 // by following the URL: http://grimore.org/secondlife/scripted_agents/corrade
10 //
11 // This script works together with a "configuration" notecard that must
12 // be placed in the same primitive as this script. The purpose of this
13 // script is to demonstrate how Corrade can be made to walk on a path and
29 office 14 // you are free to use, change, and commercialize it under the CC BY 2.0
15 // 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 list PATH = [];
138 float PAUSE = 0;
139 integer RANDOMIZE = FALSE;
140  
141 // for holding the callback URL
142 string callback = "";
143  
144 // for notecard reading
145 integer line = 0;
146  
147 // key-value data will be read into this list
148 list tuples = [];
149 // stores COrrade's current position
150 vector origin = ZERO_VECTOR;
151  
152 default {
153 state_entry() {
154 if(llGetInventoryType("configuration") != INVENTORY_NOTECARD) {
155 llOwnerSay("Sorry, could not find a configuration inventory notecard.");
156 return;
157 }
158 // DEBUG
159 llOwnerSay("Reading configuration file...");
160 llGetNotecardLine("configuration", line);
161 }
162 dataserver(key id, string data) {
163 if(data == EOF) {
164 // invariant, length(tuples) % 2 == 0
165 if(llGetListLength(tuples) % 2 != 0) {
166 llOwnerSay("Error in configuration notecard.");
167 return;
168 }
169 CORRADE = llList2Key(
170 tuples,
171 llListFindList(
172 tuples,
173 [
174 "corrade"
175 ]
176 )
177 +1);
178 if(CORRADE == NULL_KEY) {
179 llOwnerSay("Error in configuration notecard: corrade");
180 return;
181 }
182 GROUP = llList2String(
183 tuples,
184 llListFindList(
185 tuples,
186 [
187 "group"
188 ]
189 )
190 +1);
191 if(GROUP == "") {
192 llOwnerSay("Error in configuration notecard: group");
193 return;
194 }
195 PASSWORD = llList2String(
196 tuples,
197 llListFindList(
198 tuples,
199 [
200 "password"
201 ]
202 )
203 +1);
204 if(PASSWORD == "") {
205 llOwnerSay("Error in configuration notecard: password");
206 return;
207 }
208 PATH = llCSV2List(
209 llList2String(
210 tuples,
211 llListFindList(
212 tuples,
213 [
214 "path"
215 ]
216 )
217 +1)
218 );
219 if(PATH == []) {
220 llOwnerSay("Error in configuration notecard: points");
221 return;
222 }
223 PAUSE = llList2Float(
224 tuples,
225 llListFindList(
226 tuples,
227 [
228 "pause"
229 ]
230 )
231 +1);
232 if(PAUSE == 0) {
233 llOwnerSay("Error in configuration notecard: pause");
234 return;
235 }
236 string boolean = llList2String(
237 tuples,
238 llListFindList(
239 tuples,
240 [
241 "randomize"
242 ]
243 )
244 +1);
245 if(llToLower(boolean) == "true") RANDOMIZE = TRUE;
246 // DEBUG
247 llOwnerSay("Read configuration notecard...");
248 state url;
249 }
250 if(data == "") jump continue;
251 integer i = llSubStringIndex(data, "#");
252 if(i != -1) data = llDeleteSubString(data, i, -1);
253 list o = llParseString2List(data, ["="], []);
254 // get rid of starting and ending quotes
255 string k = llDumpList2String(
256 llParseString2List(
257 llStringTrim(
258 llList2String(
259 o,
260  
261 ),
262 STRING_TRIM),
263 ["\""], []
264 ), "\"");
265 string v = llDumpList2String(
266 llParseString2List(
267 llStringTrim(
268 llList2String(
269 o,
270 1
271 ),
272 STRING_TRIM),
273 ["\""], []
274 ), "\"");
275 if(k == "" || v == "") jump continue;
276 tuples += k;
277 tuples += v;
278 @continue;
279 llGetNotecardLine("configuration", ++line);
280 }
281 on_rez(integer num) {
282 llResetScript();
283 }
284 changed(integer change) {
285 if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
286 llResetScript();
287 }
288 }
289 }
290  
291 state url {
292 state_entry() {
293 // DEBUG
294 llOwnerSay("Requesting URL...");
295 llRequestURL();
296 }
297 http_request(key id, string method, string body) {
298 if(method != URL_REQUEST_GRANTED) return;
299 callback = body;
300 // DEBUG
301 llOwnerSay("Got URL...");
302 state detect;
303 }
304 on_rez(integer num) {
305 llResetScript();
306 }
307 changed(integer change) {
308 if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
309 llResetScript();
310 }
311 }
312 }
313  
314 state detect {
315 state_entry() {
316 // DEBUG
317 llOwnerSay("Detecting if Corrade is online...");
318 llSetTimerEvent(5);
319 }
320 timer() {
321 llRequestAgentData((key)CORRADE, DATA_ONLINE);
322 }
323 dataserver(key id, string data) {
324 if(data != "1") {
325 // DEBUG
326 llOwnerSay("Corrade is not online, sleeping...");
327 llSetTimerEvent(30);
328 return;
329 }
330 state notify;
331 }
332 on_rez(integer num) {
333 llResetScript();
334 }
335 changed(integer change) {
336 if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
337 llResetScript();
338 }
339 }
340 }
341  
342 state notify {
343 state_entry() {
344 // DEBUG
345 llOwnerSay("Binding to the collision notification...");
346 llInstantMessage(
347 (key)CORRADE,
348 wasKeyValueEncode(
349 [
350 "command", "notify",
351 "group", wasURLEscape(GROUP),
352 "password", wasURLEscape(PASSWORD),
353 "action", "set",
354 "type", "collision",
355 "URL", wasURLEscape(callback),
356 "callback", wasURLEscape(callback)
357 ]
358 )
359 );
360 }
361 http_request(key id, string method, string body) {
362 llHTTPResponse(id, 200, "OK");
363 if(wasKeyValueGet("command", body) != "notify" ||
364 wasKeyValueGet("success", body) != "True") {
365 // DEBUG
366 llOwnerSay("Failed to bind to the collisioin notification...");
367 state detect;
368 }
369 // DEBUG
370 llOwnerSay("Collision notification installed...");
371 state pause;
372 }
373 on_rez(integer num) {
374 llResetScript();
375 }
376 changed(integer change) {
377 if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
378 llResetScript();
379 }
380 }
381 }
382  
383 state pause {
384 state_entry() {
385 //DEBUG
386 llOwnerSay("Pausing...");
387 // Check whether Corrade is still online first.
388 llRequestAgentData((key)CORRADE, DATA_ONLINE);
389 }
390 dataserver(key id, string data) {
391 if(data != "1") {
392 // DEBUG
393 llOwnerSay("Corrade is not online, sleeping...");
394 llSetTimerEvent(30);
395 return;
396 }
397 // Corrade is online, so schedule the next walk.
398 if(RANDOMIZE) {
399 // The minimal trigger time for a timer event is ~0.045s
400 // This ensures we do not end up stuck in the pause state.
401 llSetTimerEvent(0.045 + llFrand(PAUSE - 0.045));
402 return;
403 }
404 llSetTimerEvent(PAUSE);
405 }
406 timer() {
407 llSetTimerEvent(0);
408 state find;
409 }
410 }
411  
412 state find {
413 state_entry() {
414 // We now query Corrade for its current position.
415 llInstantMessage(CORRADE,
416 wasKeyValueEncode(
417 [
418 "command", "getselfdata",
419 "group", wasURLEscape(GROUP),
420 "password", wasURLEscape(PASSWORD),
421 "data", "SimPosition",
422 "callback", wasURLEscape(callback)
423 ]
424 )
425 );
426 // alarm 60 for Corrade not responding
427 llSetTimerEvent(60);
428 }
429 timer() {
430 llSetTimerEvent(0);
431 // DEBUG
432 llOwnerSay("Corrade not responding to data query...");
433 state pause;
434 }
435 http_request(key id, string method, string body) {
436 llHTTPResponse(id, 200, "OK");
437 list data = wasCSVToList(
438 wasKeyValueGet(
439 "data",
440 wasURLUnescape(body)
441 )
442 );
443 origin= (vector)llList2String(
444 data,
445 llListFindList(
446 data,
447 (list)"SimPosition"
448 )+1
449 );
450 state walk;
451 }
452 on_rez(integer num) {
453 llResetScript();
454 }
455 changed(integer change) {
456 if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
457 llResetScript();
458 }
459 }
460 }
461  
462 state walk {
463 state_entry() {
464 // DEBUG
465 llOwnerSay("Walking...");
466  
467 // extract next destination and permute the set
468 vector next = (vector)llList2String(PATH, 0);
469 PATH = llDeleteSubList(PATH, 0, 0);
470 PATH += next;
471  
472 llInstantMessage(CORRADE,
473 wasKeyValueEncode(
474 [
475 "command", "autopilot",
476 "group", wasURLEscape(GROUP),
477 "password", wasURLEscape(PASSWORD),
478 "position", next,
479 "action", "start"
480 ]
481 )
482 );
483 // We now determine the waiting time for Corrade to reach
484 // its next destination by extracting time as a function
485 // of the distance it has to walk and the speed of travel:
486 // t = s / v
487 // This, of course, is prone to error since the distance
488 // is calculated on the shortest direct path. Nevertheless,
489 // it is a pretty good appoximation for terrain that is
490 // mostly flat and without too many curvatures.
491 // NB. 3.20 m/s is the walking speed of an avatar.
492 llSetTimerEvent(llVecDist(origin, next)/3.20);
493 }
494 http_request(key id, string method, string body) {
495 // since we have bound to the collision notification,
496 // this region of code deals with Corrade colliding
497 // with in-world assets; in which case, we stop
498 // moving to not seem awkward
499  
500 // DEBUG
501 llOwnerSay("Collided...");
502  
503 llHTTPResponse(id, 200, "OK");
504  
505 llInstantMessage(CORRADE,
506 wasKeyValueEncode(
507 [
508 "command", "autopilot",
509 "group", wasURLEscape(GROUP),
510 "password", wasURLEscape(PASSWORD),
511 "action", "stop"
512 ]
513 )
514 );
515 // We did not reach our destination since we collided with
516 // something on our path, so switch directly to waiting and
517 // attempt to reach the next destination on our path.
518 state pause;
519 }
520 timer() {
521 // We most likely reached our destination, so switch to pause.
522 llSetTimerEvent(0);
523 state pause;
524 }
525 on_rez(integer num) {
526 llResetScript();
527 }
528 changed(integer change) {
529 if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
530 llResetScript();
531 }
532 }
533 }
534