aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
authorDave Seikel2018-05-20 23:17:24 +1000
committerDave Seikel2018-05-20 23:17:24 +1000
commit9dd74045373c39d8a35a59f3c2801b0348a44a9b (patch)
treeb3cdc4ba800ffa44b98291ce12a0eb1c9ad809e4
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.
-rw-r--r--.MENUITEMS.txt18
-rw-r--r--LICENSE2
-rw-r--r--README.md532
-rw-r--r--~MLP lite for OpenSim.lsl1550
-rw-r--r--~MLP lite props.lsl354
-rw-r--r--~MLP lite tools.lsl595
-rw-r--r--~ball.lsl63
7 files changed, 3113 insertions, 1 deletions
diff --git a/.MENUITEMS.txt b/.MENUITEMS.txt
new file mode 100644
index 0000000..926e10f
--- /dev/null
+++ b/.MENUITEMS.txt
@@ -0,0 +1,18 @@
1// NORELOAD // inhibit reload on rez -- useful for worn items
2
3MENU MAIN MENU | ALL
4TOMENU EXTRAS>>
5
6MENU EXTRAS>> | ALL
7ADJUST
8CHAT Chat Info | ON //switches chat info: ON/OFF (set default)
9TOMENU OPTIONS>>
10STOP
11BACK
12
13MENU OPTIONS>> | OWNER //this menu can be accessed by the OWNER only (other options: GROUP / ALL)
14MENUUSERS MenuUsers | ALL //switches who can access menus: ALL/GROUP/OWNER (set default)
15TOOLS TOOLS>> // Show the Tools menu, if it's installed.
16RESTART ShutDown!
17BACK
18
diff --git a/LICENSE b/LICENSE
index d471360..ca922b2 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
1BSD 3-Clause License 1BSD 3-Clause License
2 2
3Copyright (c) 2018, David Seikel 3Copyright (c) 2018, onefang rejected
4All rights reserved. 4All rights reserved.
5 5
6Redistribution and use in source and binary forms, with or without 6Redistribution and use in source and binary forms, with or without
diff --git a/README.md b/README.md
index 992f454..8c9ab96 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,534 @@
1# MLP-lite 1# MLP-lite
2A lighter version of the MLP scripts, built for OpenSim. 2A lighter version of the MLP scripts, built for OpenSim.
3
4MLP lite v3.0 for OpenSim is almost a complete rewrite by onefang
5rejected, of the venerable MLP scripts. It uses OpenSim specific
6functions, so wont work in Second Life. It might not work on some
7OpenSim grids or sims, due to the use of OpenSim specific "high threat
8level" functions. Test it first before putting it in anything important.
9
10The main justification for this rewrite is that I have about a dozen
11OpenSim sims, some with dozens of copies of MLP running. So this
12variation uses some OpenSim specific LSL functions, tries to use less
13resources, be quicker, and automatically shuts down when no one is
14around. Some functions where removed, hence the "lite" in the name, most
15of these seem to not be used, or useful, or just don't get used in my
16sims. Note that I've not extensively tested any of this stuff yet. It's
17not a huge improvement, but when you have dozens of the things, every
18little bit helps. And then I added more features. lol
19
20Note that some of the OpenSim functions used are what OpenSim classes as
21"high threat level". More about that below. This means that these
22scripts wont work without some tweaking of your grid / sims
23configuration. This has only been tested in my hacked up version of
24OpenSim 0.8.2, which removes a lot of the insane threat levels and script
25delays, so anything I say about speed improvements might not apply. I
26did at least remove script delays in both LL and OS functions. If
27nothing works for you, this is likely why, and you should go back to the
28old scripts. Test it first before upgrading anything that's not full
29perm.
30
31Based on the original MLP - MULTI-LOVE-POSE V1.2 - Copyright (c) 2006, by
32Miffy Fluffy (BSD License). This code has bounced around Second Life and
33OpenSim for over a decade, with various people working on it. I can't
34compile a complete list, as I have no idea of the complete history of the
35versions I have received. The names I do know coz they where in the
36source code (in no particular order) - Miffy Fluffy, Learjeff Innis, Jez
37Ember, Liz Silverstein, Zonax Delorean, Purrcat Miranda, and Kokoro
38Fasching. Apologies to anyone who's code is in this variation and I
39haven't mentioned you. It's likely that some or all of the code written
40by these people is no longer in this variation. Thanks to you all.
41
42This variation is still BSD licensed. The text of the BSD license wasn't
43included in any of the versions I have, but it's a simple and standard
44license, you should easily find copies all over the Internet, or a
45simple search. The versions I have seen don't specify which variation of
46the BSD license, so I wont either. GitHub insists on a license file, so
47I went with 3 clause BSD.
48
49The next two sections of this document is mostly copied from the .readme
50file I got with the latest MLP versions I had laying around.
51
52================================================
53
54FEATURES:
55- Put all your poseball animations into one object (110 pairs or even more
56should be no problem).
57- Create submenus for each category of poses, for instance: "Solo,
58Boy-Girl, Girl-Girl, Dance, 3some, 4some"
59- Shows up to nine poseballs depending on the submenu you select
60- Positions can be adjusted and saved into memory or notecard.
61- Portable, can be worn and used everywhere
62- Option to adjust height offset (Z) of all poses at once (for different
63persons/locations).
64- 15 poseball colours:
65 PINK, BLUE, PINK2, BLUE2, GREEN, MAGENTA, RED, ORANGE, WHITE, BLACK,
66 YELLOW, CYAN, RED2, TEAL, GREEN2
67
68OPERATION:
69- Click the MLP object to switch it on.
70- When everything has loaded the main menu will appear.
71- Select a submenu containing poses, and select a pose. Pose balls will
72appear.
73- Sit on your pose ball (Right-click - LOVE).
74- To remove the balls, select 'EXTRAS>>' then 'STOP', or just walk away,
75it will shut down automatically when no one is around.
76
77TO ADJUST POSITIONS:
78You can adjust the poses to fit your own avatar and those you share MLP
79with.
80- Select a pose you want to adjust and sit on the balls.
81- Select 'EXTRAS>> ' then 'ADJUST'. This changes the balls into
82translucent beams.
83- Right-click a beam, select Edit and adjust the position (Shift-click to
84select more than one).
85- Position the balls anywhere within the sim.
86- 'EXTRAS>>', 'OPTIONS>>', then 'Save Pos' stores the positions into a
87new notecard.
88
89TO MAKE A BACKUP:
90Saved positions are stored in memory but are not permanent. They are
91lost on script error (See "Script run-time error / Stack-Heap Collision"
92below). They are also lost on shutdown/startup, or "Pos Reset". To
93back the positions up more permanently you have to copy them into a
94new .POSITIONS notecard:
95- Select 'EXTRAS>>', 'OPTIONS>>', then 'Save Pos' .
96All original .POSITIONS* cards are copied to .backup.POSITIONS* cards.
97Prop positions are saved to .PROPS, so the same applies.
98
99Note: After changing any *.POSITIONS* files, use the 'Pos Reset' command
100to verify your changes, if desired. This also helps to avoid losing
101changes due to Stack-Heap collisions.
102
103TO ADJUST HEIGHT OFFSET (Z):
104Select 'EXTRAS>>', 'OPTIONS>>', then'Height>>' and click the 'Z'-buttons,
105this will adjust the height for all poses. Note: the offset height is
106stored in the objects Description, so any descriptions will be replaced.
107
108TO ADD POSES:
109Copy all animations into the MLP object (if you want to use existing pose
110balls, open them to rip their animations). Note: you can use any object
111as MLP, just copy the MLP contents in the object of your choice. Open the
112.MENUITEMS and add the animations:
113
114POSE name | animation1 | animation2 ...
115
116The changes will become active after MLP is (re)started. Use the 'Menu
117Reset' command after changing
118*.MENUITEMS* files.
119
120To give an expression to an animation, add one of the following suffixes
121to the anim name in the POSE line. (Just add it to the POSE config line,
122don't change the anim name.)
123
124 Suffix / expression
125 * open mouth
126 ::1 open mouth
127 ::2 surprise
128 ::3 tongue out
129 ::4 smile
130 ::5 toothsmile
131 ::6 wink
132 ::7 cry
133 ::8 kiss
134 ::9 laugh
135 ::10 disdain
136 ::11 repulsed
137 ::12 anger
138 ::13 bored
139 ::14 sad
140 ::15 embarrassed
141 ::16 frown
142 ::17 shrug
143 ::18 afraid
144 ::19 worry
145 ::20 sleeping (combination of disdain and smile, closest I could find)
146
147To make the expression happen periodically rather than constantly, add
148another extension and the period (in seconds). This is mostly needed for
149those expressions that only last a short time. For example, to use MyAnim
150with open mouth every 5.5 seconds:
151
152 POSE Mypose | MyAnim::1::5.5
153
154TO ADD SOUNDS (buttons to play sounds), in a menu (just like a POSE
155button), add a line like this:
156
157 SOUND She moans | female-moan
158
159where "She moans" will be the button label, and "female-moan" is the
160sound to play, which must be in object inventory. For sounds in menus
161with poses (rather than in a menu specifically for sounds) I recommend
162you begin the pose name with "☊" (which looks a bit like headphones, the
163best I could find for the purpose). This serves as a clue to the user
164that the button plays a sound.
165
166TO CREATE ANIMATION SETS (menus):
167Create .MENUITEMS.xxx and .POSITIONS.xxx files (where xxx is whatever you
168want) and put the corresponding menu configs and poses in them. This
169way you can deliver a bed with folders of add-on menus so the customer
170can choose what types of anims they want to add to the menu. Note that
171you get at most 12 top menu choices. The scripts read the .POSITIONS
172files in alphabetical order (and
173.POSITIONS must be first).
174
175This also allows you to sell furniture with "enhancement packs", which
176are simply collections of .MENUITEMS.xxx, .POSITIONS.xxx, and the
177associated animations for the customer to drop into the furniture.
178Customers can easily select furniture appearance and pose bundles
179independently!
180
181SWAPPING POSES:
182Each menu can have a swap command:
183
184 SWAP | 21
185
186Would be the default. Note that this means before you hit the SWAP
187button, the balls will be in the same order as listed in the MENU and
188POSE commands, after they would be reversed. SWAP always assumes
189the unswapped condition is the order things are listed. So 21 in the
190above command means swap the first two balls.
191
192The default for more than two balls is to just fill out both strings with
193the remaining integers:
194
195 SWAP | 213456789
196
197This might be used to swap the last two balls only:
198
199 SWAP | 132
200
201Any number of swap patterns can be used:
202
203 SWAP | 132 | 321 | 312
204
205You can also use a different name for the button:
206
207 SWAP switch men | 213
208
209Also, the original one should be supported, that uses the default:
210
211 SWAP
212
213ACCESS TO THE MAIN MENU:
214The owner is the only one who can shutdown, in all cases. Anyone
215can start it.
216- if 'MenuUser' is set to OWNER: the owner is the only one who can access
217the menus
218- if 'MenuUser' is set to ALL: anyone can access the MAIN MENU
219- if 'MenuUser' is set to GROUP: members of the same Group as the MLP
220object can access the
221MAIN MENU (the MLP Group can set by right-clicking MLP and selecting
222Edit, More >> General tab - Group: Set) Note: even if "MenuUser" is set
223to ALL or GROUP, individual SUBMENUS can still be blocked (you can
224define access for each submenu in .MENUITEMS, see examples in
225.MENUITEMS).
226
227PORTABLE USE:
228Attach the object to the HUD, you can use it's default shape and colour
229for clickable bar on one of the edges of your screen (to move HUD
230position: Right-click - Edit), you can edit
231colour/transparency/size/position. Adjust the height offset (Z). Note:
232the balls will appear relative to the initial MLP position (to reset
233where the balls appear, press STOP to remove the balls, and reselect a
234submenu to rez them again).
235
236================================================
237
238FAQ for END USERS:
239
240- Will my animations be lost if I lose a poseball?
241
242No. The animations are not placed the balls, they remain in the main MLP
243object. Don't worry about the poseballs, they are copies of the one in
244the MLP object. A balls will commit suicide if left behind somewhere
245(the MLP object needs to be within the same sim).
246
247- Sometimes notecards or scripts won't open for editing, why?
248
249If the MLP object contains many things, access to it's contents can be
250slow, just keep waiting until it's all loaded. Can take over a minute
251sometimes.
252
253- "Script run-time error / Stack-Heap Collision"?
254
255Right-click/Edit the object, and use SL menu: "Tools -> Reset Scripts in
256Selection" to reset. Any saved positions that were not backed up in
257.POSITIONS files are lost, so if your furniture has lots of poses (over
25850) and you save positions, be sure to back up regularly. Use OPTIONS ->
259Pos Reset after changing .POSITIONS* files.
260
261FAQ for those who edit *.MENUITEMS files:
262
263- "Script run-time error / Stack-Heap Collision"?
264
265After a restart, this is a clue that there are too many items in
266*.MENUITEMS* or *.POSITIONS* files. Trim the menu.
267
268- My new menu appears on the main page, rather than as a submenu where I
269configured it. Why?
270
271Most likely, you named it differently in the MENU line versus the TOMENU
272line. When MPL sees a MENU line, it looks for the same name in a TOMENU
273line.
274
275================================================
276
277UPGRADE GUIDE:
278
279For the sake of these instructions, let's say you want to upgrade the
280scripts in an object called "My Old Bed", this is what you would do.
281These instructions assume that everything has copy permission.
282
283First rez My Old Bed in world somewhere. If it is already in world, take
284a copy, in case things don't work out and you want to revert to the old
285scripts. You could also move the scripts and things you are about to
286replace to a new folder, so you can put things back the way they where
287before. Or just ignore any backups and cross your fingers.
288
289Remove the old MLP scripts, they will be named ~memory, ~menu, ~menucfg,
290~pos, ~pose, ~poser, ~poser 1, ~poser 2, ~poser 3, ~poser 4, ~poser 5,
291~run, and you might also have optional scripts ~props, ~sequencer, and
292~timer. Also remove the ~ball object. There may be another script, I
293have never seen one, that deals with PRIMTOUCH, my guess is it might be
294called ~primtouch. Leave it alone, this new script should be compatible
295with it. Since I have never seen that script, nor needed it, I dunno
296for sure.
297
298Drag the "~MLP lite for OpenSim" script into My Old Bed, and the new
299~ball object. You should see "My Old Bed: OFF (touch to switch on)" in
300chat.
301
302My Old Bed might have some props in it, they will be objects other than
303~ball. In typical examples I have seen there is a pillow, so lets use
304that as an example. Drag pillow out of My Old Bed and into your
305inventory, into the same folder you are backing up everything else.
306Remove pillow from My Old Bed. Drag pillow onto the ground, edit it, and
307remove the ~prop script. Replace that script with the new ~ball script.
308Take pillow, then drag it into My Old Bed. Add the "~MLP lite props"
309script to My Old Bed.
310
311You may want to update your .MENUITEMS, or just leave them as is. Look
312at the examples for how it is now recommended to have them setup. If you
313do this take care of the OPTIONS menu, which is now called OPTIONS>> in
314this new .MENUITEMS.
315
316If you still want to edit the poses / props, or do any of the other
317tweaking, then you might want to add the "~MLP lite tools" script to My
318Old Bed. Leave it out, or remove it, once My Old Bed is fully set up.
319
320================================================
321
322DIFFERENCES FOR THE MLP lite v3.0 for OpenSim VARIATION:
323
324As mentioned above, this variation drops a few rarely used features, and
325tries to make things a bit better for OpenSim users. Note that some of
326the dropped features may be added back again in later versions, I dunno
327yet.
328
329- Up to nine avatars can be animated at once.
330
331- Menus no longer lock out other users when someone else is using it.
332
333- The SWAP command has been changed to something like the XPOSE SWAP
334command. It's documented above. The original MLP SWAP command works as
335it used to.
336
337- Changing menus no longer reverts any SWAP commands. Though if the swap
338command is different between menus, it wont let you swap again in the
339different menu.
340
341- Anyone can start up an MLP lite object, and they automatically shutdown
342if no one is around for a couple of minutes. This makes it easy to setup
343public areas with publicly usable objects that don't soak up resources
344when people forget to turn them off after using them. The startup also
345prints the startup time.
346
347- After turning on an MLP object by touching it while it is turned off,
348the MAIN MENU is shown automatically to the person that started it. I
349assume that the person that turned it on is just waiting for it to finish
350starting up so they can actually use it. Actually the menu shows after
351loading menus, but before loading positions, but you can start to use it
352straight away.
353
354- The CHECK command no longer reloads things. It also checks for
355permissions and animations that are not used, as well as a few other
356things. It's in the ~MLP lite tools script.
357
358- Less scripts. There are only two scripts now, "~MLP lite for OpenSim"
359is the main script, and ~ball in the ~ball and prop objects. OpenSim
360doesn't seem to have any memory limits per script (usually the main
361reason to split scripts like that), and it simplifies the code a lot if
362there is just one main script. The ~ball script is much simpler. Props
363use the ~ball script instead of ~prop. There are two optional scripts,
364"~MLP lite props" is used in the main MLP object if you have any props,
365and "~MLP lite tools" is used by creators and editors if needed.
366
367- It may use a bit more CPU time when expressions are being used. You
368may not notice. A lag tester was added to help with this, expressions
369may slow down when it gets laggy.
370
371- Since we use some OpenSim functions that are considered high threat,
372you might need to tweak your OpenSim settings. See below for details.
373
374- The default .MENUITEMS note card now includes an EXTRAS menu, for all
375those functions that usually go at the bottom of each menu, so you can
376fit more poses on each menu. I suggest that "TOMENU EXTRAS>>" be added
377to the end of menus, along with "BACK" and maybe "SWAP"
378
379- Various creator and editing functions have been moved to the TOOLS>>
380menu, and the "~MLP lite tools" script. You can leave this out if
381the MLP object is fully set up.
382
383- You no longer need to have the first two lines of .MENUITEMS as the
384"stand" and "default" POSE commands. It wont hurt to include them.
385
386- MENUORDER is no longer supported, menu buttons appear in the same order
387they are on the .MENUITEMS* cards, coz that's the only sane way of doing
388it.
389
390- The MAIN MENU, first menu in .MENUITEMS, no longer needs a bunch of
391"TOMENU -" commands.
392
393- While the DUMP command still prints the positions in chat, SAVE now
394saves that to a fresh .POSITIONS notecard, so you no longer have
395to cut and paste to your old .POSITIONS cards. The old cards are
396backed up, and the same applies to .PROPS cards that store props.
397
398- MLP lite remembers all editing of the pose balls, not just those done
399while in ADJUST mode, ADJUST now just makes the pose balls easier to
400edit. This makes editing of lots of poses a lot quicker, and means you
401can make quick fixes on the fly, and save them at the end if you want to.
402
403- The BALLUSERS, OFF, REDO, INVISIBLE, STAND, SHOW, HIDE, and AGAIN
404commands no longer exist. They are rarely used, or just not needed.
405
406- The code that slices up long menus into shorter ones with "BACK" and
407"MORE", to fit in the Second Life 12 button menu limit has all been
408removed. I was gonna rewrite that, but for various reasons decided to
409just drop it completely. People can chop up their own submenus if they
410need this, and that's exactly what I have seen other furniture makers do.
411As a bonus, there are no hard coded English menu or button names left in
412the code, so you can easily use MLP lite for other languages. On the
413other hand, all the messages are in English. CHECK will let you know if
414your menus are too long.
415
416- Old ~prop scripts are not supported, use the new ~ball script instead,
417which means a bit of surgery needed on old props.
418
419- Sequences are not currently supported.
420
421- The REORIENT command, and various link messages, are not currently
422supported. Some or all of these may come back. Some of the existing
423link messages changed as well. Combining the scripts meant that a lot
424of these link messages are no longer needed, but some external scripts
425may depend on them.
426
427- ZHAO support is gone. None of the ZHAO based AOs I have seen in
428OpenSim support that. Note that the ~MLPT-AutoZhao script says "This
429one supports AutoZhao, which is a ZHAO variant that turns of
430automatically when you sit." Which seems pointless, it's already doing
431the correct thing. My own AO / swimmer does the correct thing to.
432
433- No Xcite support. Xcite doesn't exist in OpenSim.
434
435- The STOP command now just stops all animations and puts away the balls,
436instead of switching to the "stand" animation.
437
438- Strided lists are used internally, in theory should use a little less
439memory than having separate lists for everything.
440
441- osMessageObject() is used instead of llSay() to communicate with the
442pose balls. This will stop any cross talk issues, and save some script
443running time. As a bonus, balls can be anywhere in the sim.
444
445- The OpenSim notecard reading functions are used instead of the clunky
446Second Life dataserver() functions. Faster and cleaner code.
447
448- While the PRIMTOUCH command is still supported, it's script isn't here.
449I never did have a copy of that script, and haven't needed to use it
450anyway.
451
452- There may be a thing or two I forgot to mention here.
453
454================================================
455
456OPENSIM THREAT LEVELS:
457
458OpenSim developers introduced a concept of "threat levels" for their new
459os* LSL functions. In my not so humble opinion, they where very
460paranoid in assigning threat levels. For this reason, these scripts may
461or may not work in any given grid or sim. In my version of OpenSim I
462have removed or reduced the more insane threat levels, so YMMV.
463
464Refer to the OpenSim documentation for how to deal with this, or ask the
465person that runs your OpenSim grid / sim.
466
467- osAvatarPlayAnimation() and osAvatarStopAnimation()
468Threat level: VeryHigh, disabled by default.
469
470These two functions can animate any avatar with any animation in the
471objects inventory, without asking permission. So I guess this threat
472level is warranted. Also means one script can manage animations for more
473than one avatar, a limitation of the LL variety.
474
475Used to get rid of one of the annoying aspects of how MLP works, which
476often confuses newbies. Also allowed me to reduce the code complexity
477and get rid of all those ~poser X scripts.
478
479- osGetRezzingObject()
480Threat level: none, enabled by default.
481
482Wow, no threat level, the only one. lol
483
484Used to reduce the complexity of the communication between the MLP object
485and the balls.
486
487- osGetNumberOfNotecardLines(), osGetNotecard(), and osGetNotecardLine()
488Threat level: VeryHigh, only enabled for estate owners and managers by
489default.
490
491A faster and less complex way of being able to do what you could always
492do, gets a VeryHigh threat level? WTF are the OpenSim developers
493smoking?
494
495Used to speed things up and reduce code complexity, exactly what they
496where designed for.
497
498- osMakeNotecard()
499Threat level: High, only enabled for estate owners and managers by
500default.
501
502Slightly less WTF than the other notecard functions, but still. Allows
503to script what the object owner can do manually, why is that a high
504threat?
505
506Saves having to cut and paste to notecards after editing ball and prop
507positions.
508
509-osMessageObject()
510Threat level: Low, only enabled for estate owners and managers by
511default.
512
513More WTF, allows to send messages between objects better. Nothing you
514couldn't do before.
515
516Used to reduce the complexity and increase the reliability of messages
517between the MLP object and balls / props.
518
519-osReplaceString()
520Threat level: VeryLow , enabled by default.
521
522Why isn't this threat level none?
523
524Used to reduce script complexity.
525
526-osSetPrimitiveParams()
527Threat level: High, disabled by default.
528
529Once again, allows scripting of something that can be done manually, and
530only works on objects the script owner actually owns. With that later
531restriction, total WTF.
532
533Used to reduce complexity and speed things up.
534
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
diff --git a/~MLP lite props.lsl b/~MLP lite props.lsl
new file mode 100644
index 0000000..f8042e4
--- /dev/null
+++ b/~MLP lite props.lsl
@@ -0,0 +1,354 @@
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
7vector RefPos;
8rotation RefRot;
9
10string Pose = "";
11
12list Props; // List of props.
13integer PROP_NAME = 0; // Name of prop, which should match a POSE name.
14integer PROP_OBJECT = 1; // | separated names of props in inventory.
15integer PROP_POSROT = 2; // | separated position and rotation pairs.
16integer PROP_STRIDE = 3;
17
18list Balls; // Ball / prop tracker.
19integer BALL_NUM = 0; // The number of the ball, negative numbers for the props.
20integer BALL_KEY = 1; // The UUID of the ball / prop.
21integer BALL_AVATAR = 2; // The UUID of any avatar sitting on the ball.
22integer BALL_STRIDE = 3;
23
24string LIST_SEP = "$!#"; // Used to seperate lists when sending them as strings.
25
26// The various link messages. The first lot are historical.
27//integer OLD_TOUCH = -1; // msg = "PRIMTOUCH"
28integer OLD_POSEB = 0; // msg = "POSEB" id = pose name
29// // Also CHECK1 and CHECK2 instead of pose name.
30integer OLD_STOP = 1; // msg = "STOP"
31//integer OLD_REF = 8; // msg = RefPos id = RefRot
32integer OLD_SITS = -11000; // msg = ball number|anim name id = avatar key
33integer OLD_STANDS = -11001; // msg = ball number id = avatar key
34integer OLD_ANIM = -11002; // msg = ball number|anim name id = avatar key
35//integer SEQ_CMD = -12001; // msg = ~sequencer command
36integer OLD_CMD = -12002; // msg = menu command for ~MLP id = user key
37
38integer MLP_CMD = -13001; // A command that ~MLP dealt with, so other scripts can hook it.
39integer TOOL_DATA = -13002; // Get Menus, Poses, Props, or Sounds list.
40integer MLP_DATA = -13003; // Set Menus, Poses, Props, or Sounds list.
41integer MLP_POSE = -13004; // doPose(pose, type);
42integer MLP_UNKNOWN = -13005; // ~MLP doesn't know this command, might be for other scripts.
43
44// Types for doPose(),
45integer POSES_POSE = -14001; // Change pose, even if it's changing to the same pose.
46integer POSES_SWAP = -14002; // Swap poses.
47integer POSES_DUMP = -14003; // Dump or save poses.
48integer POSES_Z = -14004; // Change height.
49
50
51integer listFindString(list lst, string name, integer stride)
52{
53 integer f = llListFindList(lst, [name]);
54 integer ix = f / stride;
55
56 // Round to nearest stride.
57 ix = ix * stride;
58
59 // Sanity check, make sure we found a name, not something else, else do it the slow way.
60 if ((-1 != f) && (ix != f))
61 {
62 integer l = llGetListLength(lst);
63 integer i;
64
65 f = -1;
66 for (i = 0; i < l; i += stride)
67 {
68 if (llList2String(lst, i) == name)
69 {
70 f = i;
71 i = l;
72 }
73 }
74 }
75 return f;
76}
77
78// Only used in one place, but leave it here for now.
79string prStr(string str)
80{
81 integer ix = llSubStringIndex(str, ">");
82 vector p = ((vector) llGetSubString(str, 0, ix) - RefPos) / RefRot;
83 vector r = llRot2Euler((rotation) llGetSubString(str, ix + 1, -1) / RefRot) * RAD_TO_DEG;
84
85 // OpenSim likes to swap these around, which triggers the ball movement saving.
86 // Coz OpenSim doesn't support move events, so we gotta do things the hard way.
87 if (-179.9 >= r.x) r.x = 180.0;
88 if (-179.9 >= r.y) r.y = 180.0;
89 if (-179.9 >= r.z) r.z = 180.0;
90
91 return "<" + round(p.x, 3) + "," + round(p.y, 3) + "," + round(p.z, 3) +
92 "><" + round(r.x, 1) + "," + round(r.y, 1) + "," + round(r.z, 1) + ">";
93}
94
95string round(float number, integer places)
96{
97 float shifted;
98 integer rounded;
99 string s;
100
101 shifted = number * llPow(10.0, (float) places);
102 rounded = llRound(shifted);
103 s = (string) ((float) rounded / llPow(10.0, (float)places));
104 rounded = llSubStringIndex(s, ".");
105 if (-1 != rounded)
106 s = llGetSubString(s, 0, llSubStringIndex(s, ".") + places);
107 else
108 {
109 s += ".00000000";
110 s = llGetSubString(s,0,llSubStringIndex(s, ".") + places);
111 }
112 return s;
113}
114
115integer findBall(integer num)
116{
117 integer f = llListFindList(Balls, [num]);
118 integer ix = f / BALL_STRIDE;
119
120 // Round to nearest stride.
121 ix = ix * BALL_STRIDE;
122
123 if ((-1 != f) && (ix == f)) // Sanity check, make sure we found a chan, not something else.
124 return f;
125 else
126 return -1;
127}
128
129saveBall(integer num, key id, key avatar)
130{
131 integer f = findBall(num);
132
133 if (-1 == f)
134 Balls += [num, id, avatar];
135 else
136 Balls = llListReplaceList(Balls, [num, id, avatar], f, f + BALL_STRIDE - 1);
137}
138
139rezThing(string thing, string posRot, integer num)
140{
141 integer i = llSubStringIndex(posRot, ">");
142
143 llRezObject(thing,
144 ((vector) llGetSubString(posRot, 0, i)) * RefRot + RefPos,
145 ZERO_VECTOR,
146 llEuler2Rot((vector) llGetSubString(posRot, i + 1, -1) * DEG_TO_RAD) * RefRot,
147 num);
148}
149
150readProps(list propCards)
151{
152 integer i;
153 integer l = llGetListLength(propCards);
154
155 propCards = llListSort(propCards, 1, TRUE);
156 for (i = 0; i < l; i++)
157 {
158 string card = llList2String(propCards, i);
159 list crd = llParseStringKeepNulls(osGetNotecard(card), ["\n"], []);
160 integer m = llGetListLength(crd);
161 integer j;
162
163 llOwnerSay("Reading '" + card + "'.");
164 for (j = 0; j < m; j++)
165 {
166 string data = llList2String(crd, j);
167
168 if (llGetSubString(data, 0, 0) != "/")
169 { // skip comments
170 data = llStringTrim(data, STRING_TRIM);
171 if ("" != data)
172 {
173 // .PROPS is different from .POSITIONS, which is just inconsistant.
174 // There's an extra | at the beginning of the lines, for no apparent reason.
175 // The position and rotation has a "/" between them, and normally / is a comment.
176 // But we gotta stay compatible. sigh
177 list props = llParseStringKeepNulls(data, ["|"], []);
178 string name = llStringTrim(llList2String(props, 1), STRING_TRIM);
179 string prop = llStringTrim(llList2String(props, 2), STRING_TRIM);
180 list posrots = llParseString2List(llStringTrim(llList2String(props, 3), STRING_TRIM), ["/"], []);
181
182 saveProp(name, prop, llDumpList2String(posrots, ""));
183 }
184 }
185 }
186 }
187}
188
189integer findProp(string name)
190{
191 return listFindString(Props, name, PROP_STRIDE);
192}
193
194saveProp(string name, string prop, string posRot)
195{
196 integer f = findProp(name);
197
198 if (-1 != f)
199 {
200 string t;
201
202 t = llList2String(Props, f + PROP_OBJECT);
203 if ("" != t)
204 prop += "|" + prop;
205 t = llList2String(Props, f + PROP_POSROT);
206 if ("" != t)
207 posRot += "|" + posRot;
208 Props = llListReplaceList(Props, [name, prop, posRot], f, f + PROP_STRIDE - 1);
209 }
210 else
211 Props += [name, prop, posRot];
212}
213
214// ball is either the ball number, or a POSES_* flag.
215// ball on the other hand, is a negative integer for props.
216doPose(string newPose, integer ball)
217{
218 integer f = findProp(Pose);
219 integer p = findBall(ball);
220 integer l = llGetListLength(Balls);
221 integer i = 0;
222 vector newRefPos = llGetPos();
223 rotation newRefRot = llGetRot();
224
225 newRefPos.z += ((integer) llGetObjectDesc()) / 100.0;
226
227 if (-1 != p)
228 {
229 i = p;
230 l = p + BALL_STRIDE;
231 }
232 for (; i < l; i += BALL_STRIDE)
233 {
234 integer b0 = llList2Integer(Balls, i + BALL_NUM);
235 integer isBall = (0 <= b0);
236
237 if (isBall)
238 ;
239 else //if ((POSES_SWAP != ball)
240 {
241 if (-1 != f)
242 {
243 integer m = f + PROP_POSROT;
244 string prOld = llList2String(Props, m);
245 // Find out where the prop is now, and rotation.
246 integer q = -1 - b0;
247 string bpr = prStr(llDumpList2String(
248 llGetObjectDetails(llList2Key(Balls, i + BALL_KEY), [OBJECT_POS, OBJECT_ROT]), ""));
249 string result = llDumpList2String(llListReplaceList(llParseString2List(prOld, ["|"], []), [bpr], q, q), "|");
250 // Actually store the props new position / rotation if it moved.
251 if (result != prOld)
252 {
253 llOwnerSay(" MOVEd prop " + q + ".\n\t old [" + prOld + "]\n\t new [" + result + "]");
254 Props = llListReplaceList(Props, [result], m, m);
255 }
256 }
257
258 if ((POSES_DUMP != ball) && (POSES_SWAP != ball))
259 {
260 // Remove the prop.
261 osMessageObject(llList2Key(Balls, i + BALL_KEY), "DIE");
262 Balls = llListReplaceList(Balls, [], i, i + BALL_STRIDE - 1);
263 i -= BALL_STRIDE;
264 l -= BALL_STRIDE;
265 }
266 }
267 }
268 RefPos = newRefPos;
269 RefRot = newRefRot;
270
271 // Props are per pose, so deal with them here to.
272 // Assumption - props don't MOVE when we change poses, they die, and get recreated when needed.
273 // Note that the original MLP also assumed that props don't MOVE, though they send MOVE.
274 if (POSES_POSE == ball)
275 {
276 p = findProp(newPose);
277 if (-1 != p)
278 {
279 list o = llParseStringKeepNulls(llList2String(Props, p + PROP_OBJECT), ["|"], []);
280 list q = llParseStringKeepNulls(llList2String(Props, p + PROP_POSROT), ["|"], []);
281
282 l = llGetListLength(o);
283 for (i = 0; i < l; i++)
284 rezThing(llList2String(o, i), llList2String(q, i), -1 - i);
285 }
286 }
287 Pose = newPose;
288}
289
290
291default
292{
293 link_message(integer from, integer num, string str, key id)
294 {
295 if (((0 == num) && ("POSEB" == str)) || ((1 == num) && ("STOP" == str)))
296 ; // Old messages we can ignore.
297 else if ((MLP_UNKNOWN == num) && ("LOADPROPS" == str))
298 {
299 float then = llGetTime();
300 float now = 0.0;
301 list propCards = []; // List of names of config cards.
302 string item;
303 integer i = llGetInventoryNumber(INVENTORY_NOTECARD);
304 integer PropCount;
305
306 while (i-- > 0)
307 {
308 item = llGetInventoryName(INVENTORY_NOTECARD, i);
309 if (llSubStringIndex(item, ".PROPS") == 0)
310 propCards += (list) item;
311 }
312 Props = [];
313 readProps(propCards);
314 PropCount = llGetListLength(Props) / PROP_STRIDE;
315 if (0 < PropCount)
316 {
317 now = llGetTime();
318 llOwnerSay("Loaded " + PropCount + " props in " + (string) (now - then) + " seconds.");
319 }
320 }
321 else if (MLP_POSE == num)
322 {
323 if (NULL_KEY == id)
324 id = Pose;
325 doPose((string) id, (integer) str);
326 }
327 else if (TOOL_DATA == num)
328 {
329 if ("" == str)
330 {
331 if ("Props" == id)
332 llMessageLinked(from, num, LIST_SEP + llDumpList2String(Props, LIST_SEP), id);
333 }
334 else
335 {
336 if ("Props" == id)
337 Props = llParseStringKeepNulls(llGetSubString(str, llStringLength(LIST_SEP), -1), [LIST_SEP], []);
338 }
339 }
340 else if ((OLD_CMD == num) || (MLP_CMD == num) || (MLP_UNKNOWN == num)
341 || (OLD_SITS == num) || (OLD_STANDS == num) || (OLD_ANIM == num))
342 ;
343 else
344 llOwnerSay(llGetScriptName() + " Unknown link message " + num + ", " + str);
345 }
346
347 dataserver(key queryId, string str)
348 {
349 list data = llParseString2List(str, ["|"], []);
350
351 if ("ALIVE" == llList2String(data, 0))
352 saveBall(llList2Integer(data, 1), queryId, NULL_KEY);
353 }
354}
diff --git a/~MLP lite tools.lsl b/~MLP lite tools.lsl
new file mode 100644
index 0000000..b8e9925
--- /dev/null
+++ b/~MLP lite tools.lsl
@@ -0,0 +1,595 @@
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 tools v3.0 alpha";
8
9key Owner;
10integer Channel;
11
12list Need = [];
13string Todo = "";
14
15// NOTE - This one uses \n to separate sub lists, coz | is used in some of the commands.
16list Menus; // List of menus.
17integer MENU_NAME = 0; // Name of menu.
18integer MENU_AUTH = 1; // Authorised users of menu - 0 = owner, 1 = group, 2 = all
19integer MENU_COLOURS = 2; // \n spearated list of ball colours.
20integer MENU_ENTRIES = 3; // \n separated list of entries.
21integer MENU_CMDS = 4; // \n separated list of commands, matching the entries.
22integer MENU_SWAPS = 5; // \n separated list of colour sets.
23integer MENU_STRIDE = 6;
24
25string Pose = "";
26list Poses; // List of poses.
27integer POSE_NAME = 0; // Name of pose.
28integer POSE_ANIM = 1; // | separated animations.
29integer POSE_EMOTE = 2; // | separated emotions and timers list.
30integer POSE_POSROT = 3; // | separated posiiton and rotation pairs.
31integer POSE_STRIDE = 4;
32
33list Props; // List of props.
34integer PROP_NAME = 0; // Name of prop, which should match a POSE name.
35integer PROP_OBJECT = 1; // | separated names of props in inventory.
36integer PROP_POSROT = 2; // | separated position and rotation pairs.
37integer PROP_STRIDE = 3;
38
39list Sounds;
40integer SOUND_NAME = 0;
41integer SOUND_INV = 1;
42integer SOUND_STRIDE = 2;
43
44string LIST_SEP = "$!#"; // Used to seperate lists when sending them as strings.
45
46// The various link messages. The first lot are historical.
47//integer OLD_TOUCH = -1; // msg = "PRIMTOUCH"
48integer OLD_POSEB = 0; // msg = "POSEB" id = pose name
49// // Also CHECK1 and CHECK2 instead of pose name.
50integer OLD_STOP = 1; // msg = "STOP"
51//integer OLD_REF = 8; // msg = RefPos id = RefRot
52integer OLD_SITS = -11000; // msg = ball number|anim name id = avatar key
53integer OLD_STANDS = -11001; // msg = ball number id = avatar key
54integer OLD_ANIM = -11002; // msg = ball number|anim name id = avatar key
55//integer SEQ_CMD = -12001; // msg = ~sequencer command
56integer OLD_CMD = -12002; // msg = menu command for ~MLP id = user key
57
58integer MLP_CMD = -13001; // A command that ~MLP dealt with, so other scripts can hook it.
59integer TOOL_DATA = -13002; // Get Menus, Poses, Props, or Sounds list.
60integer MLP_DATA = -13003; // Set Menus, Poses, Props, or Sounds list.
61integer MLP_POSE = -13004; // doPose(pose, type);
62integer MLP_UNKNOWN = -13005; // ~MLP doesn't know this command, might be for other scripts.
63
64// Types for doPose(),
65integer POSES_POSE = -14001; // Change pose, even if it's changing to the same pose.
66integer POSES_SWAP = -14002; // Swap poses.
67integer POSES_DUMP = -14003; // Dump or save poses.
68integer POSES_Z = -14004; // Change height.
69
70integer PERMS_CT = PERM_COPY | PERM_TRANSFER;
71
72
73integer listFindString(list lst, string name, integer stride)
74{
75 integer f = llListFindList(lst, [name]);
76 integer ix = f / stride;
77
78 // Round to nearest stride.
79 ix = ix * stride;
80
81 // Sanity check, make sure we found a name, not something else, else do it the slow way.
82 if ((-1 != f) && (ix != f))
83 {
84 integer l = llGetListLength(lst);
85 integer i;
86
87 f = -1;
88 for (i = 0; i < l; i += stride)
89 {
90 if (llList2String(lst, i) == name)
91 {
92 f = i;
93 i = l;
94 }
95 }
96 }
97 return f;
98}
99
100integer findMenu(string name)
101{
102 return listFindString(Menus, name, MENU_STRIDE);
103}
104
105list reportPose()
106{
107 list result = [];
108 integer l = llGetListLength(Poses);
109 integer i;
110
111 for (i = 0; i < l; i += POSE_STRIDE)
112 {
113 list prs = llParseString2List(llList2String(Poses, i + POSE_POSROT), ["|"], []);
114 string r = "{" + llList2String(Poses, i) + "} ";
115 integer m = llGetListLength(prs);
116 integer j;
117
118 for (j = 0; j < m; j++)
119 r += llList2String(prs, j);
120 result += [r];
121 }
122
123 return result;
124}
125
126list reportProp()
127{
128 list result = [];
129 integer l = llGetListLength(Props);
130 integer i;
131
132 for (i = 0; i < l; i += PROP_STRIDE)
133 {
134 list obs = llParseString2List(llList2String(Props, i + PROP_OBJECT), ["|"], []);
135 list prs = llParseString2List(llList2String(Props, i + PROP_POSROT), ["|"], []);
136 string r = "|" + llList2String(Props, i) + "|";
137 integer m = llGetListLength(prs);
138 integer j;
139
140 // See ~MLP lite props for a comment about why this is different from reportPose().
141 for (j = 0; j < m; j++)
142 r += llList2String(obs, j) + "|" + osReplaceString(llList2String(prs, j), "><", ">/<", -1, 0);
143 result += [r];
144 }
145
146 return result;
147}
148
149list checkPerms(string name, integer type)
150{
151 list ans = [];
152 integer i = llGetInventoryNumber(type);
153 integer p;
154
155 while (i-- > 0)
156 {
157 string a = llGetInventoryName(type, i);
158
159 ans += [a];
160 p = llGetInventoryPermMask(a, MASK_OWNER);
161 if (PERMS_CT != (p & PERMS_CT))
162 llSay(0, name + " '" + a + "' does not have copy or transfer permissions for the owner.");
163 p = llGetInventoryPermMask(a, MASK_NEXT);
164 if (PERMS_CT != (p & PERMS_CT))
165 llSay(0, name + " '" + a + "' does not have copy or transfer permissions for the next owner.");
166 }
167 return ans;
168}
169
170checkConfig()
171{
172 list ans;
173 integer l = llGetListLength(Poses);
174 integer m;
175 integer i;
176 integer j;
177 integer f;
178
179 llSay(0, "Checking most things...");
180 ans = checkPerms("Animation", INVENTORY_ANIMATION);
181 for (i = 0; i < l; i += POSE_STRIDE)
182 {
183 string name = llList2String(Poses, i + POSE_NAME);
184
185 if ("" == llList2String(Poses, i + POSE_POSROT))
186 llSay(0, "No .POSITIONS* entry for '" + name + "'.");
187 else if ("" == llList2String(Poses, i + POSE_ANIM))
188 {
189 if (("default" != name) && ("stand" != name))
190 llSay(0, "No .MENUITEMS* entry for '" + name + "'.");
191 }
192 else
193 {
194 list anims = llParseString2List(llList2String(Poses, i + POSE_ANIM), ["|"], []);
195
196 m = llGetListLength(anims);
197 for (j = 0; j < m; j++)
198 {
199 string aname = llList2String(anims, j);
200
201 if (aname != "")
202 {
203 if ((aname != "stand") && (aname != "sit_ground"))
204 {
205 if (llGetSubString(aname, -1, -1) == "*")
206 aname = llGetSubString(aname, 0, -2);
207 else
208 {
209 f = llSubStringIndex(aname, "::");
210 if (-1 != f)
211 aname = llGetSubString(aname, 0, f - 1);
212 }
213
214 if (llGetInventoryType(aname) != INVENTORY_ANIMATION)
215 llSay(0, "Animation '" + aname + "' is not in inventory (ok for build-in animations, otherwise check).");
216 f = llListFindList(ans, aname);
217 if (-1 != f)
218 ans = llDeleteSubList(ans, f, f);
219 }
220 }
221 }
222 }
223 }
224 if (0 < llGetListLength(ans))
225 llSay(0, "These animations are not in any config cards - " + llList2CSV(llListSort(ans, 1, TRUE)));
226
227 ans = checkPerms("Object", INVENTORY_OBJECT);
228 l = llGetListLength(Props);
229 f = llListFindList(ans, "~ball");
230 if (-1 != f)
231 ans = llDeleteSubList(ans, f, f);
232 for (i = 0; i < l; i += PROP_STRIDE)
233 {
234 list objs = llParseStringKeepNulls(llList2String(Props, i + PROP_OBJECT), ["|"], []);
235
236 m = llGetListLength(objs);
237 for (j = 0; j < m; j++)
238 {
239 string s = llList2String(objs, j);
240
241 f = llListFindList(ans, s);
242 if (-1 != f)
243 ans = llDeleteSubList(ans, f, f);
244 else
245 llSay(0, "Object '" + s + "' is not in inventory.");
246 }
247 }
248 if (0 < llGetListLength(ans))
249 llSay(0, "These objects are not in any config cards - " + llList2CSV(llListSort(ans, 1, TRUE)));
250
251 ans = checkPerms("Sound", INVENTORY_SOUND);
252 l = llGetListLength(Sounds);
253 if (l)
254 {
255 for (i = 0; i < l; i += SOUND_STRIDE)
256 {
257 string s = llList2String(Sounds, i + SOUND_INV);
258
259// TODO - No idea why checkPerms() is handing me ["", ""].
260 if (llStringLength(s))
261 {
262 f = llListFindList(ans, s);
263 if (-1 != f)
264 ans = llDeleteSubList(ans, f, f);
265 else
266 llSay(0, "Sound '" + s + "' is not in inventory.");
267 }
268 }
269 }
270 if (0 < llGetListLength(ans))
271 llSay(0, "These sounds are not in any config cards - " + llList2CSV(llListSort(ans, 1, TRUE)));
272
273 l = llGetListLength(Poses);
274 for (i = 0; i < l; i += MENU_STRIDE)
275 {
276 string name = llList2String(Menus, i + MENU_NAME);
277
278 ans = llParseStringKeepNulls(llList2String(Menus, i + MENU_CMDS), ["\n"], []);
279 if (12 < llGetListLength(ans))
280 llSay(0, "To many menu items in '" + name + "' menu.");
281 m = llGetListLength(ans);
282 for (j = 0; j < m; j++)
283 {
284 string cmd = llList2String(ans, j);
285
286 if ("TOMENU " == llGetSubString(cmd, 0, 6))
287 {
288 cmd = llGetSubString(cmd, 7, -1);
289 if (-1 == findMenu(cmd))
290 llSay(0, "Menu '" + cmd + "' not found.");
291 }
292 }
293 }
294
295 list scripts = llParseStringKeepNulls("memory menu menucfg pos pose poser prop props run timeout", [""],[]);
296 list found = [];
297
298 ans = checkPerms("Script", INVENTORY_SCRIPT);
299 l = llGetListLength(scripts);
300 for (i = 0; i < l; i++)
301 {
302 f = llListFindList(ans, ["~" + llList2String(scripts, i)]);
303 if (-1 != f)
304 {
305 found += ["~" + llList2String(scripts, i)];
306 ans = llDeleteSubList(ans, f, f);
307 }
308 }
309 for (i = 1; i < 9; i++)
310 {
311 f = llListFindList(ans, ["~poser " + i]);
312 if (-1 != f)
313 {
314 found += ["~poser " + i];
315 ans = llDeleteSubList(ans, f, f);
316 }
317 }
318 f = llListFindList(ans, ["~ball"]);
319 if (-1 != f)
320 {
321 found += ["~ball"];
322 ans = llDeleteSubList(ans, f, f);
323 llSay(0, "There is a '~ball' script, it should be in the '~ball' object, and in any prop objects.");
324 llSay(0, "There is a '~ball' script, it should NOT be in this '" + llGetObjectName() + "' object.");
325 }
326 f = llListFindList(ans, ["~sequencer"]);
327 if (-1 != f)
328 {
329 ans = llDeleteSubList(ans, f, f);
330 llSay(0, "There is a '~sequencer' script, which you wont need unless you where using the old MLP sequences.");
331 llSay(0, "If any old '.SEQUENCER' cards are in use, try them, see if it still works.");
332 }
333 if (0 < llGetListLength(found))
334 llSay(0, "These scripts are likely left overs from an older MLP, you can probably remove them - " + llList2CSV(llListSort(found, 1, TRUE)));
335
336
337 f = llListFindList(ans, ["~MLP lite for OpenSim"]);
338 if (-1 != f)
339 ans = llDeleteSubList(ans, f, f);
340 else
341 llSay(0, llGetScriptName() + " says it wants '~MLP lite for OpenSim'.");
342 f = llListFindList(ans, ["~MLP lite tools"]);
343 if (-1 != f)
344 ans = llDeleteSubList(ans, f, f);
345 else
346 llSay(0, llGetScriptName() + " != ~MLP lite tools!");
347
348 list cards = checkPerms("Notecard", INVENTORY_NOTECARD);
349 l = llGetListLength(cards);
350 f = -1;
351 for (i = 0; i < l; i++)
352 {
353 string crd = llList2String(cards, i);
354
355 if (llSubStringIndex(crd, ".SEQUENCES") == 0)
356 llSay(0, "Old MLP sequences might be in notecard - '" + crd + "'.");
357 if (llSubStringIndex(crd, ".PROPS") == 0)
358 f = i;
359 }
360 if (-1 == f)
361 {
362 f = llListFindList(ans, ["~MLP lite props"]);
363 if (-1 != f)
364 llSay(0, "'~MLP lite props' script is not needed if there are no .PROPS* cards.");
365 }
366 else
367 {
368 f = llListFindList(ans, ["~MLP lite props"]);
369 if (-1 == f)
370 llSay(0, "'~MLP lite props' script is needed if there are .PROPS* cards.");
371 }
372
373
374 llSay(0, "Checks completed.");
375}
376
377doMenu(key id)
378{
379 llDialog(id, Version, [
380 "All poses", " ", "Quit tools",
381 "Height>>", "Pose match>>", "Adjust pos>>",
382 "RELOAD", "RESET", "REDECORATE",
383 "CHECK", "DUMP", "SAVE"
384 ], Channel);
385}
386
387doHeight(key id)
388{
389 llDialog(id, Version, [
390 " ", " ", "Back",
391 "Z-1", "Z-5", "Z-25",
392 "Z+1", "Z+5", "Z+25"
393 ], Channel);
394}
395
396getData(string name)
397{
398 Need += [name];
399 llMessageLinked(LINK_SET, TOOL_DATA, "", name);
400}
401
402default
403{
404 state_entry()
405 {
406 Owner = llGetOwner();
407 Channel = (integer) ("0x" + llGetSubString((string) llGetKey(), -4, -1) + 1);
408 llListen(Channel, "", Owner, "");
409 }
410
411 link_message(integer from, integer num, string str, key id)
412 {
413 if (((0 == num) && ("POSEB" == str)) || ((1 == num) && ("STOP" == str)))
414 ;
415 else if ((MLP_UNKNOWN == num) && ("TOOLS" == str))
416 doMenu(id);
417 else if ((MLP_POSE == num) || (OLD_CMD == num) || (MLP_CMD == num) || (MLP_UNKNOWN == num)
418 || (OLD_SITS == num) || (OLD_STANDS == num) || (OLD_ANIM == num))
419 ;
420 else if ((TOOL_DATA == num) && ("" == str))
421 ;
422 else if ((TOOL_DATA == num) && ("" != str))
423 {
424 list data = llParseStringKeepNulls(llGetSubString(str, llStringLength(LIST_SEP), -1), [LIST_SEP], []);
425 integer f = llListFindList(Need, id);
426
427 if ("Menus" == id)
428 Menus = data;
429 else if ("Poses" == id)
430 Poses = data;
431 else if ("Props" == id)
432 Props = data;
433 else if ("Sounds" == id)
434 Sounds = data;
435 if (-1 != f)
436 {
437 integer i;
438
439 Need = llDeleteSubList(Need, f, f);
440 if (0 == llGetListLength(Need))
441 {
442 if ("CHECK" == Todo)
443 {
444 checkConfig();
445 Todo = "";
446 }
447 else if ("DUMP" == Todo)
448 {
449 string objectName = llGetObjectName();
450 list report = reportPose();
451 integer l = llGetListLength(report);
452
453 llSetObjectName(".");
454 llOwnerSay(".POSITIONS---------------------------------------------------------------");
455 for (i = 0; i < l; i++)
456 llOwnerSay(llList2String(report, i));
457 llOwnerSay(".PROPS-------------------------------------------------------------------");
458 report = reportProp();
459 l = llGetListLength(report);
460 for (i = 0; i < l; i++)
461 llOwnerSay(llList2String(report, i));
462 llOwnerSay("-------------------------------------------------------------------------");
463 llSetObjectName(objectName);
464 Todo = "";
465 }
466 else if ("SAVE" == Todo)
467 {
468 i = llGetInventoryNumber(INVENTORY_NOTECARD);
469 list cards = [];
470
471 llSay(0, "Backing up old cards.");
472 while (i-- > 0)
473 {
474 string item = llGetInventoryName(INVENTORY_NOTECARD, i);
475
476 if ((llSubStringIndex(item, ".POSITIONS") == 0) || (llSubStringIndex(item, ".PROPS") == 0))
477 cards += [llGetInventoryName(INVENTORY_NOTECARD, i)];
478 }
479 i = llGetListLength(cards);
480 while (i-- > 0)
481 {
482 string item = llList2String(cards, i);
483
484 osMakeNotecard(".backup" + item, llParseStringKeepNulls(osGetNotecard(item), ["\n"], []));
485 llRemoveInventory(item);
486 }
487
488 osMakeNotecard(".POSITIONS", reportPose());
489 llSay(0, "Current ball positions saved to the .POSITIONS notecard.");
490 if (llGetListLength(Props))
491 {
492 osMakeNotecard(".PROPS", reportProp());
493 llSay(0, "Current props positions saved to the .PROPS notecard.");
494 }
495 Todo = "";
496 }
497 else if ("All poses" == Todo)
498 {
499
500 f = llGetListLength(Menus);
501 for (i = 0; i < f; i += MENU_STRIDE)
502 {
503 string name = llList2String(Menus, i + MENU_NAME);
504 list cmds = llParseStringKeepNulls(llList2String(Menus, i + MENU_CMDS), ["\n"], []);
505 integer l = llGetListLength(cmds);
506 integer j;
507
508 llOwnerSay("Running through all the poses in menu '" + name + "'.");
509 for (j = 0; j < l; j++)
510 {
511 string p = llList2String(cmds, j);
512
513 if ("POSE " == llGetSubString(p, 0, 4))
514 {
515 string pose = llGetSubString(p, 5, -1);
516
517 llMessageLinked(LINK_SET, OLD_CMD, "TOMENU " + name, Owner);
518 llMessageLinked(LINK_SET, MLP_POSE, (string) POSES_POSE, pose);
519 llSleep(5.0);
520 }
521 }
522 }
523 llMessageLinked(LINK_SET, OLD_CMD, "STOP", Owner);
524 llOwnerSay("Finished all poses.");
525 Todo = "";
526 }
527 }
528 }
529 }
530 else
531 llOwnerSay(llGetScriptName() + " Unknown link message " + num + ", " + str);
532 }
533
534 listen(integer channel, string name, key id, string button)
535 {
536 if ("CHECK" == button)
537 {
538 Todo = button;
539 getData("Menus");
540 getData("Poses");
541 if (llGetInventoryType("~MLP lite props") == INVENTORY_SCRIPT)
542 getData("Props");
543 getData("Sounds");
544 }
545 else if ("DUMP" == button)
546 {
547 // Save any edits to the current pose first.
548 llMessageLinked(LINK_SET, MLP_POSE, (string) POSES_DUMP, NULL_KEY);
549 getData("Poses");
550 if (llGetInventoryType("~MLP lite props") == INVENTORY_SCRIPT)
551 getData("Props");
552 Todo = button;
553 }
554 else if ("SAVE" == button)
555 {
556 // Save any edits to the current pose first.
557 llMessageLinked(LINK_SET, MLP_POSE, (string) POSES_DUMP, NULL_KEY);
558 getData("Poses");
559 if (llGetInventoryType("~MLP lite props") == INVENTORY_SCRIPT)
560 getData("Props");
561 Todo = button;
562 }
563 else if ("All poses" == button)
564 {
565 getData("Menus");
566 Todo = button;
567 }
568 else if ("RESET" == button || "RELOAD" == button || "REDECORATE" == button)
569 llMessageLinked(LINK_SET, OLD_CMD, button, Owner);
570 else if ("Height>>" == button)
571 {
572 doHeight(id);
573 return;
574 }
575 else if ("Z" == llGetSubString(button, 0, 0))
576 {
577 integer i = (integer) llGetSubString(button, 1, 10);
578 integer Zoffset = (integer) llGetObjectDesc() + i;
579
580 llSetObjectDesc((string) Zoffset);
581 llMessageLinked(LINK_SET, MLP_POSE, (string) POSES_Z, NULL_KEY);
582 llOwnerSay("Height Adjustment: change by " + (string) i + "cm, new offset: " + (string) Zoffset + "cm.");
583 doHeight(id);
584 return;
585 }
586 else if ("Back" == button)
587 ;
588 else if ("Quit tools" == button)
589 {
590 llMessageLinked(LINK_SET, OLD_CMD, "TOMENU", Owner);
591 return;
592 }
593 doMenu(id);
594 }
595}
diff --git a/~ball.lsl b/~ball.lsl
new file mode 100644
index 0000000..4d6bb2d
--- /dev/null
+++ b/~ball.lsl
@@ -0,0 +1,63 @@
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
7key Boss;
8integer Number;
9integer isBall;
10
11
12default
13{
14 state_entry()
15 {
16 integer perm = llGetObjectPermMask(MASK_OWNER);
17
18 if (0 == (perm & (PERM_COPY | PERM_MOVE | PERM_MODIFY)))
19 {
20 llOwnerSay("DANGER, CAN'T COPY, MODIFY, OR MOVE THIS OBJECT!");
21 llSay(DEBUG_CHANNEL, "DANGER, CAN'T COPY, MODIFY, OR MOVE THIS OBJECT!");
22 }
23 }
24
25 on_rez(integer num)
26 {
27 string objectName = llGetObjectName();
28
29 isBall = ("~ball" == objectName);
30 if (isBall) // This is only coz OpenSim 8.2 doesn't know PRIM_SIT_TARGET.
31 llSitTarget(<0.0, 0.0, -0.1>, ZERO_ROTATION);
32 Boss = osGetRezzingObject();
33 if (NULL_KEY != Boss)
34 {
35 Number = num;
36 osMessageObject(Boss, "ALIVE|" + num);
37 llSetTimerEvent(600.0);
38 }
39 }
40
41 changed(integer change)
42 {
43 if ((CHANGED_LINK == change) && isBall)
44 osMessageObject(Boss, "AVATAR|" + Number + "|" + (string) llAvatarOnSitTarget());
45 }
46
47 dataserver(key query_id, string str)
48 {
49 if (query_id == Boss)
50 {
51 if ("LIVE" == str)
52 llSetTimerEvent(600.0);
53 else if ("DIE" == str)
54 llDie();
55 }
56 }
57
58 timer()
59 { // not heard "LIVE" from ~MLP for a while: suicide
60 llDie();
61 }
62}
63