corrade-lsl-templates – Blame information for rev 4

Subversion Repositories:
Rev:
Rev Author Line No. Line
4 office 1 ///////////////////////////////////////////////////////////////////////////
2 // Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3 //
3 ///////////////////////////////////////////////////////////////////////////
4 //
5 // This is a script that uses Corrade to change the texture on the
6 // available faces of the primitie. It requires that you have a Corrade bot
7 // with the following permissions enabled for the group specified in the
8 // configuration notecard inside this primitive:
9 //
10 // config -> groups -> group [your group] -> permissions -> interact
11 // config -> groups -> group [your group] -> permissions -> friendship
12 //
13 // Additionally, you should be a friend of your bot and have granted modify
14 // permissions to the bot by using the viewer interface:
15 //
16 // Contact -> Friends -> [ Bot ] -> Friend can edit, delete, take objects.
17 //
18 // The sit script works together with a "configuration" notecard that must
19 // be placed in the same primitive as this script. The purpose of this
20 // script is to demonstrate changing textures with Corrade and you are free
21 // to use, change, and commercialize it under the GNU/GPLv3 license at:
22 // http://www.gnu.org/licenses/gpl.html
23 //
24 ///////////////////////////////////////////////////////////////////////////
25  
26 ///////////////////////////////////////////////////////////////////////////
27 // Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 //
28 ///////////////////////////////////////////////////////////////////////////
29 string wasKeyValueGet(string k, string data) {
30 if(llStringLength(data) == 0) return "";
31 if(llStringLength(k) == 0) return "";
32 list a = llParseString2List(data, ["&", "="], []);
33 integer i = llListFindList(llList2ListStrided(a, 0, -1, 2), [ k ]);
34 if(i != -1) return llList2String(a, 2*i+1);
35 return "";
36 }
37  
38 ///////////////////////////////////////////////////////////////////////////
39 // Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 //
40 ///////////////////////////////////////////////////////////////////////////
41 string wasKeyValueEncode(list data) {
42 list k = llList2ListStrided(data, 0, -1, 2);
43 list v = llList2ListStrided(llDeleteSubList(data, 0, 0), 0, -1, 2);
44 data = [];
45 do {
46 data += llList2String(k, 0) + "=" + llList2String(v, 0);
47 k = llDeleteSubList(k, 0, 0);
48 v = llDeleteSubList(v, 0, 0);
49 } while(llGetListLength(k) != 0);
50 return llDumpList2String(data, "&");
51 }
52  
53 ///////////////////////////////////////////////////////////////////////////
54 // Copyright (C) 2011 Wizardry and Steamworks - License: GNU GPLv3 //
55 ///////////////////////////////////////////////////////////////////////////
56 vector wasCirclePoint(float radius) {
57 float x = llPow(-1, 1 + (integer) llFrand(2)) * llFrand(radius*2);
58 float y = llPow(-1, 1 + (integer) llFrand(2)) * llFrand(radius*2);
59 if(llPow(x,2) + llPow(y,2) <= llPow(radius,2))
60 return <x, y, 0>;
61 return wasCirclePoint(radius);
62 }
63  
64 ///////////////////////////////////////////////////////////////////////////
65 // Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 //
66 ///////////////////////////////////////////////////////////////////////////
67 // escapes a string in conformance with RFC1738
68 string wasURLEscape(string i) {
69 string o = "";
70 do {
71 string c = llGetSubString(i, 0, 0);
72 i = llDeleteSubString(i, 0, 0);
73 if(c == "") jump continue;
74 if(c == " ") {
75 o += "+";
76 jump continue;
77 }
78 if(c == "\n") {
79 o += "%0D" + llEscapeURL(c);
80 jump continue;
81 }
82 o += llEscapeURL(c);
83 @continue;
84 } while(i != "");
85 return o;
86 }
87  
88 ///////////////////////////////////////////////////////////////////////////
89 // Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 //
90 ///////////////////////////////////////////////////////////////////////////
91 // unescapes a string in conformance with RFC1738
92 string wasURLUnescape(string i) {
93 return llUnescapeURL(
94 llDumpList2String(
95 llParseString2List(
96 llDumpList2String(
97 llParseString2List(
98 i,
99 ["+"],
100 []
101 ),
102 " "
103 ),
104 ["%0D%0A"],
105 []
106 ),
107 "\n"
108 )
109 );
110 }
111  
112 ///////////////////////////////////////////////////////////////////////////
113 // Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 //
114 ///////////////////////////////////////////////////////////////////////////
115 list wasCSVToList(string csv) {
116 list l = [];
117 list s = [];
118 string m = "";
119 do {
120 string a = llGetSubString(csv, 0, 0);
121 csv = llDeleteSubString(csv, 0, 0);
122 if(a == ",") {
123 if(llList2String(s, -1) != "\"") {
124 l += m;
125 m = "";
126 jump continue;
127 }
128 m += a;
129 jump continue;
130 }
131 if(a == "\"" && llGetSubString(csv, 0, 0) == a) {
132 m += a;
133 csv = llDeleteSubString(csv, 0, 0);
134 jump continue;
135 }
136 if(a == "\"") {
137 if(llList2String(s, -1) != a) {
138 s += a;
139 jump continue;
140 }
141 s = llDeleteSubList(s, -1, -1);
142 jump continue;
143 }
144 m += a;
145 @continue;
146 } while(csv != "");
147 // postcondition: length(s) = 0
148 return l + m;
149 }
150  
151 ///////////////////////////////////////////////////////////////////////////
152 // Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 //
153 ///////////////////////////////////////////////////////////////////////////
154 string wasListToCSV(list l) {
155 list v = [];
156 do {
157 string a = llDumpList2String(
158 llParseStringKeepNulls(
159 llList2String(
160 l,
161  
162 ),
163 ["\""],
164 []
165 ),
166 "\"\""
167 );
168 if(llParseStringKeepNulls(
169 a,
170 [" ", ",", "\n", "\""], []
171 ) !=
172 (list) a
173 ) a = "\"" + a + "\"";
174 v += a;
175 l = llDeleteSubList(l, 0, 0);
176 } while(l != []);
177 return llDumpList2String(v, ",");
178 }
179  
180 // corrade data
181 string CORRADE = "";
182 string GROUP = "";
183 string PASSWORD = "";
184  
185 // for holding the callback URL
186 string callback = "";
187  
188 // for holding the selected face number
189 string FACE = "";
190 // for holding the selected texture
191 string TEXTURE = "";
192 // for holding the current user
193 key TOUCH = NULL_KEY;
194  
195 // for notecard reading
196 integer line = 0;
197  
198 // key-value data will be read into this list
199 list tuples = [];
200  
201 default {
202 state_entry() {
203 if(llGetInventoryType("configuration") != INVENTORY_NOTECARD) {
204 llOwnerSay("Sorry, could not find an inventory notecard.");
205 return;
206 }
207 // DEBUG
208 llOwnerSay("Reading configuration file...");
209 llGetNotecardLine("configuration", line);
210 }
211 dataserver(key id, string data) {
212 if(data == EOF) {
213 // invariant, length(tuples) % 2 == 0
214 if(llGetListLength(tuples) % 2 != 0) {
215 llOwnerSay("Error in configuration notecard.");
216 return;
217 }
218 CORRADE = llList2String(
219 tuples,
220 llListFindList(
221 tuples,
222 [
223 "corrade"
224 ]
225 )
226 +1);
227 if(CORRADE == "") {
228 llOwnerSay("Error in configuration notecard: corrade");
229 return;
230 }
231 GROUP = llList2String(
232 tuples,
233 llListFindList(
234 tuples,
235 [
236 "group"
237 ]
238 )
239 +1);
240 if(GROUP == "") {
241 llOwnerSay("Error in configuration notecard: password");
242 return;
243 }
244 PASSWORD = llList2String(
245 tuples,
246 llListFindList(
247 tuples,
248 [
249 "password"
250 ]
251 )
252 +1);
253 if(GROUP == "") {
254 llOwnerSay("Error in configuration notecard: group");
255 return;
256 }
257 // DEBUG
258 llOwnerSay("Read configuration file...");
259 state url;
260 }
261 if(data == "") jump continue;
262 integer i = llSubStringIndex(data, "#");
263 if(i != -1) data = llDeleteSubString(data, i, -1);
264 list o = llParseString2List(data, ["="], []);
265 // get rid of starting and ending quotes
266 string k = llDumpList2String(
267 llParseString2List(
268 llStringTrim(
269 llList2String(
270 o,
271  
272 ),
273 STRING_TRIM),
274 ["\""], []
275 ), "\"");
276 string v = llDumpList2String(
277 llParseString2List(
278 llStringTrim(
279 llList2String(
280 o,
281 1
282 ),
283 STRING_TRIM),
284 ["\""], []
285 ), "\"");
286 if(k == "" || v == "") jump continue;
287 tuples += k;
288 tuples += v;
289 @continue;
290 llGetNotecardLine("configuration", ++line);
291 }
292 on_rez(integer num) {
293 llResetScript();
294 }
295 changed(integer change) {
296 if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
297 llResetScript();
298 }
299 }
300 }
301  
302 state url {
303 state_entry() {
304 // DEBUG
305 llOwnerSay("Requesting URL...");
306 llRequestURL();
307 }
308 http_request(key id, string method, string body) {
309 if(method != URL_REQUEST_GRANTED) return;
310 callback = body;
311 // DEBUG
312 llOwnerSay("Got URL...");
313 state detect;
314 }
315 on_rez(integer num) {
316 llResetScript();
317 }
318 changed(integer change) {
319 if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
320 llResetScript();
321 }
322 }
323 }
324  
325 state detect {
326 state_entry() {
327 // DEBUG
328 llOwnerSay("Detecting if Corrade is online...");
329 llSetTimerEvent(5);
330 }
331 timer() {
332 llRequestAgentData((key)CORRADE, DATA_ONLINE);
333 }
334 dataserver(key id, string data) {
335 if(data != "1") {
336 // DEBUG
337 llOwnerSay("Corrade is not online, sleeping...");
338 llSetTimerEvent(30);
339 return;
340 }
341 llSensorRepeat("", (key)CORRADE, AGENT, 10, TWO_PI, 1);
342 }
343 no_sensor() {
344 // DEBUG
345 llOwnerSay("Teleporting Corrade...");
346 llInstantMessage((key)CORRADE,
347 wasKeyValueEncode(
348 [
349 "command", "teleport",
350 "group", wasURLEscape(GROUP),
351 "password", wasURLEscape(PASSWORD),
352 "entity", "region",
353 "region", wasURLEscape(llGetRegionName()),
354 "position", wasURLEscape(
355 (string)(
356 llGetPos() + wasCirclePoint(1)
357 )
358 ),
359 "callback", callback
360 ]
361 )
362 );
363 }
364 sensor(integer num) {
365 llSetTimerEvent(0);
366 state check_rights;
367 }
368 http_request(key id, string method, string body) {
369 llHTTPResponse(id, 200, "OK");
370 if(wasKeyValueGet("command", body) != "teleport" ||
371 wasKeyValueGet("success", body) != "True") {
372 // DEBUG
373 llOwnerSay("Teleport failed: " +
374 wasURLUnescape(
375 wasKeyValueGet(
376 "error",
377 body
378 )
379 )
380 );
381 state detect_trampoline;
382 }
383 llSetTimerEvent(0);
384 state check_rights;
385 }
386 on_rez(integer num) {
387 llResetScript();
388 }
389 changed(integer change) {
390 if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
391 llResetScript();
392 }
393 }
394 }
395  
396 /*
397 * Trampoline used for delaying teleport requests sent to Corrade. Once code
398 * switches into this state, the script waits for some seconds and then
399 * switches to the detect state.
400 */
401 state detect_trampoline {
402 state_entry() {
403 // DEBUG
404 llOwnerSay("Sleeping...");
405 llSetTimerEvent(30);
406 }
407 timer() {
408 llSetTimerEvent(0);
409 state detect;
410 }
411 on_rez(integer num) {
412 llResetScript();
413 }
414 changed(integer change) {
415 if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
416 llResetScript();
417 }
418 }
419 }
420  
421 /*
422 * This state checks to see whether Corrade has the necessary rights to
423 * modify your objects. It checks by using the "getfrienddata" command
424 * which requires "friendship" Corrade permissions and then tests whether
425 * the "CanModifyTheirObjects" flag is set that indicates whether Corrade
426 * is able to alter your objects. In the end it proceeds to the main state.
427 */
428 state check_rights {
429 state_entry() {
430 // DEBUG
431 llOwnerSay("Checking whether Corrade has rights to modify your objects...");
432 llInstantMessage(
433 (key)CORRADE,
434 wasKeyValueEncode(
435 [
436 "command", "getfrienddata",
437 "group", wasURLEscape(GROUP),
438 "password", wasURLEscape(PASSWORD),
439 "agent", wasURLEscape(llGetOwner()),
440 "data", "CanModifyTheirObjects",
441 "callback", wasURLEscape(callback)
442 ]
443 )
444 );
445 llSetTimerEvent(60);
446 }
447 http_request(key id, string method, string body) {
448 llHTTPResponse(id, 200, "OK");
449 if(wasKeyValueGet("command", body) != "getfrienddata" ||
450 wasKeyValueGet("success", body) != "True") {
451 // DEBUG
452 llOwnerSay("Failed to get friend rights: " +
453 wasURLUnescape(
454 wasKeyValueGet(
455 "error",
456 body
457 )
458 )
459 );
460 state detect_trampoline;
461 }
462 // DEBUG
463 llOwnerSay("Got friend rights, checking...");
464 list data = wasCSVToList(wasURLUnescape(wasKeyValueGet("data", body)));
465 integer i = llListFindList(data, [ "CanModifyTheirObjects" ]);
466 if(i == -1) {
467 // DEBUG
468 llOwnerSay("Friend data not returned by the \"getfrienddata\" command...");
469 state detect_trampoline;
470 }
471 if(llList2String(data, i+1) != "True") {
472 // DEBUG
473 llOwnerSay("Corrade cannot modify your objects, please grant Corrade permissions to modify your objects using your viewer...");
474 state detect_trampoline;
475 }
476 // DEBUG
477 llOwnerSay("Corrade has permissions to modify your objects, proceeding...");
478 llSetTimerEvent(0);
479 state select_face;
480 }
481 timer() {
482 llSetTimerEvent(0);
483 // DEBUG
484 llOwnerSay("Timeout checking for friend rights...");
485 state detect_trampoline;
486 }
487 on_rez(integer num) {
488 llResetScript();
489 }
490 changed(integer change) {
491 if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
492 llResetScript();
493 }
494 }
495 }
496  
497 /*
498 * In this state we retrieve a face number from the user.
499 */
500 state select_face {
501 state_entry() {
502 if(TOUCH == NULL_KEY) {
503 //DEBUG
504 llOwnerSay("Please touch the primitive for a menu...");
505 return;
506 }
507 integer comChannel = (integer)("0x8" + llGetSubString(llGetKey(), 0, 6));
508 integer i = llGetNumberOfSides() - 1;
509 list buttons = [];
510 do {
511 buttons += (string)i;
512 llListen(comChannel, "", "", (string)i);
513 } while(--i > -1);
514 buttons += "default";
515 llListen(comChannel, "", "", "default");
516 buttons += "all";
517 llListen(comChannel, "", "", "all");
518 llDialog(TOUCH, "Please select a face to change...", buttons, comChannel);
519 }
520 touch_start(integer num) {
521 TOUCH = llDetectedKey(0);
522 integer comChannel = (integer)("0x8" + llGetSubString(llGetKey(), 0, 6));
523 integer i = llGetNumberOfSides() - 1;
524 list buttons = [];
525 do {
526 buttons += (string)i;
527 llListen(comChannel, "", "", (string)i);
528 } while(--i > -1);
529 buttons += "default";
530 llListen(comChannel, "", "", "default");
531 buttons += "all";
532 llListen(comChannel, "", "", "all");
533 llDialog(TOUCH, "Please select a face to change...", buttons, comChannel);
534 }
535 listen(integer channel, string name, key id, string face) {
536 llOwnerSay("Got face: " + face);
537 FACE = face;
538 state select_texture;
539 }
540 on_rez(integer num) {
541 llResetScript();
542 }
543 changed(integer change) {
544 if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
545 llResetScript();
546 }
547 }
548 }
549 /*
550 * In this state we let the user select a texture from inventory.
551 */
552 state select_texture {
553 state_entry() {
554 if(TOUCH == NULL_KEY) return;
555 integer comChannel = (integer)("0x8" + llGetSubString(llGetKey(), 0, 6));
556 integer i = llGetInventoryNumber(INVENTORY_TEXTURE) -1;
557 list buttons = [];
558 do {
559 string inventoryTexture = llGetInventoryName(INVENTORY_TEXTURE, i);
560 buttons += inventoryTexture;
561 llListen(comChannel, "", "", inventoryTexture);
562 } while(--i > -1);
563 llDialog(TOUCH, "Please select from the avilable textures...", buttons, comChannel);
564 }
565 touch_start(integer num) {
566 TOUCH = llDetectedKey(0);
567 integer comChannel = (integer)("0x8" + llGetSubString(llGetKey(), 0, 6));
568 integer i = llGetInventoryNumber(INVENTORY_TEXTURE) -1;
569 list buttons = [];
570 do {
571 string inventoryTexture = llGetInventoryName(INVENTORY_TEXTURE, i);
572 buttons += inventoryTexture;
573 llListen(comChannel, "", "", inventoryTexture);
574 } while(--i > -1);
575 llDialog(TOUCH, "Please select from the avilable textures...", buttons, comChannel);
576 }
577 listen(integer channel, string name, key id, string inventoryTexture) {
578 llOwnerSay("Got texture: " + inventoryTexture);
579 TEXTURE = inventoryTexture;
580 state change_texture;
581 }
582 on_rez(integer num) {
583 llResetScript();
584 }
585 changed(integer change) {
586 if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
587 llResetScript();
588 }
589 }
590 }
591  
592 /*
593 * This state is reponsible for setting the texture on the previously
594 * selected face of this primitive. It makes a call to Corrade to set the
595 * texture on the selected face and then returns to the face selection.
596 * This state relies on the "interact" Corrade permission being set
597 * for the group that you are using this script with.
598 */
599 state change_texture {
600 state_entry() {
601 // DEBUG
602 llOwnerSay("Setting texture \"" + TEXTURE + "\" on face \"" + FACE + "\".");
603 llInstantMessage(CORRADE, wasKeyValueEncode(
604 [
605 // set the texture for the 4th face
606 // on a primitive in a 4m range
607 // specified by UUID
608 "command", "setprimitivetexturedata",
609 "group", wasURLEscape(GROUP),
610 "password", wasURLEscape(PASSWORD),
611 // the UUID of the primitive
612 "item", wasURLEscape(llGetKey()),
613 "range", 5,
614 // the face
615 "face", FACE,
616 // just set the texture to a texture UUID
617 "data", wasURLEscape(
618 wasListToCSV(
619 [
620 "TextureID", llGetInventoryKey(TEXTURE)
621 ]
622 )
623 ),
624 "callback", wasURLEscape(callback)
625 ]
626 )
627 );
628 llSetTimerEvent(60);
629 }
630 http_request(key id, string method, string body) {
631 llHTTPResponse(id, 200, "OK");
632 if(wasKeyValueGet("command", body) != "setprimitivetexturedata" ||
633 wasKeyValueGet("success", body) != "True") {
634 // DEBUG
635 llOwnerSay("Failed to set texture: " +
636 wasURLUnescape(
637 wasKeyValueGet(
638 "error",
639 body
640 )
641 )
642 );
643 state detect_trampoline;
644 }
645 // DEBUG
646 llOwnerSay("Texture set successfully...");
647 state select_face;
648 }
649 timer() {
650 llSetTimerEvent(0);
651 // DEBUG
652 llOwnerSay("Timeout setting texture...");
653 state detect_trampoline;
654 }
655 on_rez(integer num) {
656 llResetScript();
657 }
658 changed(integer change) {
659 if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
660 llResetScript();
661 }
662 }
663 }
664