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 2015 - License: GNU GPLv3 //
3 // Please see: http://www.gnu.org/licenses/gpl.html for legal details, //
4 // rights of fair usage, the disclaimer and warranty conditions. //
5 ///////////////////////////////////////////////////////////////////////////
6  
7 ///////////////////////////////////////////////////////////////////////////
8 // Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 //
9 ///////////////////////////////////////////////////////////////////////////
10 // escapes a string in conformance with RFC1738
11 string wasURLEscape(string i) {
12 string o = "";
13 do {
14 string c = llGetSubString(i, 0, 0);
15 i = llDeleteSubString(i, 0, 0);
16 if(c == "") jump continue;
17 if(c == " ") {
18 o += "+";
19 jump continue;
20 }
21 if(c == "\n") {
22 o += "%0D" + llEscapeURL(c);
23 jump continue;
24 }
25 o += llEscapeURL(c);
26 @continue;
27 } while(i != "");
28 return o;
29 }
30  
31 ///////////////////////////////////////////////////////////////////////////
32 // Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 //
33 ///////////////////////////////////////////////////////////////////////////
34 // unescapes a string in conformance with RFC1738
35 string wasURLUnescape(string i) {
36 return llUnescapeURL(
37 llDumpList2String(
38 llParseString2List(
39 llDumpList2String(
40 llParseString2List(
41 i,
42 ["+"],
43 []
44 ),
45 " "
46 ),
47 ["%0D%0A"],
48 []
49 ),
50 "\n"
51 )
52 );
53 }
54  
55 ///////////////////////////////////////////////////////////////////////////
56 // Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 //
57 ///////////////////////////////////////////////////////////////////////////
58 string wasKeyValueEncode(list data) {
59 list k = llList2ListStrided(data, 0, -1, 2);
60 list v = llList2ListStrided(llDeleteSubList(data, 0, 0), 0, -1, 2);
61 data = [];
62 do {
63 data += llList2String(k, 0) + "=" + llList2String(v, 0);
64 k = llDeleteSubList(k, 0, 0);
65 v = llDeleteSubList(v, 0, 0);
66 } while(llGetListLength(k) != 0);
67 return llDumpList2String(data, "&");
68 }
69  
70 ///////////////////////////////////////////////////////////////////////////
71 // Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 //
72 ///////////////////////////////////////////////////////////////////////////
73 string wasKeyValueGet(string k, string data) {
74 if(llStringLength(data) == 0) return "";
75 if(llStringLength(k) == 0) return "";
76 list a = llParseString2List(data, ["&", "="], []);
77 integer i = llListFindList(llList2ListStrided(a, 0, -1, 2), [ k ]);
78 if(i != -1) return llList2String(a, 2*i+1);
79 return "";
80 }
81  
82 ///////////////////////////////////////////////////////////////////////////
83 // Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 //
84 ///////////////////////////////////////////////////////////////////////////
85 list wasCSVToList(string csv) {
86 list l = [];
87 list s = [];
88 string m = "";
89 do {
90 string a = llGetSubString(csv, 0, 0);
91 csv = llDeleteSubString(csv, 0, 0);
92 if(a == ",") {
93 if(llList2String(s, -1) != "\"") {
94 l += m;
95 m = "";
96 jump continue;
97 }
98 m += a;
99 jump continue;
100 }
101 if(a == "\"" && llGetSubString(csv, 0, 0) == a) {
102 m += a;
103 csv = llDeleteSubString(csv, 0, 0);
104 jump continue;
105 }
106 if(a == "\"") {
107 if(llList2String(s, -1) != a) {
108 s += a;
109 jump continue;
110 }
111 s = llDeleteSubList(s, -1, -1);
112 jump continue;
113 }
114 m += a;
115 @continue;
116 } while(csv != "");
117 // postcondition: length(s) = 0
118 return l + m;
119 }
120  
121 ///////////////////////////////////////////////////////////////////////////
122 // Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 //
123 ///////////////////////////////////////////////////////////////////////////
124 integer wasDateTimeToStamp(
125 integer year,
126 integer month,
127 integer day,
128 integer hour,
129 integer minute,
130 integer second
131 ) {
132 month -= 2;
133 if (month <= 0) {
134 month += 12;
135 --year;
136 }
137 return
138 (
139 (((year / 4 - year / 100 + year / 400 + (367 * month) / 12 + day) +
140 year * 365 - 719499
141 ) * 24 + hour
142 ) * 60 + minute
143 ) * 60 + second;
144 }
145  
146 ///////////////////////////////////////////////////////////////////////////
147 // Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 //
148 ///////////////////////////////////////////////////////////////////////////
149 float wasFmod(float a, float p) {
150 if(p == 0) return (float)"nan";
151 return a - ((integer)(a/p) * p);
152 }
153  
154 ///////////////////////////////////////////////////////////////////////////
155 // Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3 //
156 // Original: Clive Page, Leicester University, UK. 1995-MAY-2 //
157 ///////////////////////////////////////////////////////////////////////////
158 list wasUnixTimeToDateTime(integer seconds) {
159 integer mjday = (integer)(seconds/86400 + 40587);
160 integer dateYear = 1858 + (integer)( (mjday + 321.51) / 365.25);
161 float day = (integer)( wasFmod(mjday + 262.25, 365.25) ) + 0.5;
162 integer dateMonth = 1 + (integer)(wasFmod(day / 30.6 + 2.0, 12.0) );
163 integer dateDay = 1 + (integer)(wasFmod(day,30.6));
164 float nsecs = wasFmod(seconds, 86400);
165 integer dateSeconds = (integer)wasFmod(nsecs, 60);
166 nsecs = nsecs / 60;
167 integer dateMinutes = (integer)wasFmod(nsecs, 60);
168 integer dateHour = (integer)(nsecs / 60);
169 return [ dateYear,
170 dateMonth, dateDay, dateHour, dateMinutes, dateSeconds ];
171 }
172  
173 // for changing states
174 string nextstate = "";
175  
176 // notecard reading
177 integer line = 0;
178 list tuples = [];
179  
180 // corrade data
181 key CORRADE = NULL_KEY;
182 string GROUP = "";
183 string PASSWORD = "";
184 // the owner of the rental system
185 // the dude or dudette that gets paid
186 key OWNER = NULL_KEY;
187 // the price of the rent
188 integer PRICE = 0;
189 // the time for the rent in seconds
190 integer RENT = 0;
191 string URL = "";
192 // the role to invite rentants to
193 string ROLE = "";
194  
195 default {
196 state_entry() {
197 if(llGetInventoryType("configuration") != INVENTORY_NOTECARD) {
198 llSetText("Sorry, could not find an inventory notecard.", <1, 0, 0>, 1.0);
199 return;
200 }
201 llSetText("Reading configuration notecard...", <1, 1, 0>, 1.0);
202 llGetNotecardLine("configuration", line);
203 }
204 dataserver(key id, string data) {
205 if(data == EOF) {
206 // invariant, length(tuples) % 2 == 0
207 if(llGetListLength(tuples) % 2 != 0) {
208 llSetText("Error in configuration notecard.", <1, 0, 0>, 1.0);
209 return;
210 }
211 CORRADE = (key)llList2String(
212 tuples,
213 llListFindList(
214 tuples,
215 [
216 "corrade"
217 ]
218 )
219 +1);
220 if(CORRADE == NULL_KEY) {
221 llSetText("Error in configuration notecard: corrade", <1, 0, 0>, 1.0);
222 return;
223 }
224 GROUP = llList2String(
225 tuples,
226 llListFindList(
227 tuples,
228 [
229 "group"
230 ]
231 )
232 +1);
233 if(GROUP == "") {
234 llSetText("Error in configuration notecard: group", <1, 0, 0>, 1.0);
235 return;
236 }
237 PASSWORD = llList2String(
238 tuples,
239 llListFindList(
240 tuples,
241 [
242 "password"
243 ]
244 )
245 +1);
246 if(PASSWORD == "") {
247 llSetText("Error in configuration notecard: password", <1, 0, 0>, 1.0);
248 return;
249 }
250 OWNER = (key)llList2String(
251 tuples,
252 llListFindList(
253 tuples,
254 [
255 "owner"
256 ]
257 )
258 +1);
259 if(OWNER == NULL_KEY) {
260 llSetText("Error in configuration notecard: owner", <1, 0, 0>, 1.0);
261 return;
262 }
263 PRICE = (integer)llList2String(
264 tuples,
265 llListFindList(
266 tuples,
267 [
268 "price"
269 ]
270 )
271 +1);
272 if(PRICE == 0) {
273 llSetText("Error in configuration notecard: price", <1, 0, 0>, 1.0);
274 return;
275 }
276 RENT = (integer)llList2String(
277 tuples,
278 llListFindList(
279 tuples,
280 [
281 "rent"
282 ]
283 )
284 +1);
285 if(RENT == 0) {
286 llSetText("Error in configuration notecard: rent", <1, 0, 0>, 1.0);
287 return;
288 }
289 ROLE = llList2String(
290 tuples,
291 llListFindList(
292 tuples,
293 [
294 "role"
295 ]
296 )
297 +1
298 );
299 if(ROLE == "") {
300 llSetText("Error in configuration notecard: role", <1, 0, 0>, 1.0);
301 return;
302 }
303  
304 llSetText("Read configuration notecard.", <0, 1, 0>, 1.0);
305  
306 // if data is set switch to rented state
307 if(llGetObjectDesc() != "") state rented;
308 // otherwise switch to the payment state
309 state payment;
310 }
311 if(data == "") jump continue;
312 integer i = llSubStringIndex(data, "#");
313 if(i != -1) data = llDeleteSubString(data, i, -1);
314 list o = llParseString2List(data, ["="], []);
315 // get rid of starting and ending quotes
316 string k = llDumpList2String(
317 llParseString2List(
318 llStringTrim(
319 llList2String(
320 o,
321  
322 ),
323 STRING_TRIM),
324 ["\""], []
325 ), "\"");
326 string v = llDumpList2String(
327 llParseString2List(
328 llStringTrim(
329 llList2String(
330 o,
331 1
332 ),
333 STRING_TRIM),
334 ["\""], []
335 ), "\"");
336 if(k == "" || v == "") jump continue;
337 tuples += k;
338 tuples += v;
339 @continue;
340 llGetNotecardLine("configuration", ++line);
341 }
342 on_rez(integer num) {
343 llResetScript();
344 }
345 changed(integer change) {
346 if(change & CHANGED_INVENTORY) {
347 llResetScript();
348 }
349 }
350 }
351  
352 state trampoline {
353 state_entry() {
354 llSetTimerEvent(5);
355 }
356 timer() {
357 llSetTimerEvent(0);
358 // State jump table
359 if(nextstate == "url") state url;
360 if(nextstate == "getmembers") state getmembers;
361 if(nextstate == "getrolemembers") state getrolemembers;
362 if(nextstate == "addtorole") state addtorole;
363 if(nextstate == "invite") state invite;
364 if(nextstate == "rented") state rented;
365 if(nextstate == "demote") state demote;
366 // automata in invalid state
367 }
368 on_rez(integer num) {
369 llResetScript();
370 }
371 changed(integer change) {
372 if(change & CHANGED_INVENTORY) {
373 llResetScript();
374 }
375 }
376 }
377  
378 state payment {
379 state_entry() {
380 llSetPayPrice(PRICE, [PRICE]);
381 llSetClickAction(CLICK_ACTION_PAY);
382 llSetText("☀ Touch me to rent this place! ☀", <0, 1, 0>, 1.0);
383 }
384 money(key id, integer amount) {
385 // Get the current time stamp.
386 list stamp = llList2List(
387 llParseString2List(
388 llGetTimestamp(),
389 ["-",":","T", "."],[""]
390 ),
391 0, 5
392 );
393 // convert to seconds and add the rent
394 integer delta = wasDateTimeToStamp(
395 llList2Integer(stamp, 0),
396 llList2Integer(stamp, 1),
397 llList2Integer(stamp, 2),
398 llList2Integer(stamp, 3),
399 llList2Integer(stamp, 4),
400 llList2Integer(stamp, 5)
401 ) +
402 // the amount of time is the amount paid
403 // times the rent time divided by the price
404 amount * (integer)(
405 (float)RENT /
406 (float)PRICE
407 );
408 // convert back to a timestamp
409 stamp = wasUnixTimeToDateTime(delta);
410 // and set the renter and the eviction date
411 llSetObjectDesc(
412 wasKeyValueEncode(
413 [
414 "rentantUUID", id,
415 "rentantName", llKey2Name(id),
416 "expiresDate", llList2String(stamp, 0) +
417 "-" + llList2String(stamp, 1) +
418 "-" + llList2String(stamp, 2) +
419 "T" + llList2String(stamp, 3) +
420 ":" + llList2String(stamp, 4) +
421 ":" + llList2String(stamp, 5)
422 ]
423 )
424 );
425 nextstate = "getmembers";
426 state url;
427 }
428 on_rez(integer num) {
429 llResetScript();
430 }
431 changed(integer change) {
432 if(change & CHANGED_INVENTORY) {
433 llResetScript();
434 }
435 }
436 }
437  
438 state url {
439 state_entry() {
440 // release any previous URL
441 llReleaseURL(URL);
442 // request a new URL
443 llRequestURL();
444 }
445 http_request(key id, string method, string body) {
446 if(method != URL_REQUEST_GRANTED) {
447 llSetText("☀ Unable to get an URL! ☀", <1, 0, 0>, 1.0);
448 nextstate = "url";
449 state trampoline;
450 }
451 URL = body;
452 // state URL jump table
453 if(nextstate == "getmembers") state getmembers;
454 if(nextstate == "demote") state demote;
455 // automata in invalid state
456 }
457 on_rez(integer num) {
458 llResetScript();
459 }
460 changed(integer change) {
461 if(change & CHANGED_INVENTORY) {
462 llResetScript();
463 }
464 }
465 }
466  
467 state getmembers {
468 state_entry() {
469 llSetText("☀ Getting group members... ☀", <1, 1, 0>, 1.0);
470 llInstantMessage(CORRADE,
471 wasKeyValueEncode(
472 [
473 "command", "getmembers",
474 "group", wasURLEscape(GROUP),
475 "password", wasURLEscape(PASSWORD),
476 // we just care if the agent is in the group
477 // so we use Corrade's sifting ability in order
478 // to reduce the script memory usage
479 "sift", wasURLEscape(
480 "(" +
481 wasKeyValueGet(
482 "rentantUUID",
483 llGetObjectDesc()
484 ) +
485 ")*"
486 ),
487 "callback", wasURLEscape(URL)
488 ]
489 )
490 );
491 llSetTimerEvent(60);
492 }
493 http_request(key id, string method, string body) {
494 if(wasKeyValueGet("success", body) != "True") {
495 llSetText("☀ Could not get group members! ☀", <1, 0, 0>, 1.0);
496 nextstate = "getmembers";
497 state trampoline;
498 }
499 // check that the payer is part of the role
500 integer i =
501 llListFindList(
502 wasCSVToList(
503 wasURLUnescape(
504 wasKeyValueGet(
505 "data",
506 body
507 )
508 )
509 ),
510 (list)wasKeyValueGet(
511 "rentantUUID",
512 llGetObjectDesc()
513 )
514 );
515 llSetTimerEvent(0);
516 // if they are in the group then check roles.
517 if(i != -1) state getrolemembers;
518 // otherwise invite them to the group role.
519 state invite;
520 }
521 timer() {
522 llSetTimerEvent(0);
523 nextstate = "getmembers";
524 state trampoline;
525 }
526 on_rez(integer num) {
527 llResetScript();
528 }
529 changed(integer change) {
530 if(change & CHANGED_INVENTORY) {
531 llResetScript();
532 }
533 }
534 }
535  
536 state getrolemembers {
537 state_entry() {
538 llSetText("☀ Getting role members... ☀", <1, 1, 0>, 1.0);
539 llInstantMessage(CORRADE,
540 wasKeyValueEncode(
541 [
542 "command", "getrolemembers",
543 "group", wasURLEscape(GROUP),
544 "password", wasURLEscape(PASSWORD),
545 "role", wasURLEscape(ROLE),
546 // we just care if the agent is in the renter role
547 // so we use Corrade's sifting ability in order
548 // to reduce the script memory usage
549 "sift", wasURLEscape(
550 "(" +
551 wasKeyValueGet(
552 "rentantUUID",
553 llGetObjectDesc()
554 ) +
555 ")*"
556 ),
557 "callback", wasURLEscape(URL)
558 ]
559 )
560 );
561 llSetTimerEvent(60);
562 }
563 http_request(key id, string method, string body) {
564 if(wasKeyValueGet("success", body) != "True") {
565 llSetText("☀ Could not get role members! ☀", <1, 0, 0>, 1.0);
566 nextstate = "getrolemembers";
567 state trampoline;
568 }
569 // check that the payer is part of the role
570 integer i =
571 llListFindList(
572 wasCSVToList(
573 wasURLUnescape(
574 wasKeyValueGet(
575 "data",
576 body
577 )
578 )
579 ),
580 (list)wasKeyValueGet(
581 "rentantUUID",
582 llGetObjectDesc()
583 )
584 );
585 llSetTimerEvent(0);
586 // if they are in the role then skip inviting them.
587 if(i != -1) state rented;
588 // otherwise add them to the land role.
589 state addtorole;
590 }
591 timer() {
592 llSetTimerEvent(0);
593 nextstate = "getrolemembers";
594 state trampoline;
595 }
596 on_rez(integer num) {
597 llResetScript();
598 }
599 changed(integer change) {
600 if(change & CHANGED_INVENTORY) {
601 llResetScript();
602 }
603 }
604 }
605  
606 state addtorole {
607 state_entry() {
608 llSetText("☀ Adding to role... ☀", <1, 1, 0>, 1.0);
609 llInstantMessage(CORRADE,
610 wasKeyValueEncode(
611 [
612 "command", "addtorole",
613 "group", wasURLEscape(GROUP),
614 "password", wasURLEscape(PASSWORD),
615 "agent", wasURLEscape(
616 wasKeyValueGet(
617 "rentantUUID",
618 llGetObjectDesc()
619 )
620 ),
621 "role", wasURLEscape(ROLE),
622 "callback", wasURLEscape(URL)
623 ]
624 )
625 );
626 llSetTimerEvent(60);
627 }
628 http_request(key id, string method, string body) {
629 if(wasKeyValueGet("success", body) != "True") {
630 llSetText("☀ Could not add to role! ☀", <1, 0, 0>, 1.0);
631 nextstate = "addtorole";
632 state trampoline;
633 }
634 // otherwise invite them to the group role.
635 state rented;
636 }
637 timer() {
638 llSetTimerEvent(0);
639 nextstate = "addtorole";
640 state trampoline;
641 }
642 on_rez(integer num) {
643 llResetScript();
644 }
645 changed(integer change) {
646 if(change & CHANGED_INVENTORY) {
647 llResetScript();
648 }
649 }
650 }
651  
652 state invite {
653 state_entry() {
654 llSetText("☀ Please accept the group invite! ☀", <1, 1, 0>, 1.0);
655 // invite the agent to the land group
656 llInstantMessage(CORRADE,
657 wasKeyValueEncode(
658 [
659 "command", "invite",
660 "group", wasURLEscape(GROUP),
661 "password", wasURLEscape(PASSWORD),
662 "agent", wasURLEscape(
663 wasKeyValueGet(
664 "rentantUUID",
665 llGetObjectDesc()
666 )
667 ),
668 "role", wasURLEscape(ROLE),
669 "callback", wasURLEscape(URL)
670 ]
671 )
672 );
673 // handle any Corrade timeouts
674 llSetTimerEvent(60);
675 }
676 http_request(key id, string method, string body) {
677 llHTTPResponse(id, 200, "OK");
678 // Checks if the invite was sent successfully or if that fails but the
679 // agent is already in the group (status 15345) then continue.
680 // Otherwise, jump to the trampoline and send the invite again.
681 // Status codes:
682 // http://grimore.org/secondlife/scripted_agents/corrade/status_codes/progressive
683 if(wasKeyValueGet("success", body) != "True" &&
684 wasKeyValueGet("status", body) != "15345") {
685 llSetText("☀ Group invite could not be sent! ☀", <1, 0, 0>, 1.0);
686 nextstate = "invite";
687 state trampoline;
688 }
689 llSetText("☀ Group invitation sent! ☀", <1, 1, 0>, 1.0);
690 llSetTimerEvent(0);
691 state rented;
692 }
693 timer() {
694 llSetTimerEvent(0);
695 nextstate = "invite";
696 state trampoline;
697 }
698 }
699  
700 state rented {
701 state_entry() {
702 // Get the expiration date
703 list expires = llList2List(
704 llParseString2List(
705 wasKeyValueGet(
706 "expiresDate",
707 llGetObjectDesc()
708 ),
709 ["-",":","T", "."],[""]
710 ),
711 0, 5
712 );
713 // Get the current date
714 list stamp = llList2List(
715 llParseString2List(
716 llGetTimestamp(),
717 ["-",":","T", "."],[""]
718 ),
719 0, 5
720 );
721  
722 integer delta = wasDateTimeToStamp(
723 llList2Integer(expires, 0),
724 llList2Integer(expires, 1),
725 llList2Integer(expires, 2),
726 llList2Integer(expires, 3),
727 llList2Integer(expires, 4),
728 llList2Integer(expires, 5)
729 ) - wasDateTimeToStamp(
730 llList2Integer(stamp, 0),
731 llList2Integer(stamp, 1),
732 llList2Integer(stamp, 2),
733 llList2Integer(stamp, 3),
734 llList2Integer(stamp, 4),
735 llList2Integer(stamp, 5)
736 );
737  
738 // the rent has expired, now evict the rentant
739 if(delta <= 0) {
740 llSetTimerEvent(0);
741 nextstate = "demote";
742 state url;
743 }
744  
745 // otherwise, update the remaining time
746 list remaining = wasUnixTimeToDateTime(delta);
747 remaining = llListReplaceList(remaining, [ llList2Integer(remaining, 0)-1970 ], 0, 0);
748 remaining = llListReplaceList(remaining, [ llList2Integer(remaining, 1)-1 ], 1, 1);
749 remaining = llListReplaceList(remaining, [ llList2Integer(remaining, 2)-1 ], 2, 2);
750  
751 llSetText(
752 "☀ Private property! ☀"
753 + "\n" +
754 "Rented by: " +
755 wasKeyValueGet(
756 "rentantName",
757 llGetObjectDesc()
758 )
759 + "\n" +
760 "Expires on: " +
761 wasKeyValueGet(
762 "expiresDate",
763 llGetObjectDesc()
764 )
765 + "\n" +
766 "Remaining: " +
767 llList2String(remaining, 0) + "-" +
768 llList2String(remaining, 1) + "-" +
769 llList2String(remaining, 2) + " " +
770 llList2String(remaining, 3) + ":" +
771 llList2String(remaining, 4)
772 + "\n" +
773 "Touch to extend rent.",
774 <0, 1, 1>,
775 1.0
776 );
777 llSetPayPrice(PRICE, [PRICE]);
778 llSetClickAction(CLICK_ACTION_PAY);
779 // set the countdown every minute
780 llSetTimerEvent(60);
781 }
782 money(key id, integer amount) {
783 // Get the expiration date
784 list stamp = llList2List(
785 llParseString2List(
786 wasKeyValueGet(
787 "expiresDate",
788 llGetObjectDesc()
789 ),
790 ["-",":","T", "."],[""]
791 ),
792 0, 5
793 );
794 // convert to seconds and add the extended rent
795 integer delta = wasDateTimeToStamp(
796 llList2Integer(stamp, 0),
797 llList2Integer(stamp, 1),
798 llList2Integer(stamp, 2),
799 llList2Integer(stamp, 3),
800 llList2Integer(stamp, 4),
801 llList2Integer(stamp, 5)
802 ) +
803 // the amount of time to extend is the amount
804 // paid times the rent time divided by the price
805 amount * (integer)(
806 (float)RENT /
807 (float)PRICE
808 );
809 // convert back to a timestamp
810 stamp = wasUnixTimeToDateTime(delta);
811 // and set the renter and the eviction date
812 llSetObjectDesc(
813 wasKeyValueEncode(
814 [
815 "rentantUUID", wasKeyValueGet(
816 "rentantUUID",
817 llGetObjectDesc()
818 ),
819 "rentantName", wasKeyValueGet(
820 "rentantName",
821 llGetObjectDesc()
822 ),
823 "expiresDate", llList2String(stamp, 0) +
824 "-" + llList2String(stamp, 1) +
825 "-" + llList2String(stamp, 2) +
826 "T" + llList2String(stamp, 3) +
827 ":" + llList2String(stamp, 4) +
828 ":" + llList2String(stamp, 5)
829 ]
830 )
831 );
832 llSetText("☀ Updating... ☀", <1, 1, 0>, 1.0);
833 llSetTimerEvent(0);
834 nextstate = "rented";
835 state trampoline;
836 }
837 timer() {
838 // Get the expiration date
839 list expires = llList2List(
840 llParseString2List(
841 wasKeyValueGet(
842 "expiresDate",
843 llGetObjectDesc()
844 ),
845 ["-",":","T", "."],[""]
846 ),
847 0, 5
848 );
849 // Get the current date
850 list stamp = llList2List(
851 llParseString2List(
852 llGetTimestamp(),
853 ["-",":","T", "."],[""]
854 ),
855 0, 5
856 );
857  
858 integer delta = wasDateTimeToStamp(
859 llList2Integer(expires, 0),
860 llList2Integer(expires, 1),
861 llList2Integer(expires, 2),
862 llList2Integer(expires, 3),
863 llList2Integer(expires, 4),
864 llList2Integer(expires, 5)
865 ) - wasDateTimeToStamp(
866 llList2Integer(stamp, 0),
867 llList2Integer(stamp, 1),
868 llList2Integer(stamp, 2),
869 llList2Integer(stamp, 3),
870 llList2Integer(stamp, 4),
871 llList2Integer(stamp, 5)
872 );
873  
874 // the rent has expired, now evict the rentant
875 if(delta <= 0) {
876 llSetTimerEvent(0);
877 nextstate = "demote";
878 state url;
879 }
880  
881 // otherwise, update the remaining time
882 list remaining = wasUnixTimeToDateTime(delta);
883 remaining = llListReplaceList(remaining, [ llList2Integer(remaining, 0)-1970 ], 0, 0);
884 remaining = llListReplaceList(remaining, [ llList2Integer(remaining, 1)-1 ], 1, 1);
885 remaining = llListReplaceList(remaining, [ llList2Integer(remaining, 2)-1 ], 2, 2);
886  
887 llSetText(
888 "☀ Private property! ☀"
889 + "\n" +
890 "Rented by: " +
891 wasKeyValueGet(
892 "rentantName",
893 llGetObjectDesc()
894 )
895 + "\n" +
896 "Expires on: " +
897 wasKeyValueGet(
898 "expiresDate",
899 llGetObjectDesc()
900 )
901 + "\n" +
902 "Remaining: " +
903 llList2String(remaining, 0) + "-" +
904 llList2String(remaining, 1) + "-" +
905 llList2String(remaining, 2) + " " +
906 llList2String(remaining, 3) + ":" +
907 llList2String(remaining, 4)
908 + "\n" +
909 "Touch to extend rent.",
910 <0, 1, 1>,
911 1.0
912 );
913  
914 }
915 on_rez(integer num) {
916 llResetScript();
917 }
918 changed(integer change) {
919 if(change & CHANGED_INVENTORY) {
920 llResetScript();
921 }
922 }
923 }
924  
925 state demote {
926 state_entry() {
927 llSetText("☀ Rent has expired! ☀", <1, 0, 0>, 1.0);
928 // demote the agent from the renter role
929 llInstantMessage(CORRADE,
930 wasKeyValueEncode(
931 [
932 "command", "deletefromrole",
933 "group", wasURLEscape(GROUP),
934 "password", wasURLEscape(PASSWORD),
935 "agent", wasURLEscape(
936 wasKeyValueGet(
937 "rentantUUID",
938 llGetObjectDesc()
939 )
940 ),
941 "role", wasURLEscape(ROLE),
942 "callback", wasURLEscape(URL)
943 ]
944 )
945 );
946 // handle any Corrade timeouts
947 llSetTimerEvent(60);
948 }
949 http_request(key id, string method, string body) {
950 llHTTPResponse(id, 200, "OK");
951 // Checks if the demote was sent successfully or if that fails but the
952 // agent has already left the group (status 11502) then continue.
953 // Otherwise, jump to the trampoline and send the demote again.
954 // Status codes:
955 // http://grimore.org/secondlife/scripted_agents/corrade/status_codes/progressive
956 if(wasKeyValueGet("success", body) != "True" &&
957 wasKeyValueGet("status", body) != "11502") {
958 llSetText("☀ Could not demote! ☀", <1, 0, 0>, 1.0);
959 nextstate = "demote";
960 state trampoline;
961 }
962 llSetText("☀ Renter demoted! ☀", <1, 1, 0>, 1.0);
963 llSetTimerEvent(0);
964  
965 // Now clean up the rental and restart.
966 llSetObjectDesc("");
967 llResetScript();
968 }
969 timer() {
970 llSetTimerEvent(0);
971 nextstate = "demote";
972 state trampoline;
973 }
974 }
975