aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/~MLP lite for OpenSim.lsl
diff options
context:
space:
mode:
authorDave Seikel2018-05-20 23:17:24 +1000
committerDave Seikel2018-05-20 23:17:24 +1000
commit9dd74045373c39d8a35a59f3c2801b0348a44a9b (patch)
treeb3cdc4ba800ffa44b98291ce12a0eb1c9ad809e4 /~MLP lite for OpenSim.lsl
parentInitial commit (diff)
downloadMLP-lite-9dd74045373c39d8a35a59f3c2801b0348a44a9b.zip
MLP-lite-9dd74045373c39d8a35a59f3c2801b0348a44a9b.tar.gz
MLP-lite-9dd74045373c39d8a35a59f3c2801b0348a44a9b.tar.bz2
MLP-lite-9dd74045373c39d8a35a59f3c2801b0348a44a9b.tar.xz
Initial commit.
This is the current alpha version, it was time to put it somewhere.
Diffstat (limited to '~MLP lite for OpenSim.lsl')
-rw-r--r--~MLP lite for OpenSim.lsl1550
1 files changed, 1550 insertions, 0 deletions
diff --git a/~MLP lite for OpenSim.lsl b/~MLP lite for OpenSim.lsl
new file mode 100644
index 0000000..2382a09
--- /dev/null
+++ b/~MLP lite for OpenSim.lsl
@@ -0,0 +1,1550 @@
1//
2// MLP lite v3.0 for OpenSim
3// Based on the original MLP - MULTI-LOVE-POSE V1.2 - Copyright (c) 2006, by Miffy Fluffy (BSD License)
4// This code has bounced around the Second Life and OpenSim for over a decade, with various people working on it.
5// MLP lite for OpenSim is almost a complete rewrite by onefang rejected.
6
7string Version = "MLP lite v3.0 alpha";
8
9list COLOUR_NAMES =
10[
11 "HIDE", "PINK", "BLUE", "PINK2",
12 "BLUE2", "GREEN", "MAGENTA", "RED",
13 "ORANGE", "WHITE", "BLACK", "YELLOW",
14 "CYAN", "RED2", "TEAL", "GREEN2"
15];
16
17list COLOURS =
18[
19 <0.0,0.0,0.0>, // 0 = HIDE
20 <0.835,0.345,0.482>, // 1 = PINK
21 <0.353,0.518,0.827>, // 2 = BLUE
22 <0.635,0.145,0.282>, // 3 = PINK2 - Dark pink
23 <0.153,0.318,0.627>, // 4 = BLUE2 - Dark blue
24 <0.128,0.500,0.128>, // 5 = GREEN
25 <1.000,0.000,1.000>, // 6 = MAGENTA
26 <1.000,0.000,0.000>, // 7 = RED
27 <1.000,0.500,0.000>, // 8 = ORANGE
28 <1.000,1.000,1.000>, // 9 = WHITE
29 <0.0,0.0,0.0>, // 10 = BLACK
30 <1.0,1.0,0.0>, // 11 = YELLOW
31 <0.0,0.8,0.8>, // 12 = CYAN
32 <0.5,0.0,0.0>, // 13 = RED2
33 <0.0,0.5,0.5>, // 14 = TEAL
34 <0.0,0.25,0.25> // 15 = GREEN2
35];
36
37list EXPRESSIONS =
38[
39 "",
40 "express_open_mouth",
41 "express_surprise_emote",
42 "express_tongue_out",
43 "express_smile",
44 "express_toothsmile",
45 "express_wink_emote",
46 "express_cry_emote",
47 "express_kiss",
48 "express_laugh_emote",
49 "express_disdain",
50 "express_repulsed_emote",
51 "express_anger_emote",
52 "express_bored_emote",
53 "express_sad_emote",
54 "express_embarrassed_emote",
55 "express_frown",
56 "express_shrug_emote",
57 "express_afraid_emote",
58 "express_worry_emote",
59 "SLEEP"
60];
61
62key Owner;
63integer Channel;
64integer Chat = TRUE;
65integer Redo = TRUE;
66integer ReloadOnRez = TRUE;
67
68
69integer LoadMenu = TRUE;
70list ToMenus = [];
71list SeenMenus = [];
72key User0;
73integer People = 0;
74integer MenuUsers = 0;
75string CurrentMenu = "";
76integer ThisSwap;
77string Filter = "123456789";
78string LastFilter = "123456789";
79// NOTE - This one uses \n to separate sub lists, coz | is used in some of the commands.
80list Menus; // List of menus.
81integer MENU_NAME = 0; // Name of menu.
82integer MENU_AUTH = 1; // Authorised users of menu - 0 = owner, 1 = group, 2 = all
83integer MENU_COLOURS = 2; // \n spearated list of ball colours.
84integer MENU_ENTRIES = 3; // \n separated list of entries.
85integer MENU_CMDS = 4; // \n separated list of commands, matching the entries.
86integer MENU_SWAPS = 5; // \n separated list of colour sets.
87integer MENU_STRIDE = 6;
88
89integer LoadPos = TRUE;
90vector RefPos;
91rotation RefRot;
92string Pose = "";
93list Poses; // List of poses.
94integer POSE_NAME = 0; // Name of pose.
95integer POSE_ANIM = 1; // | separated animations.
96integer POSE_EMOTE = 2; // | separated emotions and timers list.
97integer POSE_POSROT = 3; // | separated posiiton and rotation pairs.
98integer POSE_STRIDE = 4;
99
100integer Lag = 45; // Assume we start with no lag.
101float Tick = 0.2; // Shortest tick for expressions.
102list Exps; // List of current expressions playing on avatars.
103integer EXP_AVATAR = 0; // Avatar to do expression on.
104integer EXP_EXP = 1; // Name of expression.
105integer EXP_TIME = 2; // Time for expression loop.
106integer EXP_NEXT = 3; // Time for next trigger.
107integer EXP_STRIDE = 4;
108
109integer LoadProp = TRUE;
110
111list Musers; // List of menu users.
112integer MUSER_AVATAR = 0; // Key of user.
113integer MUSER_CURRENT = 1; // Current menu of user.
114integer MUSER_STACK = 2; // | separated menu stack list.
115integer MUSER_STRIDE = 3;
116
117integer MaxBalls = 1; // One as a bare minimum, for a single person. No point if there are zero balls.
118integer BallCount;
119integer Adjusting;
120list Balls; // Ball / prop tracker.
121integer BALL_NUM = 0; // The number of the ball, negative numbers for the props.
122integer BALL_KEY = 1; // The UUID of the ball / prop.
123integer BALL_AVATAR = 2; // The UUID of any avatar sitting on the ball.
124integer BALL_STRIDE = 3;
125
126list Sounds;
127integer SOUND_NAME = 0;
128integer SOUND_INV = 1;
129integer SOUND_STRIDE = 2;
130
131// TODO - Should stride this to.
132list LMButtons;
133list LMParms;
134
135string LIST_SEP = "$!#"; // Used to seperate lists when sending them as strings.
136
137// The various link messages. The first lot are historical.
138//integer OLD_TOUCH = -1; // msg = "PRIMTOUCH"
139integer OLD_POSEB = 0; // msg = "POSEB" id = pose name
140// // Also CHECK1 and CHECK2 instead of pose name.
141integer OLD_STOP = 1; // msg = "STOP"
142//integer OLD_REF = 8; // msg = RefPos id = RefRot
143integer OLD_SITS = -11000; // msg = ball number|anim name id = avatar key
144integer OLD_STANDS = -11001; // msg = ball number id = avatar key
145integer OLD_ANIM = -11002; // msg = ball number|anim name id = avatar key
146//integer SEQ_CMD = -12001; // msg = ~sequencer command
147integer OLD_CMD = -12002; // msg = menu command for ~MLP id = user key
148
149integer MLP_CMD = -13001; // A command that ~MLP dealt with, so other scripts can hook it.
150integer TOOL_DATA = -13002; // Get Menus, Poses, Props, or Sounds list.
151integer MLP_DATA = -13003; // Set Menus, Poses, Props, or Sounds list.
152integer MLP_POSE = -13004; // doPose(pose, type);
153integer MLP_UNKNOWN = -13005; // ~MLP doesn't know this command, might be for other scripts.
154
155// Types for doPose(),
156integer POSES_POSE = -14001; // Change pose, even if it's changing to the same pose.
157integer POSES_SWAP = -14002; // Swap poses.
158integer POSES_DUMP = -14003; // Dump or save poses.
159integer POSES_Z = -14004; // Change height.
160
161integer listFindString(list lst, string name, integer stride)
162{
163 integer f = llListFindList(lst, [name]);
164 integer ix = f / stride;
165
166 // Round to nearest stride.
167 ix = ix * stride;
168
169 // Sanity check, make sure we found a name, not something else, else do it the slow way.
170 if ((-1 != f) && (ix != f))
171 {
172 integer l = llGetListLength(lst);
173 integer i;
174
175 f = -1;
176 for (i = 0; i < l; i += stride)
177 {
178 if (llList2String(lst, i) == name)
179 {
180 f = i;
181 i = l;
182 }
183 }
184 }
185 return f;
186}
187
188integer listFindWord(list lst, string s)
189{
190 integer l = llGetListLength(lst);
191 integer i;
192
193 for (i = 0; i < l; i++)
194 {
195 list w = llParseStringKeepNulls(llList2String(lst, i), [" "], []);
196
197 if (-1 != llListFindList(w, s))
198 return TRUE;
199 }
200 return FALSE;
201}
202
203say(string str)
204{
205 if (Chat)
206 {
207 if (MenuUsers) llWhisper(0, str);
208 else llOwnerSay(str);
209 }
210}
211
212// Only used in one place, but leave it here for now.
213string prStr(string str)
214{
215 integer ix = llSubStringIndex(str, ">");
216 vector p = ((vector) llGetSubString(str, 0, ix) - RefPos) / RefRot;
217 vector r = llRot2Euler((rotation) llGetSubString(str, ix + 1, -1) / RefRot) * RAD_TO_DEG;
218
219 // OpenSim likes to swap these around, which triggers the ball movement saving.
220 if (-179.9 >= r.x) r.x = 180.0;
221 if (-179.9 >= r.y) r.y = 180.0;
222 if (-179.9 >= r.z) r.z = 180.0;
223
224 return "<" + round(p.x, 3) + "," + round(p.y, 3) + "," + round(p.z, 3) +
225 "><" + round(r.x, 1) + "," + round(r.y, 1) + "," + round(r.z, 1) + ">";
226}
227
228string round(float number, integer places)
229{
230 float shifted;
231 integer rounded;
232 string s;
233
234 shifted = number * llPow(10.0, (float) places);
235 rounded = llRound(shifted);
236 s = (string) ((float) rounded / llPow(10.0, (float)places));
237 rounded = llSubStringIndex(s, ".");
238 if (-1 != rounded)
239 s = llGetSubString(s, 0, llSubStringIndex(s, ".") + places);
240 else
241 {
242 s += ".00000000";
243 s = llGetSubString(s,0,llSubStringIndex(s, ".") + places);
244 }
245 return s;
246}
247
248setRunning(integer st)
249{
250 integer i = llGetInventoryNumber(INVENTORY_SCRIPT);
251
252 while (i-- > 0)
253 {
254 string s = llGetInventoryName(INVENTORY_SCRIPT, i);
255
256 if ((llSubStringIndex(s, "~MLP lite ") == 0) && (llGetScriptName() != s))
257 {
258 llSetScriptState(s, st);
259 if (st)
260 llResetOtherScript(s);
261 }
262 }
263 if (st)
264 llSetTimerEvent(120.0);
265 CurrentMenu = "";
266 doPose("", POSES_POSE, TRUE);
267}
268
269stop()
270{
271 llMessageLinked(LINK_SET, OLD_STOP, "STOP", NULL_KEY);
272 Adjusting = FALSE;
273 CurrentMenu = "";
274 doPose("", POSES_POSE, TRUE);
275 Balls = [];
276 Exps= [];
277}
278
279readMenu(list cards)
280{
281 integer i;
282 integer l = llGetListLength(cards);
283
284 cards = llListSort(cards, 1, TRUE);
285 for (i = 0; i < l; i++)
286 {
287 string menu = "";
288 string card = llList2String(cards, i);
289 list crd = llParseStringKeepNulls(osGetNotecard(card), ["\n"], []);
290 integer m = llGetListLength(crd);
291 integer j;
292 integer k;
293
294 llOwnerSay("Reading '" + card + "'.");
295 for (j = 0; j < m; j++)
296 {
297 string data = llList2String(crd, j);
298 string filter = "";
299 integer ix = llSubStringIndex(data, "/"); // remove comments
300
301 if (ix != -1)
302 {
303 if (ix == 0) data = "";
304 else data = llGetSubString(data, 0, ix - 1);
305 }
306 data = llStringTrim(data, STRING_TRIM);
307 if (data != "")
308 {
309 string cmd = data;
310 string mdata = data;
311
312 ix = llSubStringIndex(data, " ");
313 if (ix != -1)
314 {
315 cmd = llStringTrim(llGetSubString(data, 0, ix - 1), STRING_TRIM);
316 data = llStringTrim(llGetSubString(data, ix + 1, -1), STRING_TRIM);
317 mdata = data;
318 }
319 else
320 mdata = "";
321
322 string mcmd = cmd;
323 list ldata = llParseStringKeepNulls(data, ["|"], []);
324
325 ix = llGetListLength(ldata);
326 for (k = 0; k < ix; k++)
327 ldata = llListReplaceList(ldata, [llStringTrim(llList2String(ldata, k), STRING_TRIM)], k, k);
328 data = llDumpList2String(ldata, "|");
329
330 string arg1 = llList2String(ldata, 0);
331
332 if (cmd == "MENU")
333 {
334 integer auth;
335 string colours = "";
336
337 menu = arg1;
338 if (llList2String(ldata, 1) == "GROUP") auth = 1;
339 else if (llList2String(ldata, 1) != "OWNER") auth = 2;
340 for (k = 0; k < 9; ++k) // Maximum of 9 balls supported.
341 {
342 string bc = llList2String(ldata, k + 2);
343
344 if ("" != bc)
345 {
346 colours += "\n" + bc;
347 if ((k + 1) > MaxBalls)
348 MaxBalls = k + 1;
349 }
350 }
351 saveMenu(menu, auth, colours, "", "", "");
352 ToMenus += [menu];
353 }
354 else if (cmd == "NORELOAD")
355 ReloadOnRez = (arg1 != "0");
356 else
357 { // The rest are entries within a menu.
358 if (cmd == "POSE")
359 {
360 string thisPose = llStringTrim(llList2String(ldata, 0), STRING_TRIM);
361 string anims = "";
362 string exps = "";
363
364 ix = llGetListLength(ldata);
365 for (k = 1; k < ix; k++)
366 {
367 string anim = llList2String(ldata, k);
368 string exp;
369 float expTime;
370
371 if (anim == "") anim = "sit_ground";
372
373 if (llGetSubString(anim, -1, -1) == "*")
374 {
375 exp = llList2String(EXPRESSIONS, 1);
376 expTime = 0.5;
377 anim = llGetSubString(anim, 0, -2);
378 }
379 else
380 {
381 integer a = llSubStringIndex(anim, "::");
382
383 if (a == -1)
384 {
385 exp = "";
386 expTime = 0.5;
387 }
388 else
389 {
390 list parms = llParseString2List(anim, ["::"], []);
391 integer p = (integer) llList2String(parms, 1);
392
393 anim = llList2String(parms, 0);
394 exp = llList2String(EXPRESSIONS, p);
395 expTime = (float) llList2String(parms, 2);
396
397 if (expTime <= 0.0)
398 expTime = 0.5;
399 }
400 }
401 anims += "|" + anim;
402 exps += "|" + exp + "::" + (string) expTime;
403 }
404 anims = llGetSubString(anims, 1, -1);
405 exps = llGetSubString(exps, 1, -1);
406 savePose(thisPose, anims, exps, "");
407 mcmd = "POSE";
408 mdata = thisPose;
409 }
410 else if (cmd == "CHAT")
411 {
412 if (llList2String(ldata, 1) != "OFF")
413 Chat = 1;
414 }
415 else if (cmd == "MENUUSERS")
416 {
417 if (llList2String(ldata, 1) == "GROUP")
418 MenuUsers = 1;
419 else if (llList2String(ldata, 1) != "OWNER")
420 MenuUsers = 2;
421 }
422 else if (cmd == "LINKMSG")
423 {
424 LMButtons += arg1;
425 LMParms += llList2String(ldata, 1);
426 }
427 else if (cmd == "SOUND")
428 Sounds += [arg1, llList2String(ldata, 1)];
429 else if (cmd == "SWAP")
430 {
431 list sl = llListReplaceList(ldata, [], 0, 0);
432
433 if ("" == arg1)
434 arg1 = "SWAP";
435 if ("SWAP" == data)
436 sl = ["21"];
437 ix = llGetListLength(sl);
438 if (ix == 0)
439 {
440 sl = ["21"];
441 ix = 1;
442 }
443
444 for (k = 0; k < ix; k++)
445 {
446 string s = llList2String(sl, k);
447 integer ls = llStringLength(s);
448
449 s += llGetSubString("213456789", ls, -1);
450 sl = llListReplaceList(sl, [s], k, k);
451 }
452 if (ix)
453 filter = llDumpList2String(sl, "\n");
454 }
455 else if (cmd == "TOMENU")
456 {
457 if ("-" != arg1)
458 SeenMenus += [arg1];
459 }
460 ix = findMenu(menu);
461 if ((-1 != ix) && ("-" != arg1))
462 {
463 saveMenu(menu,
464 llList2Integer(Menus, ix + MENU_AUTH),
465 "",
466 llList2String(Menus, ix + MENU_ENTRIES) + "\n" + arg1,
467 llList2String(Menus, ix + MENU_CMDS) + "\n" + mcmd + " " + mdata,
468 filter);
469 }
470 }
471 }
472 }
473 }
474}
475
476integer findMenu(string name)
477{
478 return listFindString(Menus, name, MENU_STRIDE);
479}
480
481saveMenu(string name, integer auth, string colours, string entries, string commands, string swaps)
482{
483 integer f = findMenu(name);
484
485 if ("\n" == llGetSubString(colours, 0, 0))
486 colours = llGetSubString(colours, 1, -1);
487 if ("\n" == llGetSubString(entries, 0, 0))
488 entries = llGetSubString(entries, 1, -1);
489 if ("\n" == llGetSubString(commands, 0, 0))
490 commands = llGetSubString(commands, 1, -1);
491 if ("\n" == llGetSubString(swaps, 0, 0))
492 swaps = llGetSubString(swaps, 1, -1);
493 if (-1 == f)
494 Menus += [name, auth, colours, entries, commands, swaps];
495 else
496 {
497 if ("" == colours)
498 colours = llList2String(Menus, f + MENU_COLOURS);
499 if ("" == entries)
500 entries = llList2String(Menus, f + MENU_ENTRIES);
501 if ("" == commands)
502 commands = llList2String(Menus, f + MENU_CMDS);
503 if ("" == swaps)
504 swaps = llList2String(Menus, f + MENU_SWAPS);
505 Menus = llListReplaceList(Menus, [name, auth, colours, entries, commands, swaps], f, f + MENU_STRIDE - 1);
506 }
507}
508
509vector getBallColour(integer b)
510{
511 integer f = findMenu(CurrentMenu);
512
513 if (-1 != f)
514 return llList2Vector(COLOURS, llListFindList(COLOUR_NAMES,
515 llList2String(llParseStringKeepNulls(llList2String(Menus, f + MENU_COLOURS), ["\n"], []), b)));
516 return <1.0, 1.0, 1.0>;
517}
518
519unauth(key id, string button, string who)
520{
521 llDialog(id, "\n" + button + " menu allowed only for " + who, ["OK"], -1);
522}
523
524touched(key avatar)
525{
526 if (avatar == Owner || (MenuUsers == 1 && llSameGroup(avatar)) || MenuUsers == 2)
527 {
528 saveMuser(avatar, llList2String(Menus, 0 + MENU_NAME), "");
529 doMenu(avatar);
530 }
531}
532
533doMenu(key id)
534{
535 integer f = listFindString(Musers, id, MUSER_STRIDE);
536
537 if (-1 != f)
538 {
539 string menu = llList2String(Musers, f + MUSER_CURRENT);
540 integer m = findMenu(menu);
541
542 if (-1 != m)
543 {
544 integer i = llList2Integer(Menus, m + MENU_AUTH);
545
546 if (i > MenuUsers)
547 i = MenuUsers;
548 if (id == Owner || (i == 1 && llSameGroup(id)) || i == 2)
549 {
550 list entries = llParseStringKeepNulls(llList2String(Menus, m + MENU_ENTRIES), ["\n"], []);
551 list cmds = llParseStringKeepNulls(llList2String(Menus, m + MENU_CMDS), ["\n"], []);
552 string title = menu;
553
554 if ((0 == m) || listFindWord(cmds, "POSE"))
555 {
556 if ("" != Pose)
557 {
558 if (menu == CurrentMenu)
559 title += "\nCurrent pose is " + Pose;
560 else
561 title += "\nCurrent pose is " + Pose + " in menu " + CurrentMenu;
562 }
563 if ("123456789" != Filter)
564 title += "\nPositions are swapped.";
565 }
566 if (listFindWord(cmds, "ADJUST"))
567 {
568 title += "\nAdjusting is o";
569 if (Adjusting) title += "n."; else title += "ff.";
570 }
571 if (listFindWord(cmds, "CHAT"))
572 {
573 title += "\nChat is o";
574 if (Chat) title += "n."; else title += "ff.";
575 }
576 if (listFindWord(cmds, "MENUUSERS"))
577 {
578 title += "\nMenu users are ";
579 if (0 == MenuUsers)
580 title += "OWNER.";
581 if (1 == MenuUsers)
582 title += "GROUP.";
583 if (2 == MenuUsers)
584 title += "ALL.";
585 }
586 llDialog(id, Version + "\n\n" + title,
587 llList2List(entries, -3, -1)
588 + llList2List(entries, -6, -4)
589 + llList2List(entries, -9, -7)
590 + llList2List(entries, -12, -10),
591 Channel);
592 }
593 else
594 {
595 if (i == 1) unauth(id, menu, "group");
596 else unauth(id, menu, "owner");
597 }
598 }
599 else
600 llSay(0, "'" + menu + "' menu not found!");
601 }
602}
603
604readPos(list cards)
605{
606 integer i;
607 integer l = llGetListLength(cards);
608
609 cards = llListSort(cards, 1, TRUE);
610 for (i = 0; i < l; i++)
611 {
612 string card = llList2String(cards, i);
613 list crd = llParseStringKeepNulls(osGetNotecard(card), ["\n"], []);
614 integer m = llGetListLength(crd);
615 integer j;
616
617 llOwnerSay("Reading '" + card + "'.");
618 for (j = 0; j < m; j++)
619 {
620 string data = llList2String(crd, j);
621
622 if (llGetSubString(data, 0, 0) != "/")
623 { // skip comments
624 data = llStringTrim(data, STRING_TRIM);
625 integer ix = llSubStringIndex(data, "{");
626 integer jx = llSubStringIndex(data, "} <");
627
628 if (ix != -1 && jx != -1)
629 {
630 string name = llStringTrim(llGetSubString(data, ix + 1, jx - 1), STRING_TRIM);
631 string ldata = llGetSubString(data, jx + 2, -1);
632 list posrots = llParseString2List(ldata, ["<"], []);
633 string pr = "";
634
635 jx = llGetListLength(posrots);
636 for (ix = 0; ix < jx; ix += 2)
637 pr += "|<" + llStringTrim(llList2String(posrots, ix), STRING_TRIM) + "<"
638 + llStringTrim(llList2String(posrots, ix + 1), STRING_TRIM);
639 savePose(name, "", "", llGetSubString(pr, 1, -1));
640 }
641 }
642 }
643 }
644}
645
646integer findPose(string name)
647{
648 return listFindString(Poses, name, POSE_STRIDE);
649}
650
651savePose(string name, string anim, string exp, string posRot)
652{
653 integer f = findPose(name);
654
655 if (-1 != f)
656 {
657 if ("" == anim)
658 {
659 anim = llList2String(Poses, f + POSE_ANIM);
660 exp = llList2String(Poses, f + POSE_EMOTE);
661 }
662 else if ("" == posRot)
663 posRot = llList2String(Poses, f + POSE_POSROT);
664 Poses = llListReplaceList(Poses, [name, anim, exp, posRot], f, f + POSE_STRIDE - 1);
665 }
666 else
667 Poses += [name, anim, exp, posRot];
668}
669
670stopAnims(key avatar)
671{
672 if (NULL_KEY != avatar)
673 {
674 list anims = llGetAnimationList(avatar);
675 integer l = llGetListLength(anims);
676 integer i;
677
678 for (i = 0; i < l; i++)
679 {
680 string anim = llList2String(anims, i);
681
682 if (anim != "")
683 osAvatarStopAnimation(avatar, anim);
684 }
685 }
686}
687
688checkTicks()
689{
690 if (llGetListLength(Exps))
691 {
692 float dil = llGetRegionTimeDilation(); // Between 0 and 1.
693 float fps = llGetRegionFPS(); // Frames per second, up to 50.
694 integer newLag = (integer) (dil * fps);
695
696 if (llAbs(Lag - newLag) > 9)
697 {
698 if (45 <= newLag) // none
699 Tick = 0.2;
700 else if (35 <= newLag) // little
701 Tick = 0.3;
702 else if (25 <= newLag) // medium
703 Tick = 0.5;
704 else if (15 <= newLag) // lots
705 Tick = 0.7;
706 else // way too much
707 Tick = 1.0;
708 Lag = newLag;
709 }
710 llSetTimerEvent(Tick);
711 }
712 else // There's no expressions running, so just use the "anyone there" timer.
713 llSetTimerEvent(120.0);
714}
715
716string filterIt(string an)
717{
718 list anl = llParseStringKeepNulls(an, ["|"], []);
719 integer l = llGetListLength(anl);
720 integer lf = llStringLength(Filter);
721 integer i;
722 string anr = "";
723
724 for (i = 0; i < l; i++)
725 {
726 if (0 != i)
727 anr += "|";
728 anr += llList2String(anl, ((integer) llGetSubString(Filter, i, i)) - 1);
729 }
730
731 return anr;
732}
733
734rezThing(string thing, string posRot, integer num)
735{
736 integer i = llSubStringIndex(posRot, ">");
737
738 llRezObject(thing,
739 ((vector) llGetSubString(posRot, 0, i)) * RefRot + RefPos,
740 ZERO_VECTOR,
741 llEuler2Rot((vector) llGetSubString(posRot, i + 1, -1) * DEG_TO_RAD) * RefRot,
742 num);
743}
744
745// ball is either the ball number, or a POSES_* flag.
746doPose(string newPose, integer ball, integer pass)
747{
748 list anl;
749 list eml;
750 list prl;
751 integer p = findPose(newPose);
752 integer l = llGetListLength(Balls);
753 integer i;
754 vector newRefPos = llGetPos();
755 rotation newRefRot = llGetRot();
756
757 // A little bit inefficient, but keeps things a little simpler.
758 // Most of the time we pass this through a link message, so other scripts can know about it.
759 // The one time we don't is when we get that link message.
760 if (pass)
761 {
762 llMessageLinked(LINK_SET, MLP_POSE, (string) ball, newPose);
763 if ("" != newPose) // Deal with it anyway if it's the stop pose, otherwise that wont happen.
764 return;
765 }
766 llMessageLinked(LINK_SET, OLD_POSEB, "POSEB", newPose);
767 // The prStr() call below needs to use the old RefPos.
768 newRefPos.z += ((integer) llGetObjectDesc()) / 100.0;
769 if (-1 != p)
770 {
771 if ((POSES_POSE == ball) || (POSES_SWAP == ball) || (0 <= ball))
772 {
773 if ("123456789" == Filter)
774 say("The pose is now '" + newPose + "'.");
775 else
776 say("The pose is now '" + newPose + "' swapped.");
777 }
778 // Filter the data to current SWAP.
779 anl = llParseStringKeepNulls(filterIt(llList2String(Poses, p + POSE_ANIM)), ["|"], []);
780 eml = llParseStringKeepNulls(filterIt(llList2String(Poses, p + POSE_EMOTE)), ["|"], []);
781 prl = llParseStringKeepNulls(filterIt(llList2String(Poses, p + POSE_POSROT)), ["|"], []);
782 }
783
784 Exps = [];
785 i = 0;
786 if (0 <= ball) // A single ball, for when someone sits on it.
787 {
788 i = findBall(ball);
789 l = i + BALL_STRIDE;
790 }
791 for (; i < l; i += BALL_STRIDE)
792 {
793 integer b0 = llList2Integer(Balls, i + BALL_NUM);
794 integer isBall = (0 <= b0);
795 key k = llList2Key(Balls, i + BALL_KEY);
796 key a = llList2Key(Balls, i + BALL_AVATAR);
797 // Find out where the ball / prop is now, and rotation.
798 string bpr = prStr(llDumpList2String(llGetObjectDetails(k, [OBJECT_POS, OBJECT_ROT]), ""));
799
800 if (isBall) // Props are handled in their own script.
801 {
802 integer b1 = ((integer) llGetSubString(LastFilter, b0, b0)) - 1;
803 integer b2 = ((integer) llGetSubString( Filter, b0, b0)) - 1;
804
805 if ((POSES_POSE == ball) || (POSES_SWAP == ball) || (0 <= ball))
806 stopAnims(a);
807 if (-1 != p)
808 {
809 string prn = llList2String(llParseStringKeepNulls(llList2String(Poses, p + POSE_POSROT), ["|"], []), b2);
810 integer ix = llSubStringIndex(prn, ">");
811
812 if (0 > ball)
813 {
814 // Deal with ball movements.
815 integer g = llListFindList(Poses, Pose);
816 integer m = g + POSE_POSROT;
817 string prOld = llList2String(Poses, m);
818 list nprl = llParseString2List(prOld, ["|"], []);
819 string result = llDumpList2String(llListReplaceList(nprl, [bpr], b1, b1), "|");
820
821 // Actually store the balls new position / rotation if it moved.
822 if (result != prOld)
823 {
824 llOwnerSay(" MOVEd ball " + b1 + ".\n\t old [" + prOld + "]\n\t new [" + result + "]");
825 Poses = llListReplaceList(Poses, [result], m, m);
826 }
827 }
828
829 // Deal with animations and expressions.
830 if ((POSES_POSE == ball) || (POSES_SWAP == ball) || (0 <= ball))
831 {
832 list e = llParseStringKeepNulls(llList2String(eml, b0), ["::"], []);
833
834 if (NULL_KEY != a)
835 {
836 string ani = llList2String(anl, b0);
837
838 llMessageLinked(LINK_SET, OLD_ANIM, "" + i + "|" + newPose, a);
839 osAvatarPlayAnimation(a, ani);
840 if (llGetListLength(e))
841 {
842 string exp = llList2String(e, 0);
843
844 if ("" != exp)
845 Exps += [a, exp, llList2Float(e, 1), 0.0];
846 }
847 }
848 }
849 // Move the ball.
850 if (POSES_DUMP != ball)
851 {
852 if (ix != -1)
853 {
854 vector pos = ((vector) llGetSubString(prn, 0, ix)) * newRefRot + newRefPos;
855 rotation rot = llEuler2Rot((vector) llGetSubString(prn, ix + 1, -1) * DEG_TO_RAD) * newRefRot;
856
857 osSetPrimitiveParams(k, [PRIM_POSITION, pos, PRIM_ROTATION, rot]);
858 }
859 }
860 }
861 }
862 }
863 // The rezThing() call below needs the new position.
864 RefPos = newRefPos;
865 RefRot = newRefRot;
866 checkTicks();
867
868 // Delete / create balls as needed.
869 if ((POSES_POSE == ball) || (0 <= ball))
870 {
871 i = 0;
872 p = findMenu(CurrentMenu);
873 if (-1 != p)
874 i = llGetListLength(llParseStringKeepNulls(llList2String(Menus, p + MENU_COLOURS), ["\n"], []));
875 if (BallCount != i)
876 {
877 while (BallCount > i)
878 {
879 --BallCount;
880 stopAnims(llList2Key(Balls, BallCount + BALL_AVATAR));
881 setBall(BallCount, "DIE");
882 p = findBall(BallCount);
883 if (-1 != p)
884 Balls = llListReplaceList(Balls, [], p, p + BALL_STRIDE - 1);
885 }
886
887 while (BallCount < i)
888 {
889 rezThing("~ball", llList2String(prl, BallCount), BallCount);
890 BallCount++;
891 }
892 }
893 }
894 Pose = newPose;
895 LastFilter = Filter;
896}
897
898integer findBall(integer num)
899{
900 integer f = llListFindList(Balls, [num]);
901 integer ix = f / BALL_STRIDE;
902
903 // Round to nearest stride.
904 ix = ix * BALL_STRIDE;
905
906 if ((-1 != f) && (ix == f)) // Sanity check, make sure we found a chan, not something else.
907 return f;
908 else
909 return -1;
910}
911
912integer findBallByID(key id)
913{
914 integer f = llListFindList(Balls, [id]);
915 integer ix = f / BALL_STRIDE;
916
917 // Round to nearest stride.
918 ix = ix * BALL_STRIDE;
919
920 if ((-1 != f) && ((ix + BALL_KEY) == f)) // Sanity check, make sure we found a UUID, not something else.
921 return f - BALL_KEY;
922 else
923 return -1;
924}
925
926saveBall(integer num, key id, key avatar)
927{
928 integer f = findBall(num);
929
930 if (-1 == f)
931 Balls += [num, id, avatar];
932 else
933 Balls = llListReplaceList(Balls, [num, id, avatar], f, f + BALL_STRIDE - 1);
934}
935
936key getBall(integer num)
937{
938 integer f = findBall(num);
939
940 if (-1 != f)
941 return llList2Key(Balls, f + BALL_KEY);
942 else
943 return NULL_KEY;
944}
945
946setBall(integer num, string cmd)
947{
948 key k = getBall(num);
949
950 if (NULL_KEY != k)
951 osMessageObject(k, cmd);
952 else
953 llOwnerSay("Missed ball command - " + cmd);
954}
955
956setBalls(string cmd)
957{
958 integer i;
959
960 for (i = 0; i < BallCount; ++i)
961 setBall(i, cmd);
962}
963
964saveMuser(key avatar, string current, string stack)
965{
966 integer f = listFindString(Musers, avatar, MUSER_STRIDE);
967
968 if (-1 == f)
969 Musers += [avatar, current, stack];
970 else
971 Musers = llListReplaceList(Musers, [avatar, current, stack], f, f + MUSER_STRIDE - 1);
972}
973
974// return TRUE if caller should doMenu()
975integer handleCmd(key id, string button, integer isMenu)
976{
977 string cmd = button;
978 string data = cmd;
979 string menu = CurrentMenu;
980 list lst;
981 integer m;
982 integer f;
983 integer i;
984
985 if (isMenu)
986 {
987 f = listFindString(Musers, id, MUSER_STRIDE);
988 if (-1 != f)
989 {
990 menu = llList2String(Musers, f + MUSER_CURRENT);
991 m = findMenu(menu); // This was already checked before it was stuffed into Musers.
992 lst = llParseStringKeepNulls(llList2String(Menus, m + MENU_CMDS), ["\n"], []);
993 i = llListFindList(llParseStringKeepNulls(llList2String(Menus, m + MENU_ENTRIES), ["\n"], []), [button]);
994 data = llList2String(lst, i);
995 cmd = data;
996
997 i = llList2Integer(Menus, m + MENU_AUTH);
998 if (i > MenuUsers)
999 i = MenuUsers;
1000 if (!(id == Owner || (i == 1 && llSameGroup(id)) || i == 2))
1001 return FALSE;
1002 }
1003 }
1004
1005 i = llSubStringIndex(data, " ");
1006 if (-1 != i)
1007 {
1008 cmd = llGetSubString(data, 0, i - 1);
1009 data = llStringTrim(llGetSubString(data, i + 1, -1), STRING_TRIM);
1010 }
1011 else
1012 data = "";
1013
1014 if ("POSE" == cmd)
1015 {
1016 if (isMenu) CurrentMenu = menu;
1017 doPose(data, POSES_POSE, TRUE);
1018 }
1019 else if ("TOMENU" == cmd)
1020 {
1021 if ("" == data)
1022 {
1023 i = m;
1024 m = TRUE;
1025 }
1026 else
1027 {
1028 i = findMenu(data);
1029 m = FALSE;
1030 }
1031 if (-1 != i)
1032 {
1033 if (isMenu)
1034 saveMuser(id, data, menu + "|" + llList2String(Musers, f + MUSER_STACK));
1035 else
1036 {
1037 if (m)
1038 {
1039 if (Redo) doMenu(id);
1040 }
1041 else
1042 CurrentMenu = data;
1043 }
1044 }
1045 }
1046 else if (("BACK" == cmd) && isMenu)
1047 {
1048 lst = llParseStringKeepNulls(llList2String(Musers, f + MUSER_STACK), ["|"], []);
1049 saveMuser(id, llList2String(lst, 0), llDumpList2String(llDeleteSubList(lst, 0, 0), "|"));
1050 }
1051 else if ("SWAP" == cmd)
1052 {
1053 data = llList2String(Menus, m + MENU_SWAPS);
1054 if ((menu == CurrentMenu) || (llList2String(Menus, findMenu(CurrentMenu) + MENU_SWAPS) == data))
1055 {
1056 // The first one is always the normal order, so users don't set that in .MENUITEM cards.
1057 list swaps = ["123456789"] + llParseStringKeepNulls(data, ["\n"], []);
1058
1059 ++ThisSwap;
1060 if (llGetListLength(swaps) <= ThisSwap)
1061 ThisSwap = 0;
1062 LastFilter = Filter;
1063 Filter = llList2String(swaps, ThisSwap);
1064 doPose(Pose, POSES_SWAP, TRUE);
1065 }
1066 else
1067 llSay(0, "The current pose is from another menu with a different SWAP command, cant swap.");
1068 }
1069 else if ("ADJUST" == cmd)
1070 {
1071 Adjusting = ! Adjusting;
1072 f = llGetListLength(Balls);
1073
1074 for (i = 0; i < f; i += BALL_STRIDE)
1075 {
1076 integer b = llList2Integer(Balls, i + BALL_NUM);
1077 integer isBall = (0 <= b);
1078 key k = llList2Key(Balls, i + BALL_KEY);
1079 key a = llList2Key(Balls, i + BALL_AVATAR);
1080 vector c = getBallColour(b);
1081
1082 if (Adjusting)
1083 {
1084 if (NULL_KEY == a)
1085 osSetPrimitiveParams(k, [PRIM_COLOR, ALL_SIDES, c, 1.0,
1086 PRIM_SIZE, <0.2, 0.2, 0.2>, PRIM_TEXT, "Adjust", <1.0, 1.0, 1.0>, 1.0]);
1087 else
1088 osSetPrimitiveParams(k, [PRIM_COLOR, ALL_SIDES, c, 0.15,
1089 PRIM_SIZE, <0.1, 0.1, 5.0>, PRIM_TEXT, "Adjust", <1.0, 1.0, 1.0>, 1.0]);
1090 }
1091 else
1092 {
1093 if (NULL_KEY == a)
1094 osSetPrimitiveParams(k, [PRIM_COLOR, ALL_SIDES, c, 1.0,
1095 PRIM_SIZE, <0.2, 0.2, 0.2>, PRIM_TEXT, "Love", <1.0, 1.0, 1.0>, 1.0]);
1096 else
1097 osSetPrimitiveParams(k, [PRIM_COLOR, ALL_SIDES, c, 0.0,
1098 PRIM_SIZE, <0.01, 0.01, 0.01>, PRIM_TEXT, "", <1.0, 1.0, 1.0>, 1.0]);
1099 }
1100 }
1101 }
1102 else if ("CHAT" == cmd)
1103 {
1104 Chat = !Chat;
1105 if (Chat) llSay(0, button + " ON"); else llSay(0, button + " OFF");
1106 }
1107 else if ("MENUUSERS" == cmd)
1108 {
1109 MenuUsers++;
1110 if (3 <= MenuUsers) MenuUsers = 0;
1111 say(button + llList2String(["OWNER", "GROUP", "ALL"], MenuUsers) + " can use the menus.");
1112 }
1113 else if ("LINKMSG" == cmd)
1114 { // Send LM to a non-MLP script.
1115 i = llListFindList(LMButtons, [button]);
1116 if (i != -1)
1117 {
1118 lst = llCSV2List(llList2String(LMParms, i));
1119 llMessageLinked(
1120 llList2Integer(lst, 1), // destination link number
1121 llList2Integer(lst, 2), // 'num' arg
1122 llList2String(lst, 3), // 'str' arg
1123 id); // key arg
1124 if (llList2Integer(lst,0)) // inhibit remenu?
1125 return FALSE; // yes, bug out
1126 }
1127 }
1128 else if ("SOUND" == cmd)
1129 {
1130 i = llListFindList(Sounds, [button]);
1131 if (-1 != i)
1132 llPlaySound(llList2String(Sounds, i + SOUND_INV), 1.0);
1133 }
1134 else if ("TOOLS" == cmd)
1135 {
1136 if (llGetInventoryType("~MLP lite tools") == INVENTORY_SCRIPT)
1137 {
1138 llMessageLinked(LINK_SET, MLP_UNKNOWN, cmd, Owner);
1139 return FALSE;
1140 }
1141 else
1142 llOwnerSay("Please install the '~MLP lite tools' script for this to work.");
1143 }
1144 else if ("REDECORATE" == cmd || "RELOAD" == cmd || "RESET" == cmd || "RESTART" == cmd || "STOP" == cmd)
1145 {
1146 say(button);
1147 stop();
1148 LoadMenu = ("RESET" == cmd);
1149 LoadPos = ("RELOAD" == cmd);
1150 LoadProp = ("REDECORATE" == cmd);
1151 if ("RESTART" == cmd)
1152 llResetScript();
1153 else if ("STOP" == cmd)
1154 return FALSE;
1155 else
1156 state load;
1157 }
1158 else
1159 {
1160 if (isMenu)
1161 llOwnerSay("Unknown menu command '" + cmd + "' from -\n\t" + button);
1162 else
1163 llOwnerSay("Unknown command '" + cmd + "' from -\n\t" + button);
1164 llMessageLinked(LINK_SET, MLP_UNKNOWN, cmd + " " + data, id);
1165 return FALSE;
1166 }
1167 llMessageLinked(LINK_SET, MLP_CMD, cmd + " " + data, id);
1168 return TRUE;
1169}
1170
1171
1172default
1173{
1174 state_entry()
1175 {
1176 Owner = llGetOwner();
1177 Channel = (integer) ("0x" + llGetSubString((string) llGetKey(), -4, -1));
1178 setRunning(FALSE);
1179 LoadMenu = TRUE;
1180 LoadPos = TRUE;
1181 LoadProp = TRUE;
1182 llSay(0, "OFF (touch to switch on).");
1183 }
1184
1185 on_rez(integer arg)
1186 {
1187 if (ReloadOnRez)
1188 llResetScript();
1189 }
1190
1191 touch_start(integer i)
1192 {
1193 User0 = llDetectedKey(0);
1194 state load;
1195 }
1196
1197 // Waits for another script to send a link message.
1198 // This is needed coz child prims only send touch events to root prims.
1199 // So if this script is in a child prim, it can only be touched by touching that prim.
1200 link_message(integer sender_num, integer num, string str, key id)
1201 {
1202 if (str == "PRIMTOUCH" && id == Owner)
1203 state load;
1204 }
1205
1206 changed(integer change)
1207 {
1208 if (change & CHANGED_OWNER && Owner != llGetOwner())
1209 llResetScript();
1210 }
1211}
1212
1213state load
1214{
1215 state_entry()
1216 {
1217 float then = llGetTime();
1218 float now = 0.0;
1219 float total = 0.0;
1220 list menuCards = [];
1221 list posCards = [];
1222 string item;
1223 string e;
1224 string c;
1225 integer i = llGetInventoryNumber(INVENTORY_NOTECARD);
1226 integer l;
1227 integer PosCount;
1228 integer PropCount;
1229
1230 llSay(0, "STARTING, please wait...");
1231 llListen(Channel, "", NULL_KEY, "");
1232 setRunning(TRUE);
1233 if (LoadProp)
1234 llMessageLinked(LINK_SET, MLP_UNKNOWN, "LOADPROPS", NULL_KEY);
1235 while (i-- > 0)
1236 {
1237 item = llGetInventoryName(INVENTORY_NOTECARD, i);
1238 if (llSubStringIndex(item, ".MENUITEMS") == 0)
1239 menuCards += (list) item;
1240 if (llSubStringIndex(item, ".POSITIONS") == 0)
1241 posCards += (list) item;
1242 }
1243
1244 if (LoadMenu && LoadPos) // They both fiddle with Poses, but only need to clear it when loading both.
1245 Poses = [];
1246 if (LoadMenu)
1247 {
1248 Menus = [];
1249 Sounds = [];
1250 LMButtons = [];
1251 LMParms = [];
1252 MaxBalls = 1;
1253 MenuUsers = 0;
1254 Chat = TRUE;
1255 Redo = TRUE;
1256 ReloadOnRez = FALSE;
1257 Pose = "";
1258 readMenu(menuCards);
1259 // Place any otherwise unplaced menus in the main menu.
1260 l = llGetListLength(ToMenus);
1261 // Skipping the first one, which should be the main menu.
1262 for (i = 1; i < l; i++)
1263 {
1264 item = llList2String(ToMenus, i);
1265 if (-1 == llListFindList(SeenMenus, item))
1266 {
1267 e += "\n" + item;
1268 c += "\nTOMENU " + item;
1269 }
1270 }
1271 ToMenus = [];
1272 SeenMenus = [];
1273 e += "\n" + llList2String(Menus, MENU_ENTRIES);
1274 c += "\n" + llList2String(Menus, MENU_CMDS);
1275 Menus = llListReplaceList(Menus,
1276 [llGetSubString(e, 1, -1), llGetSubString(c, 1, -1)], MENU_ENTRIES, MENU_CMDS);
1277 now = llGetTime();
1278 total += now - then;
1279 llOwnerSay("Loaded " + (string) (llGetListLength(Menus) / MENU_STRIDE) + " menus in "
1280 + (string) (now - then) + " seconds.");
1281 then = now;
1282 touched(User0);
1283 }
1284
1285 if (LoadPos)
1286 {
1287 readPos(posCards);
1288 PosCount = llGetListLength(Poses) / POSE_STRIDE;
1289 now = llGetTime();
1290 total += now - then;
1291 llOwnerSay("Loaded " + (string) (llGetListLength(Poses) / POSE_STRIDE) + " positions in "
1292 + (string) (now - then) + " seconds.");
1293 then = now;
1294 }
1295 LoadMenu = TRUE;
1296 LoadPos = TRUE;
1297 LoadProp = TRUE;
1298 llSay(0, Version + ": READY in " + (string) total + " seconds.");
1299 // Give any listen event time to fire before we switch state.
1300 llSetTimerEvent(0.1);
1301 }
1302
1303 changed(integer change)
1304 {
1305 if ((change & CHANGED_OWNER) && Owner != llGetOwner())
1306 llResetScript();
1307 }
1308
1309 listen(integer channel, string name, key id, string button)
1310 {
1311 if (handleCmd(id, button, TRUE) && Redo) doMenu(id);
1312 }
1313
1314 on_rez(integer arg)
1315 {
1316 if (ReloadOnRez)
1317 llResetScript();
1318 }
1319
1320 timer()
1321 {
1322 state on;
1323 }
1324}
1325
1326state re_on
1327{
1328 state_entry()
1329 {
1330 state on;
1331 }
1332}
1333
1334state on
1335{
1336 state_entry()
1337 {
1338 llListen(Channel, "", NULL_KEY, "");
1339 }
1340
1341 on_rez(integer arg)
1342 {
1343 if (ReloadOnRez)
1344 llResetScript();
1345 BallCount = 0;
1346 setRunning(TRUE);
1347 }
1348
1349 changed(integer change)
1350 {
1351 if ((change & CHANGED_OWNER) && Owner != llGetOwner())
1352 llResetScript();
1353 }
1354
1355 // Handle messages from balls and props.
1356 dataserver(key queryId, string str)
1357 {
1358 list data = llParseString2List(str, ["|"], []);
1359 string cmd = llList2String(data, 0);
1360 if ("ALIVE" == cmd)
1361 {
1362 integer ball = llList2Integer(data, 1);
1363 integer isBall = (0 <= ball);
1364
1365 saveBall(ball, queryId, NULL_KEY);
1366 if (isBall)
1367 {
1368 vector c = getBallColour(ball);
1369
1370 osSetPrimitiveParams(queryId, [PRIM_COLOR, ALL_SIDES, c, 1.0,
1371 PRIM_NAME, "~ball" + ball, PRIM_TEXT, "Love", <1.0, 1.0, 1.0>, 1.0
1372// OpenSim doesn't support this, so the ~ball script does it.
1373// PRIM_SIT_TARGET, TRUE, <0.0, 0.0, -0.1>, ZERO_ROTATION
1374 ]);
1375 }
1376 llMessageLinked(LINK_SET, MLP_CMD, str, queryId);
1377 }
1378 else if ("AVATAR" == cmd)
1379 {
1380 integer b = findBallByID(queryId);
1381 integer ball = llList2Integer(Balls, b + BALL_NUM);
1382 key a = llList2Key(data, 2);
1383
1384 if (-1 != b)
1385 {
1386 key id = llList2Key(Balls, b + BALL_AVATAR);
1387 vector c = getBallColour(ball);
1388 if (NULL_KEY == a)
1389 {
1390 llMessageLinked(LINK_SET, OLD_STANDS, (string) ball, id);
1391 osSetPrimitiveParams(queryId, [PRIM_COLOR, ALL_SIDES, c, 1.0,
1392 PRIM_SIZE, <0.2, 0.2, 0.2>, PRIM_TEXT, "Love", <1.0, 1.0, 1.0>, 1.0]);
1393 stopAnims(id);
1394 }
1395 else
1396 {
1397 llMessageLinked(LINK_SET, OLD_SITS, (string) ball + "|" + Pose, id);
1398 osSetPrimitiveParams(queryId, [PRIM_COLOR, ALL_SIDES, c, 0.0,
1399 PRIM_SIZE, <0.01, 0.01, 0.01>, PRIM_TEXT, "", <1.0, 1.0, 1.0>, 1.0]);
1400 }
1401 Balls = llListReplaceList(Balls, [a], b + BALL_AVATAR, b + BALL_AVATAR);
1402 if (NULL_KEY != a)
1403 doPose(Pose, ball, TRUE);
1404 }
1405 llMessageLinked(LINK_SET, MLP_CMD, str, queryId);
1406 }
1407 }
1408
1409 touch_start(integer num)
1410 {
1411 integer i;
1412
1413 for (i = 0; i < num; i++)
1414 touched(llDetectedKey(i));
1415 }
1416
1417 listen(integer channel, string name, key id, string button)
1418 {
1419 if (handleCmd(id, button, TRUE) && Redo) doMenu(id);
1420 }
1421
1422 link_message(integer from, integer num, string str, key id)
1423 {
1424 if ("PRIMTOUCH" == str)
1425 touched(id);
1426 else if (OLD_CMD == num)
1427 handleCmd(id, str, FALSE);
1428 else if (TOOL_DATA == num)
1429 {
1430 if ("" == str)
1431 {
1432 list data = [];
1433 integer i = FALSE;
1434
1435 if ("Menus" == id)
1436 {
1437 data = Menus;
1438 i = TRUE;
1439 }
1440 else if ("Poses" == id)
1441 {
1442 data = Poses;
1443 i = TRUE;
1444 }
1445 else if ("Sounds" == id)
1446 {
1447 data = Sounds;
1448 i = TRUE;
1449 }
1450 if (i)
1451 llMessageLinked(from, num, LIST_SEP + llDumpList2String(data, LIST_SEP), id);
1452 }
1453 }
1454 else if (MLP_POSE == num)
1455 {
1456 if (NULL_KEY == id)
1457 id = Pose;
1458 doPose((string) id, (integer) str, FALSE);
1459 }
1460 else if (MLP_DATA == num)
1461 {
1462 list data = llParseStringKeepNulls(llGetSubString(str, llStringLength(LIST_SEP), -1), [LIST_SEP], []);
1463
1464 if ("Menus" == id)
1465 Menus = data;
1466 else if ("Poses" == id)
1467 Poses = data;
1468 else if ("Sounds" == id)
1469 Sounds = data;
1470// TODO - Do we need to update anything else to match the new data now?
1471 }
1472 else if (((0 == num) && ("POSEB" == str)) || ((1 == num) && ("STOP" == str)))
1473 ; // Old messages we can ignore.
1474 else if ((MLP_CMD == num) || (MLP_UNKNOWN == num)
1475 || (OLD_SITS == num) || (OLD_STANDS == num) || (OLD_ANIM == num))
1476 ; // Ignore these, they are for others.
1477 else
1478 llOwnerSay("Unknown link message " + num + ", " + str);
1479 }
1480
1481 no_sensor()
1482 {
1483 if (People)
1484 {
1485 People = 0;
1486 return;
1487 }
1488 People = 0;
1489 llShout(0, "No one here, shutting down.");
1490 llResetScript();
1491 }
1492
1493 sensor(integer num)
1494 {
1495 list t = [];
1496
1497 // Subtle point here, leaves People = num, which we want for later.
1498 for (People = 0; People < num; People++)
1499 {
1500 integer f = listFindString(Musers, llDetectedKey(People), MUSER_STRIDE);
1501
1502 if (-1 != f)
1503 t += llList2List(Musers, f, f + MUSER_STRIDE - 1);
1504 }
1505 Musers = t;
1506 }
1507
1508 timer()
1509 {
1510 float now = llGetTime();
1511 integer l = llGetListLength(Exps);
1512 integer i;
1513
1514 for (i = 0; i < l; i += EXP_STRIDE)
1515 {
1516 if (llList2Float(Exps, i + EXP_NEXT) <= now)
1517 {
1518 key avatar = llList2Key(Exps, i + EXP_AVATAR);
1519 string exp = llList2String(Exps, i + EXP_EXP);
1520
1521 if (exp == "SLEEP")
1522 {
1523 osAvatarStopAnimation(avatar, "express_disdain");
1524 osAvatarPlayAnimation(avatar, "express_disdain");
1525 osAvatarStopAnimation(avatar, "express_smile");
1526 osAvatarPlayAnimation(avatar, "express_smile");
1527 }
1528 else if (exp != "")
1529 {
1530 osAvatarStopAnimation(avatar, exp);
1531 osAvatarPlayAnimation(avatar, exp);
1532 }
1533 Exps = llListReplaceList(Exps, [now + llList2Float(Exps, i + EXP_TIME)], i + EXP_NEXT, i + EXP_NEXT);
1534 }
1535 }
1536
1537 if (now >= 120.0)
1538 {
1539 // Yes, I know, this might screw with the expressions timing, think we can live with that though.
1540 // LSL timing isn't very precise anyway, and makes things less robotic every couple of minutes.
1541 llResetTime();
1542 setBalls("LIVE");
1543 llSensor("", NULL_KEY, AGENT, 6.0, PI);
1544 for (i = 0; i < l; i += EXP_STRIDE)
1545 Exps = llListReplaceList(Exps, [llList2Float(Exps, i + EXP_TIME)], i + EXP_NEXT, i + EXP_NEXT);
1546 }
1547 checkTicks();
1548 }
1549}
1550