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 an automatic teleporter, and patrol script for the Corrade
6 // Second Life / OpenSim bot. You can find more details about the bot
7 // by following the URL: http://was.fm/secondlife/scripted_agents/corrade
8 //
9 // The purpose of this script is to demonstrate patroling with Corrade and
37 office 10 // you are free to use, change, and commercialize it under the GNU/GPLv3
11 // license which can be found at: http://www.gnu.org/licenses/gpl.html
4 office 12 //
13 ///////////////////////////////////////////////////////////////////////////
14  
15 ///////////////////////////////////////////////////////////////////////////
37 office 16 // Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 //
4 office 17 ///////////////////////////////////////////////////////////////////////////
18 string wasKeyValueGet(string k, string data) {
19 if(llStringLength(data) == 0) return "";
20 if(llStringLength(k) == 0) return "";
21 list a = llParseString2List(data, ["&", "="], []);
22 integer i = llListFindList(a, [ k ]);
23 if(i != -1) return llList2String(a, i+1);
24 return "";
25 }
26  
27 ///////////////////////////////////////////////////////////////////////////
37 office 28 // Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 //
4 office 29 ///////////////////////////////////////////////////////////////////////////
30 string wasKeyValueEncode(list data) {
31 list k = llList2ListStrided(data, 0, -1, 2);
32 list v = llList2ListStrided(llDeleteSubList(data, 0, 0), 0, -1, 2);
33 data = [];
34 do {
35 data += llList2String(k, 0) + "=" + llList2String(v, 0);
36 k = llDeleteSubList(k, 0, 0);
37 v = llDeleteSubList(v, 0, 0);
38 } while(llGetListLength(k) != 0);
39 return llDumpList2String(data, "&");
40 }
41  
42 ///////////////////////////////////////////////////////////////////////////
37 office 43 // Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 //
4 office 44 ///////////////////////////////////////////////////////////////////////////
45 list wasDualQuicksort(list a, list b) {
46 if(llGetListLength(a) <= 1) return a+b;
47  
48 float pivot_a = llList2Float(a, 0);
49 a = llDeleteSubList(a, 0, 0);
50 vector pivot_b = llList2Vector(b, 0);
51 b = llDeleteSubList(b, 0, 0);
52  
53 list less = [];
54 list less_b = [];
55 list more = [];
56 list more_b = [];
57  
58 do {
59 if(llList2Float(a, 0) > pivot_a) {
60 less += llList2List(a, 0, 0);
61 less_b += llList2List(b, 0, 0);
62 jump continue;
63 }
64 more += llList2List(a, 0, 0);
65 more_b += llList2List(b, 0, 0);
66 @continue;
67 a = llDeleteSubList(a, 0, 0);
68 b = llDeleteSubList(b, 0, 0);
69 } while(llGetListLength(a));
70 return wasDualQuicksort(less, less_b) + [ pivot_a ] + [ pivot_b ] + wasDualQuicksort(more, more_b);
71 }
72  
73 ///////////////////////////////////////////////////////////////////////////
37 office 74 // Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 //
4 office 75 ///////////////////////////////////////////////////////////////////////////
76 // determines whether the segment AB intersects the segment CD
77 integer wasSegmentIntersect(vector A, vector B, vector C, vector D) {
78 vector s1 = <B.x - A.x, B.y - A.y, B.z - A.z>;
79 vector s2 = <D.x - C.x, D.y - C.y, D.y - C.z>;
80  
81 float d = (s1.x * s2.y -s2.x * s1.y);
82  
83 if(d == 0) return FALSE;
84  
85 float s = (s1.x * (A.y - C.y) - s1.y * (A.x - C.x)) / d;
86 float t = (s2.x * (A.y - C.y) - s2.y * (A.x - C.x)) / d;
87  
88 // intersection at <A.x + (t * s1.x), A.y + (t * s1.y), A.z + (t * s1.z)>;
89 return (integer)(s >= 0 && s <= 1 && t >= 0 && t <= 1 &&
90 A.z + t*(B.z - A.z) == C.z + s*(D.z - C.z));
91 }
92  
93 ///////////////////////////////////////////////////////////////////////////
37 office 94 // Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 //
4 office 95 // www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html //
96 ///////////////////////////////////////////////////////////////////////////
97 integer wasPointInPolygon(vector p, list polygon) {
98 integer inside = FALSE;
99 integer i = 0;
100 integer nvert = llGetListLength(polygon);
101 integer j = nvert-1;
102 do {
103 vector pi = llList2Vector(polygon, i);
104 vector pj = llList2Vector(polygon, j);
105 if ((pi.y > p.y) != (pj.y > p.y))
106 if(p.x < (pj.x - pi.x) * (p.y - pi.y) / (pj.y - pi.y) + pi.x)
107 inside = !inside;
108 j = i++;
109 } while(i<nvert);
110 return inside;
111 }
112  
113 ///////////////////////////////////////////////////////////////////////////
37 office 114 // Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 //
4 office 115 // www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html //
116 ///////////////////////////////////////////////////////////////////////////
117 list wasPointToPolygon(list polygon, vector point) {
118 integer i = llGetListLength(polygon)-1;
119 list l = [];
120 do {
121 l = llListInsertList(l, (list)llVecDist(point, llList2Vector(polygon, i)), 0);
122 } while(--i>-1);
123 l = wasDualQuicksort(l, polygon);
124 return [llList2Float(l, 0), llList2Vector(l, 1)];
125  
126 }
127  
128 ///////////////////////////////////////////////////////////////////////////
37 office 129 // Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 //
4 office 130 // www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html //
131 ///////////////////////////////////////////////////////////////////////////
132 vector wasPolygonCentroid(list polygon, vector start, float tollerance, integer power) {
133 // calculate the distance to the point farthest away from the start.
134 list wpf = wasPointToPolygon(polygon, start);
135 float dist = llList2Float(wpf, 0);
136 vector next = llList2Vector(wpf, 1);
137  
138 // now calculate the next jump point
139 next = start + ((dist/power)/dist) * (next-start);
140  
141 // if it falls withing the tollerance range, return it;
142 if(llVecMag(start-next) < tollerance) return next;
143 return wasPolygonCentroid(polygon, next, tollerance, power*power);
144 }
145  
146 ///////////////////////////////////////////////////////////////////////////
37 office 147 // Copyright (C) 2011 Wizardry and Steamworks - License: GNU GPLv3 //
4 office 148 ///////////////////////////////////////////////////////////////////////////
149 vector wasCirclePoint(float radius) {
150 float x = llPow(-1, 1 + (integer) llFrand(2)) * llFrand(radius*2);
151 float y = llPow(-1, 1 + (integer) llFrand(2)) * llFrand(radius*2);
152 if(llPow(x,2) + llPow(y,2) <= llPow(radius,2))
153 return <x, y, 0>;
154 return wasCirclePoint(radius);
155 }
156  
157 ///////////////////////////////////////////////////////////////////////////
37 office 158 // Copyright (C) 2011 Wizardry and Steamworks - License: GNU GPLv3 //
4 office 159 ///////////////////////////////////////////////////////////////////////////
160 vector wasPolygonPoint(list polygon) {
161 vector c = wasPolygonCentroid(polygon, llList2Vector(polygon, 0), 0.05, 2);
162 float r = llList2Float(wasPointToPolygon(polygon, c), 0);
163 vector d;
164 do {
165 d = c + wasCirclePoint(r);
166 } while(wasPointInPolygon(d, polygon) == FALSE);
167 return d;
168 }
169  
170 ///////////////////////////////////////////////////////////////////////////
37 office 171 // Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 //
4 office 172 ///////////////////////////////////////////////////////////////////////////
173 // determines whether the path between the current positon m and the
174 // computed next position d will intersect any two sides of the polygon
175 vector wasPolygonPath(vector m, list polygon) {
176 integer c = llGetListLength(polygon) - 1;
177 vector d = wasPolygonPoint(polygon);
178 integer i = 0;
179 do {
180 vector s = llList2Vector(polygon, c);
181 vector p = llList2Vector(polygon, c-1);
182 // project in plane
183 if(wasSegmentIntersect(
184 <m.x, m.y, 0>,
185 <d.x, d.y, 0>,
186 <s.x, s.y, 0>,
187 <p.x, p.y, 0>))
188 ++i;
189 } while(--c > 0);
190 if(i > 1) return wasPolygonPath(m, polygon);
191 return d;
192 }
193  
194 ///////////////////////////////////////////////////////////////////////////
37 office 195 // Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 //
4 office 196 ///////////////////////////////////////////////////////////////////////////
197 // escapes a string in conformance with RFC1738
198 string wasURLEscape(string i) {
199 string o = "";
200 do {
201 string c = llGetSubString(i, 0, 0);
202 i = llDeleteSubString(i, 0, 0);
203 if(c == "") jump continue;
204 if(c == " ") {
205 o += "+";
206 jump continue;
207 }
208 if(c == "\n") {
209 o += "%0D" + llEscapeURL(c);
210 jump continue;
211 }
212 o += llEscapeURL(c);
213 @continue;
214 } while(i != "");
215 return o;
216 }
217  
218 // corrade data
219 string CORRADE = "";
220 string GROUP = "";
221 string PASSWORD = "";
222 float RADIUS = 0;
223 float WAIT = 0;
224 list POLYGON = [];
225  
226 // for holding Corrade's current location
227 vector location = ZERO_VECTOR;
228  
229 // for holding the callback URL
230 string callback = "";
231  
232 // for notecard reading
233 integer line = 0;
234  
235 // key-value data will be read into this list
236 list tuples = [];
237  
238 default {
239 state_entry() {
240 if(llGetInventoryType("configuration") != INVENTORY_NOTECARD) {
241 llOwnerSay("Sorry, could not find an inventory notecard.");
242 return;
243 }
244 // DEBUG
245 llOwnerSay("Reading configuration file...");
246 llGetNotecardLine("configuration", line);
247 }
248 dataserver(key id, string data) {
249 if(data == EOF) {
250 // invariant, length(tuples) % 2 == 0
251 if(llGetListLength(tuples) % 2 != 0) {
252 llOwnerSay("Error in configuration notecard.");
253 return;
254 }
255 CORRADE = llList2String(
256 tuples,
257 llListFindList(
258 tuples,
259 [
260 "corrade"
261 ]
262 )
263 +1);
264 if(CORRADE == "") {
265 llOwnerSay("Error in configuration notecard: corrade");
266 return;
267 }
268 GROUP = llList2String(
269 tuples,
270 llListFindList(
271 tuples,
272 [
273 "group"
274 ]
275 )
276 +1);
277 if(GROUP == "") {
278 llOwnerSay("Error in configuration notecard: group");
279 return;
280 }
281 PASSWORD = llList2String(
282 tuples,
283 llListFindList(
284 tuples,
285 [
286 "password"
287 ]
288 )
289 +1);
290 if(PASSWORD == "") {
291 llOwnerSay("Error in configuration notecard: password");
292 return;
293 }
294  
295 // BEGIN POLYGON
296 integer i = llGetListLength(tuples)-1;
297 do {
298 string n = llList2String(tuples, i);
299 if(llSubStringIndex(n, "point_") != -1) {
300 list l = llParseString2List(n, ["_"], []);
301 if(llList2String(l, 0) == "point") {
302 integer x = llList2Integer(
303 l,
304 1
305 )-1;
306 // extend the polygon to the number of points
307 while(llGetListLength(POLYGON) < x)
308 POLYGON += "";
309 // and insert the point at the location
310 POLYGON = llListReplaceList(
311 POLYGON,
312 (list)(
313 (vector)(
314 "<" + llList2CSV(
315 llParseString2List(
316 llList2String(
317 tuples,
318 llListFindList(
319 tuples,
320 (list)n
321 )
322 +1
323 ),
324 ["<", ",", ">"],
325 []
326 )
327 ) + ">")
328 ),
329 x,
330 x
331 );
332 }
333 }
334 } while(--i>-1);
335 // now clean up any empty slots
336 i = llGetListLength(POLYGON)-1;
337 do {
338 if(llList2String(POLYGON, i) == "")
339 POLYGON = llDeleteSubList(POLYGON, i, i);
340 } while(--i > -1);
341 // END POLYGON
342  
343 WAIT = llList2Float(
344 tuples,
345 llListFindList(
346 tuples,
347 [
348 "wait"
349 ]
350 )
351 +1);
352 if(WAIT == 0) {
353 llOwnerSay("Error in configuration notecard: wait");
354 return;
355 }
356 // DEBUG
357 llOwnerSay("Read configuration file...");
358 state url;
359 }
360 if(data == "") jump continue;
361 integer i = llSubStringIndex(data, "#");
362 if(i != -1) data = llDeleteSubString(data, i, -1);
363 list o = llParseString2List(data, ["="], []);
364 // get rid of starting and ending quotes
365 string k = llDumpList2String(
366 llParseString2List(
367 llStringTrim(
368 llList2String(
369 o,
370  
371 ),
372 STRING_TRIM),
373 ["\""], []
374 ), "\"");
375 string v = llDumpList2String(
376 llParseString2List(
377 llStringTrim(
378 llList2String(
379 o,
380 1
381 ),
382 STRING_TRIM),
383 ["\""], []
384 ), "\"");
385 if(k == "" || v == "") jump continue;
386 tuples += k;
387 tuples += v;
388 @continue;
389 llGetNotecardLine("configuration", ++line);
390 }
391 on_rez(integer num) {
392 llResetScript();
393 }
394 changed(integer change) {
395 if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
396 llResetScript();
397 }
398 }
399 }
400  
401 state url {
402 state_entry() {
403 // DEBUG
404 llOwnerSay("Requesting URL...");
405 llRequestURL();
406 }
407 http_request(key id, string method, string body) {
408 if(method != URL_REQUEST_GRANTED) return;
409 callback = body;
410 // DEBUG
411 llOwnerSay("Got URL...");
412 state detect;
413 }
414 on_rez(integer num) {
415 llResetScript();
416 }
417 changed(integer change) {
418 if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
419 llResetScript();
420 }
421 }
422 }
423  
424 state detect {
425 state_entry() {
426 // DEBUG
427 llOwnerSay("Detecting if Corrade is online...");
428 llSetTimerEvent(5);
429 }
430 timer() {
431 llRequestAgentData((key)CORRADE, DATA_ONLINE);
432 }
433 dataserver(key id, string data) {
434 if(data != "1") {
435 // DEBUG
436 llOwnerSay("Corrade is not online, sleeping...");
437 llSetTimerEvent(30);
438 return;
439 }
440 llSensor("", (key)CORRADE, AGENT, 10, TWO_PI);
441 }
442 no_sensor() {
443 // DEBUG
444 llOwnerSay("Teleporting Corrade...");
445 llInstantMessage((key)CORRADE,
446 wasKeyValueEncode(
447 [
448 "command", "teleport",
449 "group", wasURLEscape(GROUP),
450 "password", wasURLEscape(PASSWORD),
451 "entity", "region",
452 "region", wasURLEscape(llGetRegionName()),
453 "position", wasURLEscape(
454 (string)(
455 llGetPos() + wasCirclePoint(RADIUS)
456 )
457 ),
458 "callback", callback
459 ]
460 )
461 );
462 llSensorRepeat("", (key)CORRADE, AGENT, 10, TWO_PI, 60);
463 }
464 sensor(integer num) {
465 llSetTimerEvent(0);
466 state wander;
467 }
468 http_request(key id, string method, string body) {
469 llHTTPResponse(id, 200, "OK");
470 if(wasKeyValueGet("command", body) != "teleport" ||
471 wasKeyValueGet("success", body) != "True") {
472 // DEBUG
473 llOwnerSay("Teleport failed...");
474 return;
475 }
476 llSetTimerEvent(0);
477 state wander;
478 }
479 on_rez(integer num) {
480 llResetScript();
481 }
482 changed(integer change) {
483 if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
484 llResetScript();
485 }
486 }
487 }
488  
489 state wander {
490 state_entry() {
491 // DEBUG
492 llOwnerSay("Wandering ready...");
493 // initialize location from current location
494 location = llGetPos();
495 llSetTimerEvent(1 + llFrand(WAIT));
496 }
497 timer() {
498 llRequestAgentData((key)CORRADE, DATA_ONLINE);
499 }
500 dataserver(key id, string data) {
501 if(data != "1") {
502 // DEBUG
503 llOwnerSay("Corrade is not online, sleeping...");
504 llResetScript();
505 return;
506 }
507 // DEBUG
508 llOwnerSay("Sending next move...");
509 // get the next location
510 location = wasPolygonPath(location, POLYGON);
511 vector pos = llGetPos();
512 llInstantMessage(CORRADE,
513 wasKeyValueEncode(
514 [
37 office 515 "command", "walkto",
4 office 516 "group", wasURLEscape(GROUP),
517 "password", wasURLEscape(PASSWORD),
518 "position", wasURLEscape(
519 (string)(<location.x, location.y, pos.z>)
520 ),
37 office 521 "vicinity", "1",
522 "duration", "2500"
4 office 523 ]
524 )
525 );
526 llSetTimerEvent(1 + llFrand(WAIT));
527 }
528 on_rez(integer num) {
529 llResetScript();
530 }
531 changed(integer change) {
532 if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
533 llResetScript();
534 }
535 }
536 }