corrade-lsl-templates – Blame information for rev 37
?pathlinks?
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 | } |