corrade-lsl-templates – Blame information for rev 37

Subversion Repositories:
Rev:
Rev Author Line No. Line
4 office 1 ///////////////////////////////////////////////////////////////////////////
37 office 2 // Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3 //
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
37 office 9 // by following the URL: http://was.fm/secondlife/scripted_agents/corrade
4 office 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
37 office 14 // you are free to use, change, and commercialize it under the GNU/GPLv3
15 // license at: http://www.gnu.org/licenses/gpl.html
4 office 16 //
17 ///////////////////////////////////////////////////////////////////////////
18  
19 ///////////////////////////////////////////////////////////////////////////
37 office 20 // Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 //
4 office 21 ///////////////////////////////////////////////////////////////////////////
37 office 22 string wasKeyValueGet(string var, string kvp) {
23 list dVars = llParseString2List(kvp, ["&"], []);
24 do {
25 list data = llParseString2List(llList2String(dVars, 0), ["="], []);
26 string k = llList2String(data, 0);
27 if(k != var) jump continue;
28 return llList2String(data, 1);
29 @continue;
30 dVars = llDeleteSubList(dVars, 0, 0);
31 } while(llGetListLength(dVars));
4 office 32 return "";
33 }
37 office 34  
4 office 35 ///////////////////////////////////////////////////////////////////////////
37 office 36 // Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 //
4 office 37 ///////////////////////////////////////////////////////////////////////////
37 office 38 string wasKeyValueEncode(list kvp) {
39 if(llGetListLength(kvp) < 2) return "";
40 string k = llList2String(kvp, 0);
41 kvp = llDeleteSubList(kvp, 0, 0);
42 string v = llList2String(kvp, 0);
43 kvp = llDeleteSubList(kvp, 0, 0);
44 if(llGetListLength(kvp) < 2) return k + "=" + v;
45 return k + "=" + v + "&" + wasKeyValueEncode(kvp);
4 office 46 }
47  
37 office 48  
4 office 49 ///////////////////////////////////////////////////////////////////////////
37 office 50 // Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 //
4 office 51 ///////////////////////////////////////////////////////////////////////////
52 // escapes a string in conformance with RFC1738
53 string wasURLEscape(string i) {
54 string o = "";
55 do {
56 string c = llGetSubString(i, 0, 0);
57 i = llDeleteSubString(i, 0, 0);
58 if(c == "") jump continue;
59 if(c == " ") {
60 o += "+";
61 jump continue;
62 }
63 if(c == "\n") {
64 o += "%0D" + llEscapeURL(c);
65 jump continue;
66 }
67 o += llEscapeURL(c);
68 @continue;
69 } while(i != "");
70 return o;
71 }
72  
73 ///////////////////////////////////////////////////////////////////////////
37 office 74 // Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 //
4 office 75 ///////////////////////////////////////////////////////////////////////////
76 // unescapes a string in conformance with RFC1738
77 string wasURLUnescape(string i) {
78 return llUnescapeURL(
79 llDumpList2String(
80 llParseString2List(
81 llDumpList2String(
82 llParseString2List(
83 i,
84 ["+"],
85 []
86 ),
87 " "
88 ),
89 ["%0D%0A"],
90 []
91 ),
92 "\n"
93 )
94 );
95 }
96 ///////////////////////////////////////////////////////////////////////////
37 office 97 // Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 //
4 office 98 ///////////////////////////////////////////////////////////////////////////
99 list wasCSVToList(string csv) {
100 list l = [];
101 list s = [];
102 string m = "";
103 do {
104 string a = llGetSubString(csv, 0, 0);
105 csv = llDeleteSubString(csv, 0, 0);
106 if(a == ",") {
107 if(llList2String(s, -1) != "\"") {
108 l += m;
109 m = "";
110 jump continue;
111 }
112 m += a;
113 jump continue;
114 }
115 if(a == "\"" && llGetSubString(csv, 0, 0) == a) {
116 m += a;
117 csv = llDeleteSubString(csv, 0, 0);
118 jump continue;
119 }
120 if(a == "\"") {
121 if(llList2String(s, -1) != a) {
122 s += a;
123 jump continue;
124 }
125 s = llDeleteSubList(s, -1, -1);
126 jump continue;
127 }
128 m += a;
129 @continue;
130 } while(csv != "");
131 // postcondition: length(s) = 0
132 return l + m;
133 }
134  
135 // corrade data
136 key CORRADE = NULL_KEY;
137 string GROUP = "";
138 string PASSWORD = "";
139 list PATH = [];
140 float PAUSE = 0;
141 integer RANDOMIZE = FALSE;
142  
143 // for holding the callback URL
144 string callback = "";
145  
146 // for notecard reading
147 integer line = 0;
148  
149 // key-value data will be read into this list
150 list tuples = [];
151 // stores COrrade's current position
152 vector origin = ZERO_VECTOR;
153  
154 default {
155 state_entry() {
156 if(llGetInventoryType("configuration") != INVENTORY_NOTECARD) {
157 llOwnerSay("Sorry, could not find a configuration inventory notecard.");
158 return;
159 }
160 // DEBUG
161 llOwnerSay("Reading configuration file...");
162 llGetNotecardLine("configuration", line);
163 }
164 dataserver(key id, string data) {
165 if(data == EOF) {
166 // invariant, length(tuples) % 2 == 0
167 if(llGetListLength(tuples) % 2 != 0) {
168 llOwnerSay("Error in configuration notecard.");
169 return;
170 }
171 CORRADE = llList2Key(
172 tuples,
173 llListFindList(
174 tuples,
175 [
176 "corrade"
177 ]
178 )
179 +1);
180 if(CORRADE == NULL_KEY) {
181 llOwnerSay("Error in configuration notecard: corrade");
182 return;
183 }
184 GROUP = llList2String(
185 tuples,
186 llListFindList(
187 tuples,
188 [
189 "group"
190 ]
191 )
192 +1);
193 if(GROUP == "") {
194 llOwnerSay("Error in configuration notecard: group");
195 return;
196 }
197 PASSWORD = llList2String(
198 tuples,
199 llListFindList(
200 tuples,
201 [
202 "password"
203 ]
204 )
205 +1);
206 if(PASSWORD == "") {
207 llOwnerSay("Error in configuration notecard: password");
208 return;
209 }
210 PATH = llCSV2List(
211 llList2String(
212 tuples,
213 llListFindList(
214 tuples,
215 [
216 "path"
217 ]
218 )
219 +1)
220 );
221 if(PATH == []) {
222 llOwnerSay("Error in configuration notecard: points");
223 return;
224 }
225 PAUSE = llList2Float(
226 tuples,
227 llListFindList(
228 tuples,
229 [
230 "pause"
231 ]
232 )
233 +1);
234 if(PAUSE == 0) {
235 llOwnerSay("Error in configuration notecard: pause");
236 return;
237 }
238 string boolean = llList2String(
239 tuples,
240 llListFindList(
241 tuples,
242 [
243 "randomize"
244 ]
245 )
246 +1);
247 if(llToLower(boolean) == "true") RANDOMIZE = TRUE;
248 // DEBUG
249 llOwnerSay("Read configuration notecard...");
250 state url;
251 }
252 if(data == "") jump continue;
253 integer i = llSubStringIndex(data, "#");
254 if(i != -1) data = llDeleteSubString(data, i, -1);
255 list o = llParseString2List(data, ["="], []);
256 // get rid of starting and ending quotes
257 string k = llDumpList2String(
258 llParseString2List(
259 llStringTrim(
260 llList2String(
261 o,
262  
263 ),
264 STRING_TRIM),
265 ["\""], []
266 ), "\"");
267 string v = llDumpList2String(
268 llParseString2List(
269 llStringTrim(
270 llList2String(
271 o,
272 1
273 ),
274 STRING_TRIM),
275 ["\""], []
276 ), "\"");
277 if(k == "" || v == "") jump continue;
278 tuples += k;
279 tuples += v;
280 @continue;
281 llGetNotecardLine("configuration", ++line);
282 }
283 on_rez(integer num) {
284 llResetScript();
285 }
286 changed(integer change) {
287 if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
288 llResetScript();
289 }
290 }
291 }
292  
293 state url {
294 state_entry() {
295 // DEBUG
296 llOwnerSay("Requesting URL...");
297 llRequestURL();
298 }
299 http_request(key id, string method, string body) {
300 if(method != URL_REQUEST_GRANTED) return;
301 callback = body;
302 // DEBUG
303 llOwnerSay("Got URL...");
304 state detect;
305 }
306 on_rez(integer num) {
307 llResetScript();
308 }
309 changed(integer change) {
310 if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
311 llResetScript();
312 }
313 }
314 }
315  
316 state detect {
317 state_entry() {
318 // DEBUG
319 llOwnerSay("Detecting if Corrade is online...");
320 llSetTimerEvent(5);
321 }
322 timer() {
323 llRequestAgentData((key)CORRADE, DATA_ONLINE);
324 }
325 dataserver(key id, string data) {
326 if(data != "1") {
327 // DEBUG
328 llOwnerSay("Corrade is not online, sleeping...");
329 llSetTimerEvent(30);
330 return;
331 }
332 state notify;
333 }
334 on_rez(integer num) {
335 llResetScript();
336 }
337 changed(integer change) {
338 if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
339 llResetScript();
340 }
341 }
342 }
343  
344 state notify {
345 state_entry() {
346 // DEBUG
347 llOwnerSay("Binding to the collision notification...");
348 llInstantMessage(
37 office 349 CORRADE,
4 office 350 wasKeyValueEncode(
351 [
352 "command", "notify",
353 "group", wasURLEscape(GROUP),
354 "password", wasURLEscape(PASSWORD),
355 "action", "set",
356 "type", "collision",
357 "URL", wasURLEscape(callback),
358 "callback", wasURLEscape(callback)
359 ]
360 )
361 );
362 }
363 http_request(key id, string method, string body) {
364 llHTTPResponse(id, 200, "OK");
365 if(wasKeyValueGet("command", body) != "notify" ||
366 wasKeyValueGet("success", body) != "True") {
367 // DEBUG
368 llOwnerSay("Failed to bind to the collisioin notification...");
369 state detect;
370 }
371 // DEBUG
372 llOwnerSay("Collision notification installed...");
373 state pause;
374 }
375 on_rez(integer num) {
376 llResetScript();
377 }
378 changed(integer change) {
379 if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
380 llResetScript();
381 }
382 }
383 }
384  
385 state pause {
386 state_entry() {
387 //DEBUG
388 llOwnerSay("Pausing...");
389 // Check whether Corrade is still online first.
390 llRequestAgentData((key)CORRADE, DATA_ONLINE);
391 }
392 dataserver(key id, string data) {
393 if(data != "1") {
394 // DEBUG
395 llOwnerSay("Corrade is not online, sleeping...");
396 llSetTimerEvent(30);
397 return;
398 }
399 // Corrade is online, so schedule the next walk.
400 if(RANDOMIZE) {
401 // The minimal trigger time for a timer event is ~0.045s
402 // This ensures we do not end up stuck in the pause state.
403 llSetTimerEvent(0.045 + llFrand(PAUSE - 0.045));
404 return;
405 }
406 llSetTimerEvent(PAUSE);
407 }
408 timer() {
409 llSetTimerEvent(0);
410 state find;
411 }
412 }
413  
414 state find {
415 state_entry() {
416 // We now query Corrade for its current position.
417 llInstantMessage(CORRADE,
418 wasKeyValueEncode(
419 [
420 "command", "getselfdata",
421 "group", wasURLEscape(GROUP),
422 "password", wasURLEscape(PASSWORD),
423 "data", "SimPosition",
424 "callback", wasURLEscape(callback)
425 ]
426 )
427 );
428 // alarm 60 for Corrade not responding
429 llSetTimerEvent(60);
430 }
431 timer() {
432 llSetTimerEvent(0);
433 // DEBUG
434 llOwnerSay("Corrade not responding to data query...");
435 state pause;
436 }
437 http_request(key id, string method, string body) {
438 llHTTPResponse(id, 200, "OK");
439 list data = wasCSVToList(
440 wasKeyValueGet(
441 "data",
442 wasURLUnescape(body)
443 )
444 );
445 origin= (vector)llList2String(
446 data,
447 llListFindList(
448 data,
449 (list)"SimPosition"
450 )+1
451 );
452 state walk;
453 }
454 on_rez(integer num) {
455 llResetScript();
456 }
457 changed(integer change) {
458 if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
459 llResetScript();
460 }
461 }
462 }
463  
464 state walk {
465 state_entry() {
466 // DEBUG
467 llOwnerSay("Walking...");
468  
469 // extract next destination and permute the set
470 vector next = (vector)llList2String(PATH, 0);
471 PATH = llDeleteSubList(PATH, 0, 0);
472 PATH += next;
473  
37 office 474 // We now determine the waiting time for Corrade to reach
475 // its next destination by extracting time as a function
476 // of the distance it has to walk and the speed of travel:
477 // t = s / v
478 // This, of course, is prone to error since the distance
479 // is calculated on the shortest direct path. Nevertheless,
480 // it is a pretty good appoximation for terrain that is
481 // mostly flat and without too many curvatures.
482 // NB. 3.20 m/s is the walking speed of an avatar.
4 office 483 llInstantMessage(CORRADE,
484 wasKeyValueEncode(
485 [
37 office 486 "command", "walkto",
4 office 487 "group", wasURLEscape(GROUP),
488 "password", wasURLEscape(PASSWORD),
489 "position", next,
37 office 490 "vicinity", "1",
491 "timeout", llVecDist(origin, next)/3.20
4 office 492 ]
493 )
494 );
37 office 495  
4 office 496 llSetTimerEvent(llVecDist(origin, next)/3.20);
497 }
498 http_request(key id, string method, string body) {
499 // since we have bound to the collision notification,
500 // this region of code deals with Corrade colliding
501 // with in-world assets; in which case, we stop
502 // moving to not seem awkward
503  
504 // DEBUG
505 llOwnerSay("Collided...");
506  
507 llHTTPResponse(id, 200, "OK");
37 office 508  
4 office 509 // We did not reach our destination since we collided with
510 // something on our path, so switch directly to waiting and
511 // attempt to reach the next destination on our path.
512 state pause;
513 }
514 timer() {
515 // We most likely reached our destination, so switch to pause.
516 llSetTimerEvent(0);
517 state pause;
518 }
519 on_rez(integer num) {
520 llResetScript();
521 }
522 changed(integer change) {
523 if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
524 llResetScript();
525 }
526 }
527 }
528