From 9dd74045373c39d8a35a59f3c2801b0348a44a9b Mon Sep 17 00:00:00 2001 From: Dave Seikel Date: Sun, 20 May 2018 23:17:24 +1000 Subject: Initial commit. This is the current alpha version, it was time to put it somewhere. --- .MENUITEMS.txt | 18 + LICENSE | 2 +- README.md | 532 ++++++++++++++++ ~MLP lite for OpenSim.lsl | 1550 +++++++++++++++++++++++++++++++++++++++++++++ ~MLP lite props.lsl | 354 +++++++++++ ~MLP lite tools.lsl | 595 +++++++++++++++++ ~ball.lsl | 63 ++ 7 files changed, 3113 insertions(+), 1 deletion(-) create mode 100644 .MENUITEMS.txt create mode 100644 ~MLP lite for OpenSim.lsl create mode 100644 ~MLP lite props.lsl create mode 100644 ~MLP lite tools.lsl create mode 100644 ~ball.lsl diff --git a/.MENUITEMS.txt b/.MENUITEMS.txt new file mode 100644 index 0000000..926e10f --- /dev/null +++ b/.MENUITEMS.txt @@ -0,0 +1,18 @@ +// NORELOAD // inhibit reload on rez -- useful for worn items + +MENU MAIN MENU | ALL +TOMENU EXTRAS>> + +MENU EXTRAS>> | ALL +ADJUST +CHAT Chat Info | ON //switches chat info: ON/OFF (set default) +TOMENU OPTIONS>> +STOP +BACK + +MENU OPTIONS>> | OWNER //this menu can be accessed by the OWNER only (other options: GROUP / ALL) +MENUUSERS MenuUsers | ALL //switches who can access menus: ALL/GROUP/OWNER (set default) +TOOLS TOOLS>> // Show the Tools menu, if it's installed. +RESTART ShutDown! +BACK + diff --git a/LICENSE b/LICENSE index d471360..ca922b2 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ BSD 3-Clause License -Copyright (c) 2018, David Seikel +Copyright (c) 2018, onefang rejected All rights reserved. Redistribution 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 @@ # MLP-lite A lighter version of the MLP scripts, built for OpenSim. + +MLP lite v3.0 for OpenSim is almost a complete rewrite by onefang +rejected, of the venerable MLP scripts. It uses OpenSim specific +functions, so wont work in Second Life. It might not work on some +OpenSim grids or sims, due to the use of OpenSim specific "high threat +level" functions. Test it first before putting it in anything important. + +The main justification for this rewrite is that I have about a dozen +OpenSim sims, some with dozens of copies of MLP running. So this +variation uses some OpenSim specific LSL functions, tries to use less +resources, be quicker, and automatically shuts down when no one is +around. Some functions where removed, hence the "lite" in the name, most +of these seem to not be used, or useful, or just don't get used in my +sims. Note that I've not extensively tested any of this stuff yet. It's +not a huge improvement, but when you have dozens of the things, every +little bit helps. And then I added more features. lol + +Note that some of the OpenSim functions used are what OpenSim classes as +"high threat level". More about that below. This means that these +scripts wont work without some tweaking of your grid / sims +configuration. This has only been tested in my hacked up version of +OpenSim 0.8.2, which removes a lot of the insane threat levels and script +delays, so anything I say about speed improvements might not apply. I +did at least remove script delays in both LL and OS functions. If +nothing works for you, this is likely why, and you should go back to the +old scripts. Test it first before upgrading anything that's not full +perm. + +Based on the original MLP - MULTI-LOVE-POSE V1.2 - Copyright (c) 2006, by +Miffy Fluffy (BSD License). This code has bounced around Second Life and +OpenSim for over a decade, with various people working on it. I can't +compile a complete list, as I have no idea of the complete history of the +versions I have received. The names I do know coz they where in the +source code (in no particular order) - Miffy Fluffy, Learjeff Innis, Jez +Ember, Liz Silverstein, Zonax Delorean, Purrcat Miranda, and Kokoro +Fasching. Apologies to anyone who's code is in this variation and I +haven't mentioned you. It's likely that some or all of the code written +by these people is no longer in this variation. Thanks to you all. + +This variation is still BSD licensed. The text of the BSD license wasn't +included in any of the versions I have, but it's a simple and standard +license, you should easily find copies all over the Internet, or a +simple search. The versions I have seen don't specify which variation of +the BSD license, so I wont either. GitHub insists on a license file, so +I went with 3 clause BSD. + +The next two sections of this document is mostly copied from the .readme +file I got with the latest MLP versions I had laying around. + +================================================ + +FEATURES: +- Put all your poseball animations into one object (110 pairs or even more +should be no problem). +- Create submenus for each category of poses, for instance: "Solo, +Boy-Girl, Girl-Girl, Dance, 3some, 4some" +- Shows up to nine poseballs depending on the submenu you select +- Positions can be adjusted and saved into memory or notecard. +- Portable, can be worn and used everywhere +- Option to adjust height offset (Z) of all poses at once (for different +persons/locations). +- 15 poseball colours: + PINK, BLUE, PINK2, BLUE2, GREEN, MAGENTA, RED, ORANGE, WHITE, BLACK, + YELLOW, CYAN, RED2, TEAL, GREEN2 + +OPERATION: +- Click the MLP object to switch it on. +- When everything has loaded the main menu will appear. +- Select a submenu containing poses, and select a pose. Pose balls will +appear. +- Sit on your pose ball (Right-click - LOVE). +- To remove the balls, select 'EXTRAS>>' then 'STOP', or just walk away, +it will shut down automatically when no one is around. + +TO ADJUST POSITIONS: +You can adjust the poses to fit your own avatar and those you share MLP +with. +- Select a pose you want to adjust and sit on the balls. +- Select 'EXTRAS>> ' then 'ADJUST'. This changes the balls into +translucent beams. +- Right-click a beam, select Edit and adjust the position (Shift-click to +select more than one). +- Position the balls anywhere within the sim. +- 'EXTRAS>>', 'OPTIONS>>', then 'Save Pos' stores the positions into a +new notecard. + +TO MAKE A BACKUP: +Saved positions are stored in memory but are not permanent. They are +lost on script error (See "Script run-time error / Stack-Heap Collision" +below). They are also lost on shutdown/startup, or "Pos Reset". To +back the positions up more permanently you have to copy them into a +new .POSITIONS notecard: +- Select 'EXTRAS>>', 'OPTIONS>>', then 'Save Pos' . +All original .POSITIONS* cards are copied to .backup.POSITIONS* cards. +Prop positions are saved to .PROPS, so the same applies. + +Note: After changing any *.POSITIONS* files, use the 'Pos Reset' command +to verify your changes, if desired. This also helps to avoid losing +changes due to Stack-Heap collisions. + +TO ADJUST HEIGHT OFFSET (Z): +Select 'EXTRAS>>', 'OPTIONS>>', then'Height>>' and click the 'Z'-buttons, +this will adjust the height for all poses. Note: the offset height is +stored in the objects Description, so any descriptions will be replaced. + +TO ADD POSES: +Copy all animations into the MLP object (if you want to use existing pose +balls, open them to rip their animations). Note: you can use any object +as MLP, just copy the MLP contents in the object of your choice. Open the +.MENUITEMS and add the animations: + +POSE name | animation1 | animation2 ... + +The changes will become active after MLP is (re)started. Use the 'Menu +Reset' command after changing +*.MENUITEMS* files. + +To give an expression to an animation, add one of the following suffixes +to the anim name in the POSE line. (Just add it to the POSE config line, +don't change the anim name.) + + Suffix / expression + * open mouth + ::1 open mouth + ::2 surprise + ::3 tongue out + ::4 smile + ::5 toothsmile + ::6 wink + ::7 cry + ::8 kiss + ::9 laugh + ::10 disdain + ::11 repulsed + ::12 anger + ::13 bored + ::14 sad + ::15 embarrassed + ::16 frown + ::17 shrug + ::18 afraid + ::19 worry + ::20 sleeping (combination of disdain and smile, closest I could find) + +To make the expression happen periodically rather than constantly, add +another extension and the period (in seconds). This is mostly needed for +those expressions that only last a short time. For example, to use MyAnim +with open mouth every 5.5 seconds: + + POSE Mypose | MyAnim::1::5.5 + +TO ADD SOUNDS (buttons to play sounds), in a menu (just like a POSE +button), add a line like this: + + SOUND She moans | female-moan + +where "She moans" will be the button label, and "female-moan" is the +sound to play, which must be in object inventory. For sounds in menus +with poses (rather than in a menu specifically for sounds) I recommend +you begin the pose name with "☊" (which looks a bit like headphones, the +best I could find for the purpose). This serves as a clue to the user +that the button plays a sound. + +TO CREATE ANIMATION SETS (menus): +Create .MENUITEMS.xxx and .POSITIONS.xxx files (where xxx is whatever you +want) and put the corresponding menu configs and poses in them. This +way you can deliver a bed with folders of add-on menus so the customer +can choose what types of anims they want to add to the menu. Note that +you get at most 12 top menu choices. The scripts read the .POSITIONS +files in alphabetical order (and +.POSITIONS must be first). + +This also allows you to sell furniture with "enhancement packs", which +are simply collections of .MENUITEMS.xxx, .POSITIONS.xxx, and the +associated animations for the customer to drop into the furniture. +Customers can easily select furniture appearance and pose bundles +independently! + +SWAPPING POSES: +Each menu can have a swap command: + + SWAP | 21 + +Would be the default. Note that this means before you hit the SWAP +button, the balls will be in the same order as listed in the MENU and +POSE commands, after they would be reversed. SWAP always assumes +the unswapped condition is the order things are listed. So 21 in the +above command means swap the first two balls. + +The default for more than two balls is to just fill out both strings with +the remaining integers: + + SWAP | 213456789 + +This might be used to swap the last two balls only: + + SWAP | 132 + +Any number of swap patterns can be used: + + SWAP | 132 | 321 | 312 + +You can also use a different name for the button: + + SWAP switch men | 213 + +Also, the original one should be supported, that uses the default: + + SWAP + +ACCESS TO THE MAIN MENU: +The owner is the only one who can shutdown, in all cases. Anyone +can start it. +- if 'MenuUser' is set to OWNER: the owner is the only one who can access +the menus +- if 'MenuUser' is set to ALL: anyone can access the MAIN MENU +- if 'MenuUser' is set to GROUP: members of the same Group as the MLP +object can access the +MAIN MENU (the MLP Group can set by right-clicking MLP and selecting +Edit, More >> General tab - Group: Set) Note: even if "MenuUser" is set +to ALL or GROUP, individual SUBMENUS can still be blocked (you can +define access for each submenu in .MENUITEMS, see examples in +.MENUITEMS). + +PORTABLE USE: +Attach the object to the HUD, you can use it's default shape and colour +for clickable bar on one of the edges of your screen (to move HUD +position: Right-click - Edit), you can edit +colour/transparency/size/position. Adjust the height offset (Z). Note: +the balls will appear relative to the initial MLP position (to reset +where the balls appear, press STOP to remove the balls, and reselect a +submenu to rez them again). + +================================================ + +FAQ for END USERS: + +- Will my animations be lost if I lose a poseball? + +No. The animations are not placed the balls, they remain in the main MLP +object. Don't worry about the poseballs, they are copies of the one in +the MLP object. A balls will commit suicide if left behind somewhere +(the MLP object needs to be within the same sim). + +- Sometimes notecards or scripts won't open for editing, why? + +If the MLP object contains many things, access to it's contents can be +slow, just keep waiting until it's all loaded. Can take over a minute +sometimes. + +- "Script run-time error / Stack-Heap Collision"? + +Right-click/Edit the object, and use SL menu: "Tools -> Reset Scripts in +Selection" to reset. Any saved positions that were not backed up in +.POSITIONS files are lost, so if your furniture has lots of poses (over +50) and you save positions, be sure to back up regularly. Use OPTIONS -> +Pos Reset after changing .POSITIONS* files. + +FAQ for those who edit *.MENUITEMS files: + +- "Script run-time error / Stack-Heap Collision"? + +After a restart, this is a clue that there are too many items in +*.MENUITEMS* or *.POSITIONS* files. Trim the menu. + +- My new menu appears on the main page, rather than as a submenu where I +configured it. Why? + +Most likely, you named it differently in the MENU line versus the TOMENU +line. When MPL sees a MENU line, it looks for the same name in a TOMENU +line. + +================================================ + +UPGRADE GUIDE: + +For the sake of these instructions, let's say you want to upgrade the +scripts in an object called "My Old Bed", this is what you would do. +These instructions assume that everything has copy permission. + +First rez My Old Bed in world somewhere. If it is already in world, take +a copy, in case things don't work out and you want to revert to the old +scripts. You could also move the scripts and things you are about to +replace to a new folder, so you can put things back the way they where +before. Or just ignore any backups and cross your fingers. + +Remove the old MLP scripts, they will be named ~memory, ~menu, ~menucfg, +~pos, ~pose, ~poser, ~poser 1, ~poser 2, ~poser 3, ~poser 4, ~poser 5, +~run, and you might also have optional scripts ~props, ~sequencer, and +~timer. Also remove the ~ball object. There may be another script, I +have never seen one, that deals with PRIMTOUCH, my guess is it might be +called ~primtouch. Leave it alone, this new script should be compatible +with it. Since I have never seen that script, nor needed it, I dunno +for sure. + +Drag the "~MLP lite for OpenSim" script into My Old Bed, and the new +~ball object. You should see "My Old Bed: OFF (touch to switch on)" in +chat. + +My Old Bed might have some props in it, they will be objects other than +~ball. In typical examples I have seen there is a pillow, so lets use +that as an example. Drag pillow out of My Old Bed and into your +inventory, into the same folder you are backing up everything else. +Remove pillow from My Old Bed. Drag pillow onto the ground, edit it, and +remove the ~prop script. Replace that script with the new ~ball script. +Take pillow, then drag it into My Old Bed. Add the "~MLP lite props" +script to My Old Bed. + +You may want to update your .MENUITEMS, or just leave them as is. Look +at the examples for how it is now recommended to have them setup. If you +do this take care of the OPTIONS menu, which is now called OPTIONS>> in +this new .MENUITEMS. + +If you still want to edit the poses / props, or do any of the other +tweaking, then you might want to add the "~MLP lite tools" script to My +Old Bed. Leave it out, or remove it, once My Old Bed is fully set up. + +================================================ + +DIFFERENCES FOR THE MLP lite v3.0 for OpenSim VARIATION: + +As mentioned above, this variation drops a few rarely used features, and +tries to make things a bit better for OpenSim users. Note that some of +the dropped features may be added back again in later versions, I dunno +yet. + +- Up to nine avatars can be animated at once. + +- Menus no longer lock out other users when someone else is using it. + +- The SWAP command has been changed to something like the XPOSE SWAP +command. It's documented above. The original MLP SWAP command works as +it used to. + +- Changing menus no longer reverts any SWAP commands. Though if the swap +command is different between menus, it wont let you swap again in the +different menu. + +- Anyone can start up an MLP lite object, and they automatically shutdown +if no one is around for a couple of minutes. This makes it easy to setup +public areas with publicly usable objects that don't soak up resources +when people forget to turn them off after using them. The startup also +prints the startup time. + +- After turning on an MLP object by touching it while it is turned off, +the MAIN MENU is shown automatically to the person that started it. I +assume that the person that turned it on is just waiting for it to finish +starting up so they can actually use it. Actually the menu shows after +loading menus, but before loading positions, but you can start to use it +straight away. + +- The CHECK command no longer reloads things. It also checks for +permissions and animations that are not used, as well as a few other +things. It's in the ~MLP lite tools script. + +- Less scripts. There are only two scripts now, "~MLP lite for OpenSim" +is the main script, and ~ball in the ~ball and prop objects. OpenSim +doesn't seem to have any memory limits per script (usually the main +reason to split scripts like that), and it simplifies the code a lot if +there is just one main script. The ~ball script is much simpler. Props +use the ~ball script instead of ~prop. There are two optional scripts, +"~MLP lite props" is used in the main MLP object if you have any props, +and "~MLP lite tools" is used by creators and editors if needed. + +- It may use a bit more CPU time when expressions are being used. You +may not notice. A lag tester was added to help with this, expressions +may slow down when it gets laggy. + +- Since we use some OpenSim functions that are considered high threat, +you might need to tweak your OpenSim settings. See below for details. + +- The default .MENUITEMS note card now includes an EXTRAS menu, for all +those functions that usually go at the bottom of each menu, so you can +fit more poses on each menu. I suggest that "TOMENU EXTRAS>>" be added +to the end of menus, along with "BACK" and maybe "SWAP" + +- Various creator and editing functions have been moved to the TOOLS>> +menu, and the "~MLP lite tools" script. You can leave this out if +the MLP object is fully set up. + +- You no longer need to have the first two lines of .MENUITEMS as the +"stand" and "default" POSE commands. It wont hurt to include them. + +- MENUORDER is no longer supported, menu buttons appear in the same order +they are on the .MENUITEMS* cards, coz that's the only sane way of doing +it. + +- The MAIN MENU, first menu in .MENUITEMS, no longer needs a bunch of +"TOMENU -" commands. + +- While the DUMP command still prints the positions in chat, SAVE now +saves that to a fresh .POSITIONS notecard, so you no longer have +to cut and paste to your old .POSITIONS cards. The old cards are +backed up, and the same applies to .PROPS cards that store props. + +- MLP lite remembers all editing of the pose balls, not just those done +while in ADJUST mode, ADJUST now just makes the pose balls easier to +edit. This makes editing of lots of poses a lot quicker, and means you +can make quick fixes on the fly, and save them at the end if you want to. + +- The BALLUSERS, OFF, REDO, INVISIBLE, STAND, SHOW, HIDE, and AGAIN +commands no longer exist. They are rarely used, or just not needed. + +- The code that slices up long menus into shorter ones with "BACK" and +"MORE", to fit in the Second Life 12 button menu limit has all been +removed. I was gonna rewrite that, but for various reasons decided to +just drop it completely. People can chop up their own submenus if they +need this, and that's exactly what I have seen other furniture makers do. +As a bonus, there are no hard coded English menu or button names left in +the code, so you can easily use MLP lite for other languages. On the +other hand, all the messages are in English. CHECK will let you know if +your menus are too long. + +- Old ~prop scripts are not supported, use the new ~ball script instead, +which means a bit of surgery needed on old props. + +- Sequences are not currently supported. + +- The REORIENT command, and various link messages, are not currently +supported. Some or all of these may come back. Some of the existing +link messages changed as well. Combining the scripts meant that a lot +of these link messages are no longer needed, but some external scripts +may depend on them. + +- ZHAO support is gone. None of the ZHAO based AOs I have seen in +OpenSim support that. Note that the ~MLPT-AutoZhao script says "This +one supports AutoZhao, which is a ZHAO variant that turns of +automatically when you sit." Which seems pointless, it's already doing +the correct thing. My own AO / swimmer does the correct thing to. + +- No Xcite support. Xcite doesn't exist in OpenSim. + +- The STOP command now just stops all animations and puts away the balls, +instead of switching to the "stand" animation. + +- Strided lists are used internally, in theory should use a little less +memory than having separate lists for everything. + +- osMessageObject() is used instead of llSay() to communicate with the +pose balls. This will stop any cross talk issues, and save some script +running time. As a bonus, balls can be anywhere in the sim. + +- The OpenSim notecard reading functions are used instead of the clunky +Second Life dataserver() functions. Faster and cleaner code. + +- While the PRIMTOUCH command is still supported, it's script isn't here. +I never did have a copy of that script, and haven't needed to use it +anyway. + +- There may be a thing or two I forgot to mention here. + +================================================ + +OPENSIM THREAT LEVELS: + +OpenSim developers introduced a concept of "threat levels" for their new +os* LSL functions. In my not so humble opinion, they where very +paranoid in assigning threat levels. For this reason, these scripts may +or may not work in any given grid or sim. In my version of OpenSim I +have removed or reduced the more insane threat levels, so YMMV. + +Refer to the OpenSim documentation for how to deal with this, or ask the +person that runs your OpenSim grid / sim. + +- osAvatarPlayAnimation() and osAvatarStopAnimation() +Threat level: VeryHigh, disabled by default. + +These two functions can animate any avatar with any animation in the +objects inventory, without asking permission. So I guess this threat +level is warranted. Also means one script can manage animations for more +than one avatar, a limitation of the LL variety. + +Used to get rid of one of the annoying aspects of how MLP works, which +often confuses newbies. Also allowed me to reduce the code complexity +and get rid of all those ~poser X scripts. + +- osGetRezzingObject() +Threat level: none, enabled by default. + +Wow, no threat level, the only one. lol + +Used to reduce the complexity of the communication between the MLP object +and the balls. + +- osGetNumberOfNotecardLines(), osGetNotecard(), and osGetNotecardLine() +Threat level: VeryHigh, only enabled for estate owners and managers by +default. + +A faster and less complex way of being able to do what you could always +do, gets a VeryHigh threat level? WTF are the OpenSim developers +smoking? + +Used to speed things up and reduce code complexity, exactly what they +where designed for. + +- osMakeNotecard() +Threat level: High, only enabled for estate owners and managers by +default. + +Slightly less WTF than the other notecard functions, but still. Allows +to script what the object owner can do manually, why is that a high +threat? + +Saves having to cut and paste to notecards after editing ball and prop +positions. + +-osMessageObject() +Threat level: Low, only enabled for estate owners and managers by +default. + +More WTF, allows to send messages between objects better. Nothing you +couldn't do before. + +Used to reduce the complexity and increase the reliability of messages +between the MLP object and balls / props. + +-osReplaceString() +Threat level: VeryLow , enabled by default. + +Why isn't this threat level none? + +Used to reduce script complexity. + +-osSetPrimitiveParams() +Threat level: High, disabled by default. + +Once again, allows scripting of something that can be done manually, and +only works on objects the script owner actually owns. With that later +restriction, total WTF. + +Used to reduce complexity and speed things up. + 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 @@ +// +// MLP lite v3.0 for OpenSim +// Based on the original MLP - MULTI-LOVE-POSE V1.2 - Copyright (c) 2006, by Miffy Fluffy (BSD License) +// This code has bounced around the Second Life and OpenSim for over a decade, with various people working on it. +// MLP lite for OpenSim is almost a complete rewrite by onefang rejected. + +string Version = "MLP lite v3.0 alpha"; + +list COLOUR_NAMES = +[ + "HIDE", "PINK", "BLUE", "PINK2", + "BLUE2", "GREEN", "MAGENTA", "RED", + "ORANGE", "WHITE", "BLACK", "YELLOW", + "CYAN", "RED2", "TEAL", "GREEN2" +]; + +list COLOURS = +[ + <0.0,0.0,0.0>, // 0 = HIDE + <0.835,0.345,0.482>, // 1 = PINK + <0.353,0.518,0.827>, // 2 = BLUE + <0.635,0.145,0.282>, // 3 = PINK2 - Dark pink + <0.153,0.318,0.627>, // 4 = BLUE2 - Dark blue + <0.128,0.500,0.128>, // 5 = GREEN + <1.000,0.000,1.000>, // 6 = MAGENTA + <1.000,0.000,0.000>, // 7 = RED + <1.000,0.500,0.000>, // 8 = ORANGE + <1.000,1.000,1.000>, // 9 = WHITE + <0.0,0.0,0.0>, // 10 = BLACK + <1.0,1.0,0.0>, // 11 = YELLOW + <0.0,0.8,0.8>, // 12 = CYAN + <0.5,0.0,0.0>, // 13 = RED2 + <0.0,0.5,0.5>, // 14 = TEAL + <0.0,0.25,0.25> // 15 = GREEN2 +]; + +list EXPRESSIONS = +[ + "", + "express_open_mouth", + "express_surprise_emote", + "express_tongue_out", + "express_smile", + "express_toothsmile", + "express_wink_emote", + "express_cry_emote", + "express_kiss", + "express_laugh_emote", + "express_disdain", + "express_repulsed_emote", + "express_anger_emote", + "express_bored_emote", + "express_sad_emote", + "express_embarrassed_emote", + "express_frown", + "express_shrug_emote", + "express_afraid_emote", + "express_worry_emote", + "SLEEP" +]; + +key Owner; +integer Channel; +integer Chat = TRUE; +integer Redo = TRUE; +integer ReloadOnRez = TRUE; + + +integer LoadMenu = TRUE; +list ToMenus = []; +list SeenMenus = []; +key User0; +integer People = 0; +integer MenuUsers = 0; +string CurrentMenu = ""; +integer ThisSwap; +string Filter = "123456789"; +string LastFilter = "123456789"; +// NOTE - This one uses \n to separate sub lists, coz | is used in some of the commands. +list Menus; // List of menus. +integer MENU_NAME = 0; // Name of menu. +integer MENU_AUTH = 1; // Authorised users of menu - 0 = owner, 1 = group, 2 = all +integer MENU_COLOURS = 2; // \n spearated list of ball colours. +integer MENU_ENTRIES = 3; // \n separated list of entries. +integer MENU_CMDS = 4; // \n separated list of commands, matching the entries. +integer MENU_SWAPS = 5; // \n separated list of colour sets. +integer MENU_STRIDE = 6; + +integer LoadPos = TRUE; +vector RefPos; +rotation RefRot; +string Pose = ""; +list Poses; // List of poses. +integer POSE_NAME = 0; // Name of pose. +integer POSE_ANIM = 1; // | separated animations. +integer POSE_EMOTE = 2; // | separated emotions and timers list. +integer POSE_POSROT = 3; // | separated posiiton and rotation pairs. +integer POSE_STRIDE = 4; + +integer Lag = 45; // Assume we start with no lag. +float Tick = 0.2; // Shortest tick for expressions. +list Exps; // List of current expressions playing on avatars. +integer EXP_AVATAR = 0; // Avatar to do expression on. +integer EXP_EXP = 1; // Name of expression. +integer EXP_TIME = 2; // Time for expression loop. +integer EXP_NEXT = 3; // Time for next trigger. +integer EXP_STRIDE = 4; + +integer LoadProp = TRUE; + +list Musers; // List of menu users. +integer MUSER_AVATAR = 0; // Key of user. +integer MUSER_CURRENT = 1; // Current menu of user. +integer MUSER_STACK = 2; // | separated menu stack list. +integer MUSER_STRIDE = 3; + +integer MaxBalls = 1; // One as a bare minimum, for a single person. No point if there are zero balls. +integer BallCount; +integer Adjusting; +list Balls; // Ball / prop tracker. +integer BALL_NUM = 0; // The number of the ball, negative numbers for the props. +integer BALL_KEY = 1; // The UUID of the ball / prop. +integer BALL_AVATAR = 2; // The UUID of any avatar sitting on the ball. +integer BALL_STRIDE = 3; + +list Sounds; +integer SOUND_NAME = 0; +integer SOUND_INV = 1; +integer SOUND_STRIDE = 2; + +// TODO - Should stride this to. +list LMButtons; +list LMParms; + +string LIST_SEP = "$!#"; // Used to seperate lists when sending them as strings. + +// The various link messages. The first lot are historical. +//integer OLD_TOUCH = -1; // msg = "PRIMTOUCH" +integer OLD_POSEB = 0; // msg = "POSEB" id = pose name +// // Also CHECK1 and CHECK2 instead of pose name. +integer OLD_STOP = 1; // msg = "STOP" +//integer OLD_REF = 8; // msg = RefPos id = RefRot +integer OLD_SITS = -11000; // msg = ball number|anim name id = avatar key +integer OLD_STANDS = -11001; // msg = ball number id = avatar key +integer OLD_ANIM = -11002; // msg = ball number|anim name id = avatar key +//integer SEQ_CMD = -12001; // msg = ~sequencer command +integer OLD_CMD = -12002; // msg = menu command for ~MLP id = user key + +integer MLP_CMD = -13001; // A command that ~MLP dealt with, so other scripts can hook it. +integer TOOL_DATA = -13002; // Get Menus, Poses, Props, or Sounds list. +integer MLP_DATA = -13003; // Set Menus, Poses, Props, or Sounds list. +integer MLP_POSE = -13004; // doPose(pose, type); +integer MLP_UNKNOWN = -13005; // ~MLP doesn't know this command, might be for other scripts. + +// Types for doPose(), +integer POSES_POSE = -14001; // Change pose, even if it's changing to the same pose. +integer POSES_SWAP = -14002; // Swap poses. +integer POSES_DUMP = -14003; // Dump or save poses. +integer POSES_Z = -14004; // Change height. + +integer listFindString(list lst, string name, integer stride) +{ + integer f = llListFindList(lst, [name]); + integer ix = f / stride; + + // Round to nearest stride. + ix = ix * stride; + + // Sanity check, make sure we found a name, not something else, else do it the slow way. + if ((-1 != f) && (ix != f)) + { + integer l = llGetListLength(lst); + integer i; + + f = -1; + for (i = 0; i < l; i += stride) + { + if (llList2String(lst, i) == name) + { + f = i; + i = l; + } + } + } + return f; +} + +integer listFindWord(list lst, string s) +{ + integer l = llGetListLength(lst); + integer i; + + for (i = 0; i < l; i++) + { + list w = llParseStringKeepNulls(llList2String(lst, i), [" "], []); + + if (-1 != llListFindList(w, s)) + return TRUE; + } + return FALSE; +} + +say(string str) +{ + if (Chat) + { + if (MenuUsers) llWhisper(0, str); + else llOwnerSay(str); + } +} + +// Only used in one place, but leave it here for now. +string prStr(string str) +{ + integer ix = llSubStringIndex(str, ">"); + vector p = ((vector) llGetSubString(str, 0, ix) - RefPos) / RefRot; + vector r = llRot2Euler((rotation) llGetSubString(str, ix + 1, -1) / RefRot) * RAD_TO_DEG; + + // OpenSim likes to swap these around, which triggers the ball movement saving. + if (-179.9 >= r.x) r.x = 180.0; + if (-179.9 >= r.y) r.y = 180.0; + if (-179.9 >= r.z) r.z = 180.0; + + return "<" + round(p.x, 3) + "," + round(p.y, 3) + "," + round(p.z, 3) + + "><" + round(r.x, 1) + "," + round(r.y, 1) + "," + round(r.z, 1) + ">"; +} + +string round(float number, integer places) +{ + float shifted; + integer rounded; + string s; + + shifted = number * llPow(10.0, (float) places); + rounded = llRound(shifted); + s = (string) ((float) rounded / llPow(10.0, (float)places)); + rounded = llSubStringIndex(s, "."); + if (-1 != rounded) + s = llGetSubString(s, 0, llSubStringIndex(s, ".") + places); + else + { + s += ".00000000"; + s = llGetSubString(s,0,llSubStringIndex(s, ".") + places); + } + return s; +} + +setRunning(integer st) +{ + integer i = llGetInventoryNumber(INVENTORY_SCRIPT); + + while (i-- > 0) + { + string s = llGetInventoryName(INVENTORY_SCRIPT, i); + + if ((llSubStringIndex(s, "~MLP lite ") == 0) && (llGetScriptName() != s)) + { + llSetScriptState(s, st); + if (st) + llResetOtherScript(s); + } + } + if (st) + llSetTimerEvent(120.0); + CurrentMenu = ""; + doPose("", POSES_POSE, TRUE); +} + +stop() +{ + llMessageLinked(LINK_SET, OLD_STOP, "STOP", NULL_KEY); + Adjusting = FALSE; + CurrentMenu = ""; + doPose("", POSES_POSE, TRUE); + Balls = []; + Exps= []; +} + +readMenu(list cards) +{ + integer i; + integer l = llGetListLength(cards); + + cards = llListSort(cards, 1, TRUE); + for (i = 0; i < l; i++) + { + string menu = ""; + string card = llList2String(cards, i); + list crd = llParseStringKeepNulls(osGetNotecard(card), ["\n"], []); + integer m = llGetListLength(crd); + integer j; + integer k; + + llOwnerSay("Reading '" + card + "'."); + for (j = 0; j < m; j++) + { + string data = llList2String(crd, j); + string filter = ""; + integer ix = llSubStringIndex(data, "/"); // remove comments + + if (ix != -1) + { + if (ix == 0) data = ""; + else data = llGetSubString(data, 0, ix - 1); + } + data = llStringTrim(data, STRING_TRIM); + if (data != "") + { + string cmd = data; + string mdata = data; + + ix = llSubStringIndex(data, " "); + if (ix != -1) + { + cmd = llStringTrim(llGetSubString(data, 0, ix - 1), STRING_TRIM); + data = llStringTrim(llGetSubString(data, ix + 1, -1), STRING_TRIM); + mdata = data; + } + else + mdata = ""; + + string mcmd = cmd; + list ldata = llParseStringKeepNulls(data, ["|"], []); + + ix = llGetListLength(ldata); + for (k = 0; k < ix; k++) + ldata = llListReplaceList(ldata, [llStringTrim(llList2String(ldata, k), STRING_TRIM)], k, k); + data = llDumpList2String(ldata, "|"); + + string arg1 = llList2String(ldata, 0); + + if (cmd == "MENU") + { + integer auth; + string colours = ""; + + menu = arg1; + if (llList2String(ldata, 1) == "GROUP") auth = 1; + else if (llList2String(ldata, 1) != "OWNER") auth = 2; + for (k = 0; k < 9; ++k) // Maximum of 9 balls supported. + { + string bc = llList2String(ldata, k + 2); + + if ("" != bc) + { + colours += "\n" + bc; + if ((k + 1) > MaxBalls) + MaxBalls = k + 1; + } + } + saveMenu(menu, auth, colours, "", "", ""); + ToMenus += [menu]; + } + else if (cmd == "NORELOAD") + ReloadOnRez = (arg1 != "0"); + else + { // The rest are entries within a menu. + if (cmd == "POSE") + { + string thisPose = llStringTrim(llList2String(ldata, 0), STRING_TRIM); + string anims = ""; + string exps = ""; + + ix = llGetListLength(ldata); + for (k = 1; k < ix; k++) + { + string anim = llList2String(ldata, k); + string exp; + float expTime; + + if (anim == "") anim = "sit_ground"; + + if (llGetSubString(anim, -1, -1) == "*") + { + exp = llList2String(EXPRESSIONS, 1); + expTime = 0.5; + anim = llGetSubString(anim, 0, -2); + } + else + { + integer a = llSubStringIndex(anim, "::"); + + if (a == -1) + { + exp = ""; + expTime = 0.5; + } + else + { + list parms = llParseString2List(anim, ["::"], []); + integer p = (integer) llList2String(parms, 1); + + anim = llList2String(parms, 0); + exp = llList2String(EXPRESSIONS, p); + expTime = (float) llList2String(parms, 2); + + if (expTime <= 0.0) + expTime = 0.5; + } + } + anims += "|" + anim; + exps += "|" + exp + "::" + (string) expTime; + } + anims = llGetSubString(anims, 1, -1); + exps = llGetSubString(exps, 1, -1); + savePose(thisPose, anims, exps, ""); + mcmd = "POSE"; + mdata = thisPose; + } + else if (cmd == "CHAT") + { + if (llList2String(ldata, 1) != "OFF") + Chat = 1; + } + else if (cmd == "MENUUSERS") + { + if (llList2String(ldata, 1) == "GROUP") + MenuUsers = 1; + else if (llList2String(ldata, 1) != "OWNER") + MenuUsers = 2; + } + else if (cmd == "LINKMSG") + { + LMButtons += arg1; + LMParms += llList2String(ldata, 1); + } + else if (cmd == "SOUND") + Sounds += [arg1, llList2String(ldata, 1)]; + else if (cmd == "SWAP") + { + list sl = llListReplaceList(ldata, [], 0, 0); + + if ("" == arg1) + arg1 = "SWAP"; + if ("SWAP" == data) + sl = ["21"]; + ix = llGetListLength(sl); + if (ix == 0) + { + sl = ["21"]; + ix = 1; + } + + for (k = 0; k < ix; k++) + { + string s = llList2String(sl, k); + integer ls = llStringLength(s); + + s += llGetSubString("213456789", ls, -1); + sl = llListReplaceList(sl, [s], k, k); + } + if (ix) + filter = llDumpList2String(sl, "\n"); + } + else if (cmd == "TOMENU") + { + if ("-" != arg1) + SeenMenus += [arg1]; + } + ix = findMenu(menu); + if ((-1 != ix) && ("-" != arg1)) + { + saveMenu(menu, + llList2Integer(Menus, ix + MENU_AUTH), + "", + llList2String(Menus, ix + MENU_ENTRIES) + "\n" + arg1, + llList2String(Menus, ix + MENU_CMDS) + "\n" + mcmd + " " + mdata, + filter); + } + } + } + } + } +} + +integer findMenu(string name) +{ + return listFindString(Menus, name, MENU_STRIDE); +} + +saveMenu(string name, integer auth, string colours, string entries, string commands, string swaps) +{ + integer f = findMenu(name); + + if ("\n" == llGetSubString(colours, 0, 0)) + colours = llGetSubString(colours, 1, -1); + if ("\n" == llGetSubString(entries, 0, 0)) + entries = llGetSubString(entries, 1, -1); + if ("\n" == llGetSubString(commands, 0, 0)) + commands = llGetSubString(commands, 1, -1); + if ("\n" == llGetSubString(swaps, 0, 0)) + swaps = llGetSubString(swaps, 1, -1); + if (-1 == f) + Menus += [name, auth, colours, entries, commands, swaps]; + else + { + if ("" == colours) + colours = llList2String(Menus, f + MENU_COLOURS); + if ("" == entries) + entries = llList2String(Menus, f + MENU_ENTRIES); + if ("" == commands) + commands = llList2String(Menus, f + MENU_CMDS); + if ("" == swaps) + swaps = llList2String(Menus, f + MENU_SWAPS); + Menus = llListReplaceList(Menus, [name, auth, colours, entries, commands, swaps], f, f + MENU_STRIDE - 1); + } +} + +vector getBallColour(integer b) +{ + integer f = findMenu(CurrentMenu); + + if (-1 != f) + return llList2Vector(COLOURS, llListFindList(COLOUR_NAMES, + llList2String(llParseStringKeepNulls(llList2String(Menus, f + MENU_COLOURS), ["\n"], []), b))); + return <1.0, 1.0, 1.0>; +} + +unauth(key id, string button, string who) +{ + llDialog(id, "\n" + button + " menu allowed only for " + who, ["OK"], -1); +} + +touched(key avatar) +{ + if (avatar == Owner || (MenuUsers == 1 && llSameGroup(avatar)) || MenuUsers == 2) + { + saveMuser(avatar, llList2String(Menus, 0 + MENU_NAME), ""); + doMenu(avatar); + } +} + +doMenu(key id) +{ + integer f = listFindString(Musers, id, MUSER_STRIDE); + + if (-1 != f) + { + string menu = llList2String(Musers, f + MUSER_CURRENT); + integer m = findMenu(menu); + + if (-1 != m) + { + integer i = llList2Integer(Menus, m + MENU_AUTH); + + if (i > MenuUsers) + i = MenuUsers; + if (id == Owner || (i == 1 && llSameGroup(id)) || i == 2) + { + list entries = llParseStringKeepNulls(llList2String(Menus, m + MENU_ENTRIES), ["\n"], []); + list cmds = llParseStringKeepNulls(llList2String(Menus, m + MENU_CMDS), ["\n"], []); + string title = menu; + + if ((0 == m) || listFindWord(cmds, "POSE")) + { + if ("" != Pose) + { + if (menu == CurrentMenu) + title += "\nCurrent pose is " + Pose; + else + title += "\nCurrent pose is " + Pose + " in menu " + CurrentMenu; + } + if ("123456789" != Filter) + title += "\nPositions are swapped."; + } + if (listFindWord(cmds, "ADJUST")) + { + title += "\nAdjusting is o"; + if (Adjusting) title += "n."; else title += "ff."; + } + if (listFindWord(cmds, "CHAT")) + { + title += "\nChat is o"; + if (Chat) title += "n."; else title += "ff."; + } + if (listFindWord(cmds, "MENUUSERS")) + { + title += "\nMenu users are "; + if (0 == MenuUsers) + title += "OWNER."; + if (1 == MenuUsers) + title += "GROUP."; + if (2 == MenuUsers) + title += "ALL."; + } + llDialog(id, Version + "\n\n" + title, + llList2List(entries, -3, -1) + + llList2List(entries, -6, -4) + + llList2List(entries, -9, -7) + + llList2List(entries, -12, -10), + Channel); + } + else + { + if (i == 1) unauth(id, menu, "group"); + else unauth(id, menu, "owner"); + } + } + else + llSay(0, "'" + menu + "' menu not found!"); + } +} + +readPos(list cards) +{ + integer i; + integer l = llGetListLength(cards); + + cards = llListSort(cards, 1, TRUE); + for (i = 0; i < l; i++) + { + string card = llList2String(cards, i); + list crd = llParseStringKeepNulls(osGetNotecard(card), ["\n"], []); + integer m = llGetListLength(crd); + integer j; + + llOwnerSay("Reading '" + card + "'."); + for (j = 0; j < m; j++) + { + string data = llList2String(crd, j); + + if (llGetSubString(data, 0, 0) != "/") + { // skip comments + data = llStringTrim(data, STRING_TRIM); + integer ix = llSubStringIndex(data, "{"); + integer jx = llSubStringIndex(data, "} <"); + + if (ix != -1 && jx != -1) + { + string name = llStringTrim(llGetSubString(data, ix + 1, jx - 1), STRING_TRIM); + string ldata = llGetSubString(data, jx + 2, -1); + list posrots = llParseString2List(ldata, ["<"], []); + string pr = ""; + + jx = llGetListLength(posrots); + for (ix = 0; ix < jx; ix += 2) + pr += "|<" + llStringTrim(llList2String(posrots, ix), STRING_TRIM) + "<" + + llStringTrim(llList2String(posrots, ix + 1), STRING_TRIM); + savePose(name, "", "", llGetSubString(pr, 1, -1)); + } + } + } + } +} + +integer findPose(string name) +{ + return listFindString(Poses, name, POSE_STRIDE); +} + +savePose(string name, string anim, string exp, string posRot) +{ + integer f = findPose(name); + + if (-1 != f) + { + if ("" == anim) + { + anim = llList2String(Poses, f + POSE_ANIM); + exp = llList2String(Poses, f + POSE_EMOTE); + } + else if ("" == posRot) + posRot = llList2String(Poses, f + POSE_POSROT); + Poses = llListReplaceList(Poses, [name, anim, exp, posRot], f, f + POSE_STRIDE - 1); + } + else + Poses += [name, anim, exp, posRot]; +} + +stopAnims(key avatar) +{ + if (NULL_KEY != avatar) + { + list anims = llGetAnimationList(avatar); + integer l = llGetListLength(anims); + integer i; + + for (i = 0; i < l; i++) + { + string anim = llList2String(anims, i); + + if (anim != "") + osAvatarStopAnimation(avatar, anim); + } + } +} + +checkTicks() +{ + if (llGetListLength(Exps)) + { + float dil = llGetRegionTimeDilation(); // Between 0 and 1. + float fps = llGetRegionFPS(); // Frames per second, up to 50. + integer newLag = (integer) (dil * fps); + + if (llAbs(Lag - newLag) > 9) + { + if (45 <= newLag) // none + Tick = 0.2; + else if (35 <= newLag) // little + Tick = 0.3; + else if (25 <= newLag) // medium + Tick = 0.5; + else if (15 <= newLag) // lots + Tick = 0.7; + else // way too much + Tick = 1.0; + Lag = newLag; + } + llSetTimerEvent(Tick); + } + else // There's no expressions running, so just use the "anyone there" timer. + llSetTimerEvent(120.0); +} + +string filterIt(string an) +{ + list anl = llParseStringKeepNulls(an, ["|"], []); + integer l = llGetListLength(anl); + integer lf = llStringLength(Filter); + integer i; + string anr = ""; + + for (i = 0; i < l; i++) + { + if (0 != i) + anr += "|"; + anr += llList2String(anl, ((integer) llGetSubString(Filter, i, i)) - 1); + } + + return anr; +} + +rezThing(string thing, string posRot, integer num) +{ + integer i = llSubStringIndex(posRot, ">"); + + llRezObject(thing, + ((vector) llGetSubString(posRot, 0, i)) * RefRot + RefPos, + ZERO_VECTOR, + llEuler2Rot((vector) llGetSubString(posRot, i + 1, -1) * DEG_TO_RAD) * RefRot, + num); +} + +// ball is either the ball number, or a POSES_* flag. +doPose(string newPose, integer ball, integer pass) +{ + list anl; + list eml; + list prl; + integer p = findPose(newPose); + integer l = llGetListLength(Balls); + integer i; + vector newRefPos = llGetPos(); + rotation newRefRot = llGetRot(); + + // A little bit inefficient, but keeps things a little simpler. + // Most of the time we pass this through a link message, so other scripts can know about it. + // The one time we don't is when we get that link message. + if (pass) + { + llMessageLinked(LINK_SET, MLP_POSE, (string) ball, newPose); + if ("" != newPose) // Deal with it anyway if it's the stop pose, otherwise that wont happen. + return; + } + llMessageLinked(LINK_SET, OLD_POSEB, "POSEB", newPose); + // The prStr() call below needs to use the old RefPos. + newRefPos.z += ((integer) llGetObjectDesc()) / 100.0; + if (-1 != p) + { + if ((POSES_POSE == ball) || (POSES_SWAP == ball) || (0 <= ball)) + { + if ("123456789" == Filter) + say("The pose is now '" + newPose + "'."); + else + say("The pose is now '" + newPose + "' swapped."); + } + // Filter the data to current SWAP. + anl = llParseStringKeepNulls(filterIt(llList2String(Poses, p + POSE_ANIM)), ["|"], []); + eml = llParseStringKeepNulls(filterIt(llList2String(Poses, p + POSE_EMOTE)), ["|"], []); + prl = llParseStringKeepNulls(filterIt(llList2String(Poses, p + POSE_POSROT)), ["|"], []); + } + + Exps = []; + i = 0; + if (0 <= ball) // A single ball, for when someone sits on it. + { + i = findBall(ball); + l = i + BALL_STRIDE; + } + for (; i < l; i += BALL_STRIDE) + { + integer b0 = llList2Integer(Balls, i + BALL_NUM); + integer isBall = (0 <= b0); + key k = llList2Key(Balls, i + BALL_KEY); + key a = llList2Key(Balls, i + BALL_AVATAR); + // Find out where the ball / prop is now, and rotation. + string bpr = prStr(llDumpList2String(llGetObjectDetails(k, [OBJECT_POS, OBJECT_ROT]), "")); + + if (isBall) // Props are handled in their own script. + { + integer b1 = ((integer) llGetSubString(LastFilter, b0, b0)) - 1; + integer b2 = ((integer) llGetSubString( Filter, b0, b0)) - 1; + + if ((POSES_POSE == ball) || (POSES_SWAP == ball) || (0 <= ball)) + stopAnims(a); + if (-1 != p) + { + string prn = llList2String(llParseStringKeepNulls(llList2String(Poses, p + POSE_POSROT), ["|"], []), b2); + integer ix = llSubStringIndex(prn, ">"); + + if (0 > ball) + { + // Deal with ball movements. + integer g = llListFindList(Poses, Pose); + integer m = g + POSE_POSROT; + string prOld = llList2String(Poses, m); + list nprl = llParseString2List(prOld, ["|"], []); + string result = llDumpList2String(llListReplaceList(nprl, [bpr], b1, b1), "|"); + + // Actually store the balls new position / rotation if it moved. + if (result != prOld) + { + llOwnerSay(" MOVEd ball " + b1 + ".\n\t old [" + prOld + "]\n\t new [" + result + "]"); + Poses = llListReplaceList(Poses, [result], m, m); + } + } + + // Deal with animations and expressions. + if ((POSES_POSE == ball) || (POSES_SWAP == ball) || (0 <= ball)) + { + list e = llParseStringKeepNulls(llList2String(eml, b0), ["::"], []); + + if (NULL_KEY != a) + { + string ani = llList2String(anl, b0); + + llMessageLinked(LINK_SET, OLD_ANIM, "" + i + "|" + newPose, a); + osAvatarPlayAnimation(a, ani); + if (llGetListLength(e)) + { + string exp = llList2String(e, 0); + + if ("" != exp) + Exps += [a, exp, llList2Float(e, 1), 0.0]; + } + } + } + // Move the ball. + if (POSES_DUMP != ball) + { + if (ix != -1) + { + vector pos = ((vector) llGetSubString(prn, 0, ix)) * newRefRot + newRefPos; + rotation rot = llEuler2Rot((vector) llGetSubString(prn, ix + 1, -1) * DEG_TO_RAD) * newRefRot; + + osSetPrimitiveParams(k, [PRIM_POSITION, pos, PRIM_ROTATION, rot]); + } + } + } + } + } + // The rezThing() call below needs the new position. + RefPos = newRefPos; + RefRot = newRefRot; + checkTicks(); + + // Delete / create balls as needed. + if ((POSES_POSE == ball) || (0 <= ball)) + { + i = 0; + p = findMenu(CurrentMenu); + if (-1 != p) + i = llGetListLength(llParseStringKeepNulls(llList2String(Menus, p + MENU_COLOURS), ["\n"], [])); + if (BallCount != i) + { + while (BallCount > i) + { + --BallCount; + stopAnims(llList2Key(Balls, BallCount + BALL_AVATAR)); + setBall(BallCount, "DIE"); + p = findBall(BallCount); + if (-1 != p) + Balls = llListReplaceList(Balls, [], p, p + BALL_STRIDE - 1); + } + + while (BallCount < i) + { + rezThing("~ball", llList2String(prl, BallCount), BallCount); + BallCount++; + } + } + } + Pose = newPose; + LastFilter = Filter; +} + +integer findBall(integer num) +{ + integer f = llListFindList(Balls, [num]); + integer ix = f / BALL_STRIDE; + + // Round to nearest stride. + ix = ix * BALL_STRIDE; + + if ((-1 != f) && (ix == f)) // Sanity check, make sure we found a chan, not something else. + return f; + else + return -1; +} + +integer findBallByID(key id) +{ + integer f = llListFindList(Balls, [id]); + integer ix = f / BALL_STRIDE; + + // Round to nearest stride. + ix = ix * BALL_STRIDE; + + if ((-1 != f) && ((ix + BALL_KEY) == f)) // Sanity check, make sure we found a UUID, not something else. + return f - BALL_KEY; + else + return -1; +} + +saveBall(integer num, key id, key avatar) +{ + integer f = findBall(num); + + if (-1 == f) + Balls += [num, id, avatar]; + else + Balls = llListReplaceList(Balls, [num, id, avatar], f, f + BALL_STRIDE - 1); +} + +key getBall(integer num) +{ + integer f = findBall(num); + + if (-1 != f) + return llList2Key(Balls, f + BALL_KEY); + else + return NULL_KEY; +} + +setBall(integer num, string cmd) +{ + key k = getBall(num); + + if (NULL_KEY != k) + osMessageObject(k, cmd); + else + llOwnerSay("Missed ball command - " + cmd); +} + +setBalls(string cmd) +{ + integer i; + + for (i = 0; i < BallCount; ++i) + setBall(i, cmd); +} + +saveMuser(key avatar, string current, string stack) +{ + integer f = listFindString(Musers, avatar, MUSER_STRIDE); + + if (-1 == f) + Musers += [avatar, current, stack]; + else + Musers = llListReplaceList(Musers, [avatar, current, stack], f, f + MUSER_STRIDE - 1); +} + +// return TRUE if caller should doMenu() +integer handleCmd(key id, string button, integer isMenu) +{ + string cmd = button; + string data = cmd; + string menu = CurrentMenu; + list lst; + integer m; + integer f; + integer i; + + if (isMenu) + { + f = listFindString(Musers, id, MUSER_STRIDE); + if (-1 != f) + { + menu = llList2String(Musers, f + MUSER_CURRENT); + m = findMenu(menu); // This was already checked before it was stuffed into Musers. + lst = llParseStringKeepNulls(llList2String(Menus, m + MENU_CMDS), ["\n"], []); + i = llListFindList(llParseStringKeepNulls(llList2String(Menus, m + MENU_ENTRIES), ["\n"], []), [button]); + data = llList2String(lst, i); + cmd = data; + + i = llList2Integer(Menus, m + MENU_AUTH); + if (i > MenuUsers) + i = MenuUsers; + if (!(id == Owner || (i == 1 && llSameGroup(id)) || i == 2)) + return FALSE; + } + } + + i = llSubStringIndex(data, " "); + if (-1 != i) + { + cmd = llGetSubString(data, 0, i - 1); + data = llStringTrim(llGetSubString(data, i + 1, -1), STRING_TRIM); + } + else + data = ""; + + if ("POSE" == cmd) + { + if (isMenu) CurrentMenu = menu; + doPose(data, POSES_POSE, TRUE); + } + else if ("TOMENU" == cmd) + { + if ("" == data) + { + i = m; + m = TRUE; + } + else + { + i = findMenu(data); + m = FALSE; + } + if (-1 != i) + { + if (isMenu) + saveMuser(id, data, menu + "|" + llList2String(Musers, f + MUSER_STACK)); + else + { + if (m) + { + if (Redo) doMenu(id); + } + else + CurrentMenu = data; + } + } + } + else if (("BACK" == cmd) && isMenu) + { + lst = llParseStringKeepNulls(llList2String(Musers, f + MUSER_STACK), ["|"], []); + saveMuser(id, llList2String(lst, 0), llDumpList2String(llDeleteSubList(lst, 0, 0), "|")); + } + else if ("SWAP" == cmd) + { + data = llList2String(Menus, m + MENU_SWAPS); + if ((menu == CurrentMenu) || (llList2String(Menus, findMenu(CurrentMenu) + MENU_SWAPS) == data)) + { + // The first one is always the normal order, so users don't set that in .MENUITEM cards. + list swaps = ["123456789"] + llParseStringKeepNulls(data, ["\n"], []); + + ++ThisSwap; + if (llGetListLength(swaps) <= ThisSwap) + ThisSwap = 0; + LastFilter = Filter; + Filter = llList2String(swaps, ThisSwap); + doPose(Pose, POSES_SWAP, TRUE); + } + else + llSay(0, "The current pose is from another menu with a different SWAP command, cant swap."); + } + else if ("ADJUST" == cmd) + { + Adjusting = ! Adjusting; + f = llGetListLength(Balls); + + for (i = 0; i < f; i += BALL_STRIDE) + { + integer b = llList2Integer(Balls, i + BALL_NUM); + integer isBall = (0 <= b); + key k = llList2Key(Balls, i + BALL_KEY); + key a = llList2Key(Balls, i + BALL_AVATAR); + vector c = getBallColour(b); + + if (Adjusting) + { + if (NULL_KEY == a) + osSetPrimitiveParams(k, [PRIM_COLOR, ALL_SIDES, c, 1.0, + PRIM_SIZE, <0.2, 0.2, 0.2>, PRIM_TEXT, "Adjust", <1.0, 1.0, 1.0>, 1.0]); + else + osSetPrimitiveParams(k, [PRIM_COLOR, ALL_SIDES, c, 0.15, + PRIM_SIZE, <0.1, 0.1, 5.0>, PRIM_TEXT, "Adjust", <1.0, 1.0, 1.0>, 1.0]); + } + else + { + if (NULL_KEY == a) + osSetPrimitiveParams(k, [PRIM_COLOR, ALL_SIDES, c, 1.0, + PRIM_SIZE, <0.2, 0.2, 0.2>, PRIM_TEXT, "Love", <1.0, 1.0, 1.0>, 1.0]); + else + osSetPrimitiveParams(k, [PRIM_COLOR, ALL_SIDES, c, 0.0, + PRIM_SIZE, <0.01, 0.01, 0.01>, PRIM_TEXT, "", <1.0, 1.0, 1.0>, 1.0]); + } + } + } + else if ("CHAT" == cmd) + { + Chat = !Chat; + if (Chat) llSay(0, button + " ON"); else llSay(0, button + " OFF"); + } + else if ("MENUUSERS" == cmd) + { + MenuUsers++; + if (3 <= MenuUsers) MenuUsers = 0; + say(button + llList2String(["OWNER", "GROUP", "ALL"], MenuUsers) + " can use the menus."); + } + else if ("LINKMSG" == cmd) + { // Send LM to a non-MLP script. + i = llListFindList(LMButtons, [button]); + if (i != -1) + { + lst = llCSV2List(llList2String(LMParms, i)); + llMessageLinked( + llList2Integer(lst, 1), // destination link number + llList2Integer(lst, 2), // 'num' arg + llList2String(lst, 3), // 'str' arg + id); // key arg + if (llList2Integer(lst,0)) // inhibit remenu? + return FALSE; // yes, bug out + } + } + else if ("SOUND" == cmd) + { + i = llListFindList(Sounds, [button]); + if (-1 != i) + llPlaySound(llList2String(Sounds, i + SOUND_INV), 1.0); + } + else if ("TOOLS" == cmd) + { + if (llGetInventoryType("~MLP lite tools") == INVENTORY_SCRIPT) + { + llMessageLinked(LINK_SET, MLP_UNKNOWN, cmd, Owner); + return FALSE; + } + else + llOwnerSay("Please install the '~MLP lite tools' script for this to work."); + } + else if ("REDECORATE" == cmd || "RELOAD" == cmd || "RESET" == cmd || "RESTART" == cmd || "STOP" == cmd) + { + say(button); + stop(); + LoadMenu = ("RESET" == cmd); + LoadPos = ("RELOAD" == cmd); + LoadProp = ("REDECORATE" == cmd); + if ("RESTART" == cmd) + llResetScript(); + else if ("STOP" == cmd) + return FALSE; + else + state load; + } + else + { + if (isMenu) + llOwnerSay("Unknown menu command '" + cmd + "' from -\n\t" + button); + else + llOwnerSay("Unknown command '" + cmd + "' from -\n\t" + button); + llMessageLinked(LINK_SET, MLP_UNKNOWN, cmd + " " + data, id); + return FALSE; + } + llMessageLinked(LINK_SET, MLP_CMD, cmd + " " + data, id); + return TRUE; +} + + +default +{ + state_entry() + { + Owner = llGetOwner(); + Channel = (integer) ("0x" + llGetSubString((string) llGetKey(), -4, -1)); + setRunning(FALSE); + LoadMenu = TRUE; + LoadPos = TRUE; + LoadProp = TRUE; + llSay(0, "OFF (touch to switch on)."); + } + + on_rez(integer arg) + { + if (ReloadOnRez) + llResetScript(); + } + + touch_start(integer i) + { + User0 = llDetectedKey(0); + state load; + } + + // Waits for another script to send a link message. + // This is needed coz child prims only send touch events to root prims. + // So if this script is in a child prim, it can only be touched by touching that prim. + link_message(integer sender_num, integer num, string str, key id) + { + if (str == "PRIMTOUCH" && id == Owner) + state load; + } + + changed(integer change) + { + if (change & CHANGED_OWNER && Owner != llGetOwner()) + llResetScript(); + } +} + +state load +{ + state_entry() + { + float then = llGetTime(); + float now = 0.0; + float total = 0.0; + list menuCards = []; + list posCards = []; + string item; + string e; + string c; + integer i = llGetInventoryNumber(INVENTORY_NOTECARD); + integer l; + integer PosCount; + integer PropCount; + + llSay(0, "STARTING, please wait..."); + llListen(Channel, "", NULL_KEY, ""); + setRunning(TRUE); + if (LoadProp) + llMessageLinked(LINK_SET, MLP_UNKNOWN, "LOADPROPS", NULL_KEY); + while (i-- > 0) + { + item = llGetInventoryName(INVENTORY_NOTECARD, i); + if (llSubStringIndex(item, ".MENUITEMS") == 0) + menuCards += (list) item; + if (llSubStringIndex(item, ".POSITIONS") == 0) + posCards += (list) item; + } + + if (LoadMenu && LoadPos) // They both fiddle with Poses, but only need to clear it when loading both. + Poses = []; + if (LoadMenu) + { + Menus = []; + Sounds = []; + LMButtons = []; + LMParms = []; + MaxBalls = 1; + MenuUsers = 0; + Chat = TRUE; + Redo = TRUE; + ReloadOnRez = FALSE; + Pose = ""; + readMenu(menuCards); + // Place any otherwise unplaced menus in the main menu. + l = llGetListLength(ToMenus); + // Skipping the first one, which should be the main menu. + for (i = 1; i < l; i++) + { + item = llList2String(ToMenus, i); + if (-1 == llListFindList(SeenMenus, item)) + { + e += "\n" + item; + c += "\nTOMENU " + item; + } + } + ToMenus = []; + SeenMenus = []; + e += "\n" + llList2String(Menus, MENU_ENTRIES); + c += "\n" + llList2String(Menus, MENU_CMDS); + Menus = llListReplaceList(Menus, + [llGetSubString(e, 1, -1), llGetSubString(c, 1, -1)], MENU_ENTRIES, MENU_CMDS); + now = llGetTime(); + total += now - then; + llOwnerSay("Loaded " + (string) (llGetListLength(Menus) / MENU_STRIDE) + " menus in " + + (string) (now - then) + " seconds."); + then = now; + touched(User0); + } + + if (LoadPos) + { + readPos(posCards); + PosCount = llGetListLength(Poses) / POSE_STRIDE; + now = llGetTime(); + total += now - then; + llOwnerSay("Loaded " + (string) (llGetListLength(Poses) / POSE_STRIDE) + " positions in " + + (string) (now - then) + " seconds."); + then = now; + } + LoadMenu = TRUE; + LoadPos = TRUE; + LoadProp = TRUE; + llSay(0, Version + ": READY in " + (string) total + " seconds."); + // Give any listen event time to fire before we switch state. + llSetTimerEvent(0.1); + } + + changed(integer change) + { + if ((change & CHANGED_OWNER) && Owner != llGetOwner()) + llResetScript(); + } + + listen(integer channel, string name, key id, string button) + { + if (handleCmd(id, button, TRUE) && Redo) doMenu(id); + } + + on_rez(integer arg) + { + if (ReloadOnRez) + llResetScript(); + } + + timer() + { + state on; + } +} + +state re_on +{ + state_entry() + { + state on; + } +} + +state on +{ + state_entry() + { + llListen(Channel, "", NULL_KEY, ""); + } + + on_rez(integer arg) + { + if (ReloadOnRez) + llResetScript(); + BallCount = 0; + setRunning(TRUE); + } + + changed(integer change) + { + if ((change & CHANGED_OWNER) && Owner != llGetOwner()) + llResetScript(); + } + + // Handle messages from balls and props. + dataserver(key queryId, string str) + { + list data = llParseString2List(str, ["|"], []); + string cmd = llList2String(data, 0); + if ("ALIVE" == cmd) + { + integer ball = llList2Integer(data, 1); + integer isBall = (0 <= ball); + + saveBall(ball, queryId, NULL_KEY); + if (isBall) + { + vector c = getBallColour(ball); + + osSetPrimitiveParams(queryId, [PRIM_COLOR, ALL_SIDES, c, 1.0, + PRIM_NAME, "~ball" + ball, PRIM_TEXT, "Love", <1.0, 1.0, 1.0>, 1.0 +// OpenSim doesn't support this, so the ~ball script does it. +// PRIM_SIT_TARGET, TRUE, <0.0, 0.0, -0.1>, ZERO_ROTATION + ]); + } + llMessageLinked(LINK_SET, MLP_CMD, str, queryId); + } + else if ("AVATAR" == cmd) + { + integer b = findBallByID(queryId); + integer ball = llList2Integer(Balls, b + BALL_NUM); + key a = llList2Key(data, 2); + + if (-1 != b) + { + key id = llList2Key(Balls, b + BALL_AVATAR); + vector c = getBallColour(ball); + if (NULL_KEY == a) + { + llMessageLinked(LINK_SET, OLD_STANDS, (string) ball, id); + osSetPrimitiveParams(queryId, [PRIM_COLOR, ALL_SIDES, c, 1.0, + PRIM_SIZE, <0.2, 0.2, 0.2>, PRIM_TEXT, "Love", <1.0, 1.0, 1.0>, 1.0]); + stopAnims(id); + } + else + { + llMessageLinked(LINK_SET, OLD_SITS, (string) ball + "|" + Pose, id); + osSetPrimitiveParams(queryId, [PRIM_COLOR, ALL_SIDES, c, 0.0, + PRIM_SIZE, <0.01, 0.01, 0.01>, PRIM_TEXT, "", <1.0, 1.0, 1.0>, 1.0]); + } + Balls = llListReplaceList(Balls, [a], b + BALL_AVATAR, b + BALL_AVATAR); + if (NULL_KEY != a) + doPose(Pose, ball, TRUE); + } + llMessageLinked(LINK_SET, MLP_CMD, str, queryId); + } + } + + touch_start(integer num) + { + integer i; + + for (i = 0; i < num; i++) + touched(llDetectedKey(i)); + } + + listen(integer channel, string name, key id, string button) + { + if (handleCmd(id, button, TRUE) && Redo) doMenu(id); + } + + link_message(integer from, integer num, string str, key id) + { + if ("PRIMTOUCH" == str) + touched(id); + else if (OLD_CMD == num) + handleCmd(id, str, FALSE); + else if (TOOL_DATA == num) + { + if ("" == str) + { + list data = []; + integer i = FALSE; + + if ("Menus" == id) + { + data = Menus; + i = TRUE; + } + else if ("Poses" == id) + { + data = Poses; + i = TRUE; + } + else if ("Sounds" == id) + { + data = Sounds; + i = TRUE; + } + if (i) + llMessageLinked(from, num, LIST_SEP + llDumpList2String(data, LIST_SEP), id); + } + } + else if (MLP_POSE == num) + { + if (NULL_KEY == id) + id = Pose; + doPose((string) id, (integer) str, FALSE); + } + else if (MLP_DATA == num) + { + list data = llParseStringKeepNulls(llGetSubString(str, llStringLength(LIST_SEP), -1), [LIST_SEP], []); + + if ("Menus" == id) + Menus = data; + else if ("Poses" == id) + Poses = data; + else if ("Sounds" == id) + Sounds = data; +// TODO - Do we need to update anything else to match the new data now? + } + else if (((0 == num) && ("POSEB" == str)) || ((1 == num) && ("STOP" == str))) + ; // Old messages we can ignore. + else if ((MLP_CMD == num) || (MLP_UNKNOWN == num) + || (OLD_SITS == num) || (OLD_STANDS == num) || (OLD_ANIM == num)) + ; // Ignore these, they are for others. + else + llOwnerSay("Unknown link message " + num + ", " + str); + } + + no_sensor() + { + if (People) + { + People = 0; + return; + } + People = 0; + llShout(0, "No one here, shutting down."); + llResetScript(); + } + + sensor(integer num) + { + list t = []; + + // Subtle point here, leaves People = num, which we want for later. + for (People = 0; People < num; People++) + { + integer f = listFindString(Musers, llDetectedKey(People), MUSER_STRIDE); + + if (-1 != f) + t += llList2List(Musers, f, f + MUSER_STRIDE - 1); + } + Musers = t; + } + + timer() + { + float now = llGetTime(); + integer l = llGetListLength(Exps); + integer i; + + for (i = 0; i < l; i += EXP_STRIDE) + { + if (llList2Float(Exps, i + EXP_NEXT) <= now) + { + key avatar = llList2Key(Exps, i + EXP_AVATAR); + string exp = llList2String(Exps, i + EXP_EXP); + + if (exp == "SLEEP") + { + osAvatarStopAnimation(avatar, "express_disdain"); + osAvatarPlayAnimation(avatar, "express_disdain"); + osAvatarStopAnimation(avatar, "express_smile"); + osAvatarPlayAnimation(avatar, "express_smile"); + } + else if (exp != "") + { + osAvatarStopAnimation(avatar, exp); + osAvatarPlayAnimation(avatar, exp); + } + Exps = llListReplaceList(Exps, [now + llList2Float(Exps, i + EXP_TIME)], i + EXP_NEXT, i + EXP_NEXT); + } + } + + if (now >= 120.0) + { + // Yes, I know, this might screw with the expressions timing, think we can live with that though. + // LSL timing isn't very precise anyway, and makes things less robotic every couple of minutes. + llResetTime(); + setBalls("LIVE"); + llSensor("", NULL_KEY, AGENT, 6.0, PI); + for (i = 0; i < l; i += EXP_STRIDE) + Exps = llListReplaceList(Exps, [llList2Float(Exps, i + EXP_TIME)], i + EXP_NEXT, i + EXP_NEXT); + } + checkTicks(); + } +} + 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 @@ +// +// MLP lite v3.0 for OpenSim +// Based on the original MLP - MULTI-LOVE-POSE V1.2 - Copyright (c) 2006, by Miffy Fluffy (BSD License) +// This code has bounced around the Second Life and OpenSim for over a decade, with various people working on it. +// MLP lite for OpenSim is almost a complete rewrite by onefang rejected. + +vector RefPos; +rotation RefRot; + +string Pose = ""; + +list Props; // List of props. +integer PROP_NAME = 0; // Name of prop, which should match a POSE name. +integer PROP_OBJECT = 1; // | separated names of props in inventory. +integer PROP_POSROT = 2; // | separated position and rotation pairs. +integer PROP_STRIDE = 3; + +list Balls; // Ball / prop tracker. +integer BALL_NUM = 0; // The number of the ball, negative numbers for the props. +integer BALL_KEY = 1; // The UUID of the ball / prop. +integer BALL_AVATAR = 2; // The UUID of any avatar sitting on the ball. +integer BALL_STRIDE = 3; + +string LIST_SEP = "$!#"; // Used to seperate lists when sending them as strings. + +// The various link messages. The first lot are historical. +//integer OLD_TOUCH = -1; // msg = "PRIMTOUCH" +integer OLD_POSEB = 0; // msg = "POSEB" id = pose name +// // Also CHECK1 and CHECK2 instead of pose name. +integer OLD_STOP = 1; // msg = "STOP" +//integer OLD_REF = 8; // msg = RefPos id = RefRot +integer OLD_SITS = -11000; // msg = ball number|anim name id = avatar key +integer OLD_STANDS = -11001; // msg = ball number id = avatar key +integer OLD_ANIM = -11002; // msg = ball number|anim name id = avatar key +//integer SEQ_CMD = -12001; // msg = ~sequencer command +integer OLD_CMD = -12002; // msg = menu command for ~MLP id = user key + +integer MLP_CMD = -13001; // A command that ~MLP dealt with, so other scripts can hook it. +integer TOOL_DATA = -13002; // Get Menus, Poses, Props, or Sounds list. +integer MLP_DATA = -13003; // Set Menus, Poses, Props, or Sounds list. +integer MLP_POSE = -13004; // doPose(pose, type); +integer MLP_UNKNOWN = -13005; // ~MLP doesn't know this command, might be for other scripts. + +// Types for doPose(), +integer POSES_POSE = -14001; // Change pose, even if it's changing to the same pose. +integer POSES_SWAP = -14002; // Swap poses. +integer POSES_DUMP = -14003; // Dump or save poses. +integer POSES_Z = -14004; // Change height. + + +integer listFindString(list lst, string name, integer stride) +{ + integer f = llListFindList(lst, [name]); + integer ix = f / stride; + + // Round to nearest stride. + ix = ix * stride; + + // Sanity check, make sure we found a name, not something else, else do it the slow way. + if ((-1 != f) && (ix != f)) + { + integer l = llGetListLength(lst); + integer i; + + f = -1; + for (i = 0; i < l; i += stride) + { + if (llList2String(lst, i) == name) + { + f = i; + i = l; + } + } + } + return f; +} + +// Only used in one place, but leave it here for now. +string prStr(string str) +{ + integer ix = llSubStringIndex(str, ">"); + vector p = ((vector) llGetSubString(str, 0, ix) - RefPos) / RefRot; + vector r = llRot2Euler((rotation) llGetSubString(str, ix + 1, -1) / RefRot) * RAD_TO_DEG; + + // OpenSim likes to swap these around, which triggers the ball movement saving. + // Coz OpenSim doesn't support move events, so we gotta do things the hard way. + if (-179.9 >= r.x) r.x = 180.0; + if (-179.9 >= r.y) r.y = 180.0; + if (-179.9 >= r.z) r.z = 180.0; + + return "<" + round(p.x, 3) + "," + round(p.y, 3) + "," + round(p.z, 3) + + "><" + round(r.x, 1) + "," + round(r.y, 1) + "," + round(r.z, 1) + ">"; +} + +string round(float number, integer places) +{ + float shifted; + integer rounded; + string s; + + shifted = number * llPow(10.0, (float) places); + rounded = llRound(shifted); + s = (string) ((float) rounded / llPow(10.0, (float)places)); + rounded = llSubStringIndex(s, "."); + if (-1 != rounded) + s = llGetSubString(s, 0, llSubStringIndex(s, ".") + places); + else + { + s += ".00000000"; + s = llGetSubString(s,0,llSubStringIndex(s, ".") + places); + } + return s; +} + +integer findBall(integer num) +{ + integer f = llListFindList(Balls, [num]); + integer ix = f / BALL_STRIDE; + + // Round to nearest stride. + ix = ix * BALL_STRIDE; + + if ((-1 != f) && (ix == f)) // Sanity check, make sure we found a chan, not something else. + return f; + else + return -1; +} + +saveBall(integer num, key id, key avatar) +{ + integer f = findBall(num); + + if (-1 == f) + Balls += [num, id, avatar]; + else + Balls = llListReplaceList(Balls, [num, id, avatar], f, f + BALL_STRIDE - 1); +} + +rezThing(string thing, string posRot, integer num) +{ + integer i = llSubStringIndex(posRot, ">"); + + llRezObject(thing, + ((vector) llGetSubString(posRot, 0, i)) * RefRot + RefPos, + ZERO_VECTOR, + llEuler2Rot((vector) llGetSubString(posRot, i + 1, -1) * DEG_TO_RAD) * RefRot, + num); +} + +readProps(list propCards) +{ + integer i; + integer l = llGetListLength(propCards); + + propCards = llListSort(propCards, 1, TRUE); + for (i = 0; i < l; i++) + { + string card = llList2String(propCards, i); + list crd = llParseStringKeepNulls(osGetNotecard(card), ["\n"], []); + integer m = llGetListLength(crd); + integer j; + + llOwnerSay("Reading '" + card + "'."); + for (j = 0; j < m; j++) + { + string data = llList2String(crd, j); + + if (llGetSubString(data, 0, 0) != "/") + { // skip comments + data = llStringTrim(data, STRING_TRIM); + if ("" != data) + { + // .PROPS is different from .POSITIONS, which is just inconsistant. + // There's an extra | at the beginning of the lines, for no apparent reason. + // The position and rotation has a "/" between them, and normally / is a comment. + // But we gotta stay compatible. sigh + list props = llParseStringKeepNulls(data, ["|"], []); + string name = llStringTrim(llList2String(props, 1), STRING_TRIM); + string prop = llStringTrim(llList2String(props, 2), STRING_TRIM); + list posrots = llParseString2List(llStringTrim(llList2String(props, 3), STRING_TRIM), ["/"], []); + + saveProp(name, prop, llDumpList2String(posrots, "")); + } + } + } + } +} + +integer findProp(string name) +{ + return listFindString(Props, name, PROP_STRIDE); +} + +saveProp(string name, string prop, string posRot) +{ + integer f = findProp(name); + + if (-1 != f) + { + string t; + + t = llList2String(Props, f + PROP_OBJECT); + if ("" != t) + prop += "|" + prop; + t = llList2String(Props, f + PROP_POSROT); + if ("" != t) + posRot += "|" + posRot; + Props = llListReplaceList(Props, [name, prop, posRot], f, f + PROP_STRIDE - 1); + } + else + Props += [name, prop, posRot]; +} + +// ball is either the ball number, or a POSES_* flag. +// ball on the other hand, is a negative integer for props. +doPose(string newPose, integer ball) +{ + integer f = findProp(Pose); + integer p = findBall(ball); + integer l = llGetListLength(Balls); + integer i = 0; + vector newRefPos = llGetPos(); + rotation newRefRot = llGetRot(); + + newRefPos.z += ((integer) llGetObjectDesc()) / 100.0; + + if (-1 != p) + { + i = p; + l = p + BALL_STRIDE; + } + for (; i < l; i += BALL_STRIDE) + { + integer b0 = llList2Integer(Balls, i + BALL_NUM); + integer isBall = (0 <= b0); + + if (isBall) + ; + else //if ((POSES_SWAP != ball) + { + if (-1 != f) + { + integer m = f + PROP_POSROT; + string prOld = llList2String(Props, m); + // Find out where the prop is now, and rotation. + integer q = -1 - b0; + string bpr = prStr(llDumpList2String( + llGetObjectDetails(llList2Key(Balls, i + BALL_KEY), [OBJECT_POS, OBJECT_ROT]), "")); + string result = llDumpList2String(llListReplaceList(llParseString2List(prOld, ["|"], []), [bpr], q, q), "|"); + // Actually store the props new position / rotation if it moved. + if (result != prOld) + { + llOwnerSay(" MOVEd prop " + q + ".\n\t old [" + prOld + "]\n\t new [" + result + "]"); + Props = llListReplaceList(Props, [result], m, m); + } + } + + if ((POSES_DUMP != ball) && (POSES_SWAP != ball)) + { + // Remove the prop. + osMessageObject(llList2Key(Balls, i + BALL_KEY), "DIE"); + Balls = llListReplaceList(Balls, [], i, i + BALL_STRIDE - 1); + i -= BALL_STRIDE; + l -= BALL_STRIDE; + } + } + } + RefPos = newRefPos; + RefRot = newRefRot; + + // Props are per pose, so deal with them here to. + // Assumption - props don't MOVE when we change poses, they die, and get recreated when needed. + // Note that the original MLP also assumed that props don't MOVE, though they send MOVE. + if (POSES_POSE == ball) + { + p = findProp(newPose); + if (-1 != p) + { + list o = llParseStringKeepNulls(llList2String(Props, p + PROP_OBJECT), ["|"], []); + list q = llParseStringKeepNulls(llList2String(Props, p + PROP_POSROT), ["|"], []); + + l = llGetListLength(o); + for (i = 0; i < l; i++) + rezThing(llList2String(o, i), llList2String(q, i), -1 - i); + } + } + Pose = newPose; +} + + +default +{ + link_message(integer from, integer num, string str, key id) + { + if (((0 == num) && ("POSEB" == str)) || ((1 == num) && ("STOP" == str))) + ; // Old messages we can ignore. + else if ((MLP_UNKNOWN == num) && ("LOADPROPS" == str)) + { + float then = llGetTime(); + float now = 0.0; + list propCards = []; // List of names of config cards. + string item; + integer i = llGetInventoryNumber(INVENTORY_NOTECARD); + integer PropCount; + + while (i-- > 0) + { + item = llGetInventoryName(INVENTORY_NOTECARD, i); + if (llSubStringIndex(item, ".PROPS") == 0) + propCards += (list) item; + } + Props = []; + readProps(propCards); + PropCount = llGetListLength(Props) / PROP_STRIDE; + if (0 < PropCount) + { + now = llGetTime(); + llOwnerSay("Loaded " + PropCount + " props in " + (string) (now - then) + " seconds."); + } + } + else if (MLP_POSE == num) + { + if (NULL_KEY == id) + id = Pose; + doPose((string) id, (integer) str); + } + else if (TOOL_DATA == num) + { + if ("" == str) + { + if ("Props" == id) + llMessageLinked(from, num, LIST_SEP + llDumpList2String(Props, LIST_SEP), id); + } + else + { + if ("Props" == id) + Props = llParseStringKeepNulls(llGetSubString(str, llStringLength(LIST_SEP), -1), [LIST_SEP], []); + } + } + else if ((OLD_CMD == num) || (MLP_CMD == num) || (MLP_UNKNOWN == num) + || (OLD_SITS == num) || (OLD_STANDS == num) || (OLD_ANIM == num)) + ; + else + llOwnerSay(llGetScriptName() + " Unknown link message " + num + ", " + str); + } + + dataserver(key queryId, string str) + { + list data = llParseString2List(str, ["|"], []); + + if ("ALIVE" == llList2String(data, 0)) + saveBall(llList2Integer(data, 1), queryId, NULL_KEY); + } +} 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 @@ +// +// MLP lite v3.0 for OpenSim +// Based on the original MLP - MULTI-LOVE-POSE V1.2 - Copyright (c) 2006, by Miffy Fluffy (BSD License) +// This code has bounced around the Second Life and OpenSim for over a decade, with various people working on it. +// MLP lite for OpenSim is almost a complete rewrite by onefang rejected. + +string Version = "MLP lite tools v3.0 alpha"; + +key Owner; +integer Channel; + +list Need = []; +string Todo = ""; + +// NOTE - This one uses \n to separate sub lists, coz | is used in some of the commands. +list Menus; // List of menus. +integer MENU_NAME = 0; // Name of menu. +integer MENU_AUTH = 1; // Authorised users of menu - 0 = owner, 1 = group, 2 = all +integer MENU_COLOURS = 2; // \n spearated list of ball colours. +integer MENU_ENTRIES = 3; // \n separated list of entries. +integer MENU_CMDS = 4; // \n separated list of commands, matching the entries. +integer MENU_SWAPS = 5; // \n separated list of colour sets. +integer MENU_STRIDE = 6; + +string Pose = ""; +list Poses; // List of poses. +integer POSE_NAME = 0; // Name of pose. +integer POSE_ANIM = 1; // | separated animations. +integer POSE_EMOTE = 2; // | separated emotions and timers list. +integer POSE_POSROT = 3; // | separated posiiton and rotation pairs. +integer POSE_STRIDE = 4; + +list Props; // List of props. +integer PROP_NAME = 0; // Name of prop, which should match a POSE name. +integer PROP_OBJECT = 1; // | separated names of props in inventory. +integer PROP_POSROT = 2; // | separated position and rotation pairs. +integer PROP_STRIDE = 3; + +list Sounds; +integer SOUND_NAME = 0; +integer SOUND_INV = 1; +integer SOUND_STRIDE = 2; + +string LIST_SEP = "$!#"; // Used to seperate lists when sending them as strings. + +// The various link messages. The first lot are historical. +//integer OLD_TOUCH = -1; // msg = "PRIMTOUCH" +integer OLD_POSEB = 0; // msg = "POSEB" id = pose name +// // Also CHECK1 and CHECK2 instead of pose name. +integer OLD_STOP = 1; // msg = "STOP" +//integer OLD_REF = 8; // msg = RefPos id = RefRot +integer OLD_SITS = -11000; // msg = ball number|anim name id = avatar key +integer OLD_STANDS = -11001; // msg = ball number id = avatar key +integer OLD_ANIM = -11002; // msg = ball number|anim name id = avatar key +//integer SEQ_CMD = -12001; // msg = ~sequencer command +integer OLD_CMD = -12002; // msg = menu command for ~MLP id = user key + +integer MLP_CMD = -13001; // A command that ~MLP dealt with, so other scripts can hook it. +integer TOOL_DATA = -13002; // Get Menus, Poses, Props, or Sounds list. +integer MLP_DATA = -13003; // Set Menus, Poses, Props, or Sounds list. +integer MLP_POSE = -13004; // doPose(pose, type); +integer MLP_UNKNOWN = -13005; // ~MLP doesn't know this command, might be for other scripts. + +// Types for doPose(), +integer POSES_POSE = -14001; // Change pose, even if it's changing to the same pose. +integer POSES_SWAP = -14002; // Swap poses. +integer POSES_DUMP = -14003; // Dump or save poses. +integer POSES_Z = -14004; // Change height. + +integer PERMS_CT = PERM_COPY | PERM_TRANSFER; + + +integer listFindString(list lst, string name, integer stride) +{ + integer f = llListFindList(lst, [name]); + integer ix = f / stride; + + // Round to nearest stride. + ix = ix * stride; + + // Sanity check, make sure we found a name, not something else, else do it the slow way. + if ((-1 != f) && (ix != f)) + { + integer l = llGetListLength(lst); + integer i; + + f = -1; + for (i = 0; i < l; i += stride) + { + if (llList2String(lst, i) == name) + { + f = i; + i = l; + } + } + } + return f; +} + +integer findMenu(string name) +{ + return listFindString(Menus, name, MENU_STRIDE); +} + +list reportPose() +{ + list result = []; + integer l = llGetListLength(Poses); + integer i; + + for (i = 0; i < l; i += POSE_STRIDE) + { + list prs = llParseString2List(llList2String(Poses, i + POSE_POSROT), ["|"], []); + string r = "{" + llList2String(Poses, i) + "} "; + integer m = llGetListLength(prs); + integer j; + + for (j = 0; j < m; j++) + r += llList2String(prs, j); + result += [r]; + } + + return result; +} + +list reportProp() +{ + list result = []; + integer l = llGetListLength(Props); + integer i; + + for (i = 0; i < l; i += PROP_STRIDE) + { + list obs = llParseString2List(llList2String(Props, i + PROP_OBJECT), ["|"], []); + list prs = llParseString2List(llList2String(Props, i + PROP_POSROT), ["|"], []); + string r = "|" + llList2String(Props, i) + "|"; + integer m = llGetListLength(prs); + integer j; + + // See ~MLP lite props for a comment about why this is different from reportPose(). + for (j = 0; j < m; j++) + r += llList2String(obs, j) + "|" + osReplaceString(llList2String(prs, j), "><", ">/<", -1, 0); + result += [r]; + } + + return result; +} + +list checkPerms(string name, integer type) +{ + list ans = []; + integer i = llGetInventoryNumber(type); + integer p; + + while (i-- > 0) + { + string a = llGetInventoryName(type, i); + + ans += [a]; + p = llGetInventoryPermMask(a, MASK_OWNER); + if (PERMS_CT != (p & PERMS_CT)) + llSay(0, name + " '" + a + "' does not have copy or transfer permissions for the owner."); + p = llGetInventoryPermMask(a, MASK_NEXT); + if (PERMS_CT != (p & PERMS_CT)) + llSay(0, name + " '" + a + "' does not have copy or transfer permissions for the next owner."); + } + return ans; +} + +checkConfig() +{ + list ans; + integer l = llGetListLength(Poses); + integer m; + integer i; + integer j; + integer f; + + llSay(0, "Checking most things..."); + ans = checkPerms("Animation", INVENTORY_ANIMATION); + for (i = 0; i < l; i += POSE_STRIDE) + { + string name = llList2String(Poses, i + POSE_NAME); + + if ("" == llList2String(Poses, i + POSE_POSROT)) + llSay(0, "No .POSITIONS* entry for '" + name + "'."); + else if ("" == llList2String(Poses, i + POSE_ANIM)) + { + if (("default" != name) && ("stand" != name)) + llSay(0, "No .MENUITEMS* entry for '" + name + "'."); + } + else + { + list anims = llParseString2List(llList2String(Poses, i + POSE_ANIM), ["|"], []); + + m = llGetListLength(anims); + for (j = 0; j < m; j++) + { + string aname = llList2String(anims, j); + + if (aname != "") + { + if ((aname != "stand") && (aname != "sit_ground")) + { + if (llGetSubString(aname, -1, -1) == "*") + aname = llGetSubString(aname, 0, -2); + else + { + f = llSubStringIndex(aname, "::"); + if (-1 != f) + aname = llGetSubString(aname, 0, f - 1); + } + + if (llGetInventoryType(aname) != INVENTORY_ANIMATION) + llSay(0, "Animation '" + aname + "' is not in inventory (ok for build-in animations, otherwise check)."); + f = llListFindList(ans, aname); + if (-1 != f) + ans = llDeleteSubList(ans, f, f); + } + } + } + } + } + if (0 < llGetListLength(ans)) + llSay(0, "These animations are not in any config cards - " + llList2CSV(llListSort(ans, 1, TRUE))); + + ans = checkPerms("Object", INVENTORY_OBJECT); + l = llGetListLength(Props); + f = llListFindList(ans, "~ball"); + if (-1 != f) + ans = llDeleteSubList(ans, f, f); + for (i = 0; i < l; i += PROP_STRIDE) + { + list objs = llParseStringKeepNulls(llList2String(Props, i + PROP_OBJECT), ["|"], []); + + m = llGetListLength(objs); + for (j = 0; j < m; j++) + { + string s = llList2String(objs, j); + + f = llListFindList(ans, s); + if (-1 != f) + ans = llDeleteSubList(ans, f, f); + else + llSay(0, "Object '" + s + "' is not in inventory."); + } + } + if (0 < llGetListLength(ans)) + llSay(0, "These objects are not in any config cards - " + llList2CSV(llListSort(ans, 1, TRUE))); + + ans = checkPerms("Sound", INVENTORY_SOUND); + l = llGetListLength(Sounds); + if (l) + { + for (i = 0; i < l; i += SOUND_STRIDE) + { + string s = llList2String(Sounds, i + SOUND_INV); + +// TODO - No idea why checkPerms() is handing me ["", ""]. + if (llStringLength(s)) + { + f = llListFindList(ans, s); + if (-1 != f) + ans = llDeleteSubList(ans, f, f); + else + llSay(0, "Sound '" + s + "' is not in inventory."); + } + } + } + if (0 < llGetListLength(ans)) + llSay(0, "These sounds are not in any config cards - " + llList2CSV(llListSort(ans, 1, TRUE))); + + l = llGetListLength(Poses); + for (i = 0; i < l; i += MENU_STRIDE) + { + string name = llList2String(Menus, i + MENU_NAME); + + ans = llParseStringKeepNulls(llList2String(Menus, i + MENU_CMDS), ["\n"], []); + if (12 < llGetListLength(ans)) + llSay(0, "To many menu items in '" + name + "' menu."); + m = llGetListLength(ans); + for (j = 0; j < m; j++) + { + string cmd = llList2String(ans, j); + + if ("TOMENU " == llGetSubString(cmd, 0, 6)) + { + cmd = llGetSubString(cmd, 7, -1); + if (-1 == findMenu(cmd)) + llSay(0, "Menu '" + cmd + "' not found."); + } + } + } + + list scripts = llParseStringKeepNulls("memory menu menucfg pos pose poser prop props run timeout", [""],[]); + list found = []; + + ans = checkPerms("Script", INVENTORY_SCRIPT); + l = llGetListLength(scripts); + for (i = 0; i < l; i++) + { + f = llListFindList(ans, ["~" + llList2String(scripts, i)]); + if (-1 != f) + { + found += ["~" + llList2String(scripts, i)]; + ans = llDeleteSubList(ans, f, f); + } + } + for (i = 1; i < 9; i++) + { + f = llListFindList(ans, ["~poser " + i]); + if (-1 != f) + { + found += ["~poser " + i]; + ans = llDeleteSubList(ans, f, f); + } + } + f = llListFindList(ans, ["~ball"]); + if (-1 != f) + { + found += ["~ball"]; + ans = llDeleteSubList(ans, f, f); + llSay(0, "There is a '~ball' script, it should be in the '~ball' object, and in any prop objects."); + llSay(0, "There is a '~ball' script, it should NOT be in this '" + llGetObjectName() + "' object."); + } + f = llListFindList(ans, ["~sequencer"]); + if (-1 != f) + { + ans = llDeleteSubList(ans, f, f); + llSay(0, "There is a '~sequencer' script, which you wont need unless you where using the old MLP sequences."); + llSay(0, "If any old '.SEQUENCER' cards are in use, try them, see if it still works."); + } + if (0 < llGetListLength(found)) + llSay(0, "These scripts are likely left overs from an older MLP, you can probably remove them - " + llList2CSV(llListSort(found, 1, TRUE))); + + + f = llListFindList(ans, ["~MLP lite for OpenSim"]); + if (-1 != f) + ans = llDeleteSubList(ans, f, f); + else + llSay(0, llGetScriptName() + " says it wants '~MLP lite for OpenSim'."); + f = llListFindList(ans, ["~MLP lite tools"]); + if (-1 != f) + ans = llDeleteSubList(ans, f, f); + else + llSay(0, llGetScriptName() + " != ~MLP lite tools!"); + + list cards = checkPerms("Notecard", INVENTORY_NOTECARD); + l = llGetListLength(cards); + f = -1; + for (i = 0; i < l; i++) + { + string crd = llList2String(cards, i); + + if (llSubStringIndex(crd, ".SEQUENCES") == 0) + llSay(0, "Old MLP sequences might be in notecard - '" + crd + "'."); + if (llSubStringIndex(crd, ".PROPS") == 0) + f = i; + } + if (-1 == f) + { + f = llListFindList(ans, ["~MLP lite props"]); + if (-1 != f) + llSay(0, "'~MLP lite props' script is not needed if there are no .PROPS* cards."); + } + else + { + f = llListFindList(ans, ["~MLP lite props"]); + if (-1 == f) + llSay(0, "'~MLP lite props' script is needed if there are .PROPS* cards."); + } + + + llSay(0, "Checks completed."); +} + +doMenu(key id) +{ + llDialog(id, Version, [ + "All poses", " ", "Quit tools", + "Height>>", "Pose match>>", "Adjust pos>>", + "RELOAD", "RESET", "REDECORATE", + "CHECK", "DUMP", "SAVE" + ], Channel); +} + +doHeight(key id) +{ + llDialog(id, Version, [ + " ", " ", "Back", + "Z-1", "Z-5", "Z-25", + "Z+1", "Z+5", "Z+25" + ], Channel); +} + +getData(string name) +{ + Need += [name]; + llMessageLinked(LINK_SET, TOOL_DATA, "", name); +} + +default +{ + state_entry() + { + Owner = llGetOwner(); + Channel = (integer) ("0x" + llGetSubString((string) llGetKey(), -4, -1) + 1); + llListen(Channel, "", Owner, ""); + } + + link_message(integer from, integer num, string str, key id) + { + if (((0 == num) && ("POSEB" == str)) || ((1 == num) && ("STOP" == str))) + ; + else if ((MLP_UNKNOWN == num) && ("TOOLS" == str)) + doMenu(id); + else if ((MLP_POSE == num) || (OLD_CMD == num) || (MLP_CMD == num) || (MLP_UNKNOWN == num) + || (OLD_SITS == num) || (OLD_STANDS == num) || (OLD_ANIM == num)) + ; + else if ((TOOL_DATA == num) && ("" == str)) + ; + else if ((TOOL_DATA == num) && ("" != str)) + { + list data = llParseStringKeepNulls(llGetSubString(str, llStringLength(LIST_SEP), -1), [LIST_SEP], []); + integer f = llListFindList(Need, id); + + if ("Menus" == id) + Menus = data; + else if ("Poses" == id) + Poses = data; + else if ("Props" == id) + Props = data; + else if ("Sounds" == id) + Sounds = data; + if (-1 != f) + { + integer i; + + Need = llDeleteSubList(Need, f, f); + if (0 == llGetListLength(Need)) + { + if ("CHECK" == Todo) + { + checkConfig(); + Todo = ""; + } + else if ("DUMP" == Todo) + { + string objectName = llGetObjectName(); + list report = reportPose(); + integer l = llGetListLength(report); + + llSetObjectName("."); + llOwnerSay(".POSITIONS---------------------------------------------------------------"); + for (i = 0; i < l; i++) + llOwnerSay(llList2String(report, i)); + llOwnerSay(".PROPS-------------------------------------------------------------------"); + report = reportProp(); + l = llGetListLength(report); + for (i = 0; i < l; i++) + llOwnerSay(llList2String(report, i)); + llOwnerSay("-------------------------------------------------------------------------"); + llSetObjectName(objectName); + Todo = ""; + } + else if ("SAVE" == Todo) + { + i = llGetInventoryNumber(INVENTORY_NOTECARD); + list cards = []; + + llSay(0, "Backing up old cards."); + while (i-- > 0) + { + string item = llGetInventoryName(INVENTORY_NOTECARD, i); + + if ((llSubStringIndex(item, ".POSITIONS") == 0) || (llSubStringIndex(item, ".PROPS") == 0)) + cards += [llGetInventoryName(INVENTORY_NOTECARD, i)]; + } + i = llGetListLength(cards); + while (i-- > 0) + { + string item = llList2String(cards, i); + + osMakeNotecard(".backup" + item, llParseStringKeepNulls(osGetNotecard(item), ["\n"], [])); + llRemoveInventory(item); + } + + osMakeNotecard(".POSITIONS", reportPose()); + llSay(0, "Current ball positions saved to the .POSITIONS notecard."); + if (llGetListLength(Props)) + { + osMakeNotecard(".PROPS", reportProp()); + llSay(0, "Current props positions saved to the .PROPS notecard."); + } + Todo = ""; + } + else if ("All poses" == Todo) + { + + f = llGetListLength(Menus); + for (i = 0; i < f; i += MENU_STRIDE) + { + string name = llList2String(Menus, i + MENU_NAME); + list cmds = llParseStringKeepNulls(llList2String(Menus, i + MENU_CMDS), ["\n"], []); + integer l = llGetListLength(cmds); + integer j; + + llOwnerSay("Running through all the poses in menu '" + name + "'."); + for (j = 0; j < l; j++) + { + string p = llList2String(cmds, j); + + if ("POSE " == llGetSubString(p, 0, 4)) + { + string pose = llGetSubString(p, 5, -1); + + llMessageLinked(LINK_SET, OLD_CMD, "TOMENU " + name, Owner); + llMessageLinked(LINK_SET, MLP_POSE, (string) POSES_POSE, pose); + llSleep(5.0); + } + } + } + llMessageLinked(LINK_SET, OLD_CMD, "STOP", Owner); + llOwnerSay("Finished all poses."); + Todo = ""; + } + } + } + } + else + llOwnerSay(llGetScriptName() + " Unknown link message " + num + ", " + str); + } + + listen(integer channel, string name, key id, string button) + { + if ("CHECK" == button) + { + Todo = button; + getData("Menus"); + getData("Poses"); + if (llGetInventoryType("~MLP lite props") == INVENTORY_SCRIPT) + getData("Props"); + getData("Sounds"); + } + else if ("DUMP" == button) + { + // Save any edits to the current pose first. + llMessageLinked(LINK_SET, MLP_POSE, (string) POSES_DUMP, NULL_KEY); + getData("Poses"); + if (llGetInventoryType("~MLP lite props") == INVENTORY_SCRIPT) + getData("Props"); + Todo = button; + } + else if ("SAVE" == button) + { + // Save any edits to the current pose first. + llMessageLinked(LINK_SET, MLP_POSE, (string) POSES_DUMP, NULL_KEY); + getData("Poses"); + if (llGetInventoryType("~MLP lite props") == INVENTORY_SCRIPT) + getData("Props"); + Todo = button; + } + else if ("All poses" == button) + { + getData("Menus"); + Todo = button; + } + else if ("RESET" == button || "RELOAD" == button || "REDECORATE" == button) + llMessageLinked(LINK_SET, OLD_CMD, button, Owner); + else if ("Height>>" == button) + { + doHeight(id); + return; + } + else if ("Z" == llGetSubString(button, 0, 0)) + { + integer i = (integer) llGetSubString(button, 1, 10); + integer Zoffset = (integer) llGetObjectDesc() + i; + + llSetObjectDesc((string) Zoffset); + llMessageLinked(LINK_SET, MLP_POSE, (string) POSES_Z, NULL_KEY); + llOwnerSay("Height Adjustment: change by " + (string) i + "cm, new offset: " + (string) Zoffset + "cm."); + doHeight(id); + return; + } + else if ("Back" == button) + ; + else if ("Quit tools" == button) + { + llMessageLinked(LINK_SET, OLD_CMD, "TOMENU", Owner); + return; + } + doMenu(id); + } +} diff --git a/~ball.lsl b/~ball.lsl new file mode 100644 index 0000000..4d6bb2d --- /dev/null +++ b/~ball.lsl @@ -0,0 +1,63 @@ +// +// MLP lite v3.0 for OpenSim +// Based on the original MLP - MULTI-LOVE-POSE V1.2 - Copyright (c) 2006, by Miffy Fluffy (BSD License) +// This code has bounced around the Second Life and OpenSim for over a decade, with various people working on it. +// MLP lite for OpenSim is almost a complete rewrite by onefang rejected. + +key Boss; +integer Number; +integer isBall; + + +default +{ + state_entry() + { + integer perm = llGetObjectPermMask(MASK_OWNER); + + if (0 == (perm & (PERM_COPY | PERM_MOVE | PERM_MODIFY))) + { + llOwnerSay("DANGER, CAN'T COPY, MODIFY, OR MOVE THIS OBJECT!"); + llSay(DEBUG_CHANNEL, "DANGER, CAN'T COPY, MODIFY, OR MOVE THIS OBJECT!"); + } + } + + on_rez(integer num) + { + string objectName = llGetObjectName(); + + isBall = ("~ball" == objectName); + if (isBall) // This is only coz OpenSim 8.2 doesn't know PRIM_SIT_TARGET. + llSitTarget(<0.0, 0.0, -0.1>, ZERO_ROTATION); + Boss = osGetRezzingObject(); + if (NULL_KEY != Boss) + { + Number = num; + osMessageObject(Boss, "ALIVE|" + num); + llSetTimerEvent(600.0); + } + } + + changed(integer change) + { + if ((CHANGED_LINK == change) && isBall) + osMessageObject(Boss, "AVATAR|" + Number + "|" + (string) llAvatarOnSitTarget()); + } + + dataserver(key query_id, string str) + { + if (query_id == Boss) + { + if ("LIVE" == str) + llSetTimerEvent(600.0); + else if ("DIE" == str) + llDie(); + } + } + + timer() + { // not heard "LIVE" from ~MLP for a while: suicide + llDie(); + } +} + -- cgit v1.1