Join us in Outworldz at www.outworldz.com:9000 or follow us:

Search dozens of selected web sites for OpenSim and LSL script

New! Script Meta-Search will search thousands of scripts here and at other sites for LSL or Opensim scripts.
Loading

Want to add a script or a project? Upload it and a half million people will see it and your name here this year.

Home   Show All
Category: Contributor: Creator
NPC PMAC  

PMAC

PARAMOUR MULTI-ANIMATION CONTROLLER (PMAC) v1.02(OSSL)

Category: NPC
By : Aine Caoimhe
Created: 2015-11-24 Edited: 2015-11-23
Worlds: OpenSim

the Zip file

Download all files for PMAC
Contents are in zip format, with .LSL (text) source code and LSLEdit (text + Solution) formats.
Get file # 1. !!!READ ME - 1. Overview, Feature List, Change Log, License.txt
Get file # 2. !!!READ ME - 2. Owner Initial Setup Instructions.txt
Get file # 3. !!!READ ME - 3. User Instructions.txt
Get file # 4. !!!READ ME - 4. Basic Tweaking Instructions.txt
Get file # 5. !!!READ ME - 5. Advanced Builders Information.txt
Get file # 6. PMAC Core v1.02.lsl
Get file # 7. Presentation Add On.lsl
Get file # 8. ~~~positioner.lsl
1
2 // Provided under Creative Commons Attribution-Non-Commercial-ShareAlike 4.0 International license.
3 // Please be sure you read and adhere to the terms of this license: https://creativecommons.org/licenses/by-nc-sa/4.0/
4
5 PARAMOUR MULTI-ANIMATION CONTROLLER (PMAC) v1.02
6 by Aine Caoimhe (c. LACM) January 2015
7
8 **** OVERVIEW *****
9
10 The Paramour Multi-Animation Controller (PMAC) is a no-poseball script system
11 intended for use in
12 almost any piece of furniture that needs to be able to position and animate
13 multiple avatars at once.
14 PMAC is designed to offer a high performance alternative to existing systems
15 such as MLP, nPose, and
16 xPose. PMAC uses a core engine that leverages the powerful OSSL functions
17 available only in Opensim
18 to drastically increase the script's speed and reliability, while at the same
19 time using only a
20 fraction of the sim resources of the existing systems.
21
22
23 ***** KEY FEATURES *****
24
25 * WORKS IN ANYTHING - The PMAC system can be put into anything, from a
26 single-prim scultpy blanket or
27 one-piece mesh sofa to an elaborate multi-piece bedroom linkset. You can put it
28 in a simple pine cube
29 prim if you feel like it. There's nothing extra to attach and hide or position
30 nearby. All it takes
31 is a single prim. The only "requirement" is that PMAC must be in the root prim
32 of an object.
33
34 * NO POSEBALLS - There are no poseballs used at all for PMAC. None to disguise
35 or hide as part of the
36 furniture, none that have to be rezzed and sat on, none to get lost inside a
37 sofa or buried underground
38 or scattered around a sim...all of the users simply sit on the furniture item
39 that contains the main
40 script and PMAC takes care of the rest using a "virtual poseball" approach that
41 exists within the logic of the script.
42
43 * A TRUE MULTI-AVATAR CONTROLLER - PMAC systems can be set up to handle
44 animations designed for 1 to as
45 many as 9 avatars with the flexibility to load any preset configuration on the
46 fly. You could set it up
47 in a sofa to act like a sit AO when you're lounging around alone, and quickly
48 turn it into a couples
49 controller when that special someone comes to visit, and then switch it into a
50 multi-person handler
51 when you have even more company...and it's all available virtually instantly
52 from the menu without
53 the need to stand up or reset...all adjustments are done on the fly during use.
54 Now your VL furniture
55 items can be just as flexible as your RL ones are.
56
57 * REGION-FRIENDLY - Most multi-avatar controllers are pretty harsh on a
58 simulator, eating up valuable
59 cycles, and memory and other script resources. PMAC's extensive use of OSSL
60 functions allows it to
61 drastically reduce this overhead. The core PMAC system is a single script. Yep,
62 a fully functional
63 controller for up to 9 avi all done with one low-footprint script. When not in
64 use, it is completely
65 dormant and uses virtually no simulator resources at all (just the tiny amount
66 of memory required for
67 the script itself). Even when in use, the system's footprint barely changes --
68 unlike SL systems with
69 poseball scripts, animation handler scripts, multiple timers, multiple
70 listeners, high message traffic,
71 large menu memory consumption, and all the other complications that LSL-based
72 scripts are subject to.
73 Even with 9 avatars seated all it uses is a single script and single listener.
74 The only time it ever uses a timer is on those rare occasions when you're
75 editing positions or setting up a new system.
76
77 * SPEED - The total time to initialize a typical PMAC system is approximately
78 one second. That's right,
79 just 1 second! If for some reason you need to reset it you'll be ready to use it
80 again almost instantly.
81 Due to its unique OSSL-based menu method, PMAC dialogs pop up right away even
82 for very extensive set-ups
83 with hundreds of different animations.
84
85 * SYNCH - PMAC uses the tried and true Paramour synch method, keeping up to 9
86 avatars moving in perfect
87 unison once their animations are in your viewer's cache.
88
89 * FULL NPC INTEGRATION - Want some company but none of your friends are online?
90 PMAC can rez a NPC
91 (or two, or three, or up to eight!) to join you instead. When you're done, PMAC
92 tucks them away again
93 for next time. You can pick who, where, and when at the touch of a few simple
94 dialog buttons.
95
96
97 * AUTO-MODE - Don't want to have to change your own animations? Simply engage
98 PMAC's auto mode and
99 let the system do it for you, more or less like engaging a sit AO for up to 9
100 people. Timing can be chosen from a variety of options and changed on the fly;
101 and you can configure the system to engage it automatically so you can have your
102 furniture fully useable without ever even needing to see a dialog (but of course
103 you can simply touch the object to bring up the menu and disengage this at any
104 time).
105
106 * SWAPPING - You can quickly and easily swap positions with anyone else -- even
107 if they aren't there
108 and it's just an unoccupied "virtual position".
109
110 * AUTO HEIGHT ADJUSTMENTS - PMAC automatically adjusts positions based on each
111 avatar's height (although admittedly this is a very rudimentary estimate and
112 depends a lot on how the original animation was created). In many cases this
113 will be sufficient unless you have a very tall or short avatar.
114
115 * ON-THE-FLY POSITION ADJUSTMENTS - The owner can make more detailed adjustments
116 to animation positioning at any time, rapidly and on the fly, and it remains
117 temporarily stored in the script's memory.
118
119 * NO NOTECARD EDITING FOR USERS - If the owner wishes to persist any position
120 adjustments to the system's notecards, PMAC facilitates this by handling it all
121 done with the touch of a single dialog button. No more copy-pasting reams of
122 text from chat into a notecard and hoping you don't mess something up! Typically
123 the only time you'll manually open and edit a notecard is when making major
124 changes such as adding new animations or deleting existing ones.
125
126 * EASY CONFIGURATION - PMAC can be fully configured in the script or via an
127 optional configuration notecard which some users might find a little less
128 intimidating to do.
129
130 * USER ACCESS RESTRICTIONS - The PMAC system has a variety of settings allowing
131 you to control who can use it, who can access which menus or animations, who can
132 rez which NPCs, and other similar access-control restrictions.
133
134 * POWERFULL ADD-ON COMMAND SYSTEM INTEGRATION - As a design goal, I wanted the
135 PMAC system to be easy and intuitive enough to just "rez and play" without what
136 can be a rather nasty learning curve of many other systems. I also felt it was
137 imperative to optimize PMAC's performance, simulator footprint, and XEngine
138 demands. As a result, only the most important and commonly-demanded features are
139 part of the core "out of the box" PMAC system. But I also wanted to offer the
140 flexibility for more advanced builders and users to enhance its functions,
141 capabilities and offerings, so PMAC incorporates an integrated and extremely
142 powerful command system that opens up almost endless possibilities via the use
143 of scripted add-ons. If PMAC doesn't do something you want it to do, it's very
144 likely that an add-on can be scripted to do it in conjunction with PMAC's
145 command system -- at the expense of the additional script(s) overhead. I will
146 likely create add-ons for the most often-requested extra features; but anyone
147 with reasonable scripting skills can write their own custom applications for
148 specialized requirements. The only limits are likely to be your imagination (and
149 ability to script it).
150
151 * FREE - The PMAC system is the culmination of many months of work and almost 5
152 years of development and testing of various approaches to making the "next
153 generation" multi-avatar controller, and I'm releasing it to the Opensim
154 community for free (under Creative Commons Attribution-Non-Commercial-ShareAlike
155 4.0 International license). Any add-ons I create for it in future will be free,
156 too, and I sincerely hope that other add-on creators will follow suit and donate
157 their enhancements to the community as well.
158
159 * COMPANION MLP CONVERTER - Thanks to Seth Nygard, existing MLP 2.x systems can
160 be easily converted to PMAC in a matter of minutes using a supplied conversion
161 script. Look for the "PMAC Builder's Kit" which contains everything you need to
162 take an existing MLP system and turn it into a PMAC system.
163
164 ***** CHANGE LOG *****
165
166 v. 1.0 January 2015 (Initial beta release)
167
168 v. 1.01 March 2015 (General release)
169 - NEW: added ability to supply a configuration notecard that will be read on
170 initialization and override scripted variable values (written by Seth Nygard -
171 thanks Seth!)
172 - TWEAK: tweaked Seth's configuration addition to support both his nameset as
173 well as the actual variable names being supplied
174 - TWEAK: slight further optimizations of memory use
175 - TWEAK: system is now made completely inactive if worn
176 - TWEAK: only owner is now notified when initialization completes
177 - FIX: minor text tweaks and typo corrections
178
179 v 1.02 May 2015 (Update Release)
180 - BUGFIX/OVERSIGHT: add flag so if an addon rezzes an object core is prevented
181 from interpreting this as an edit handle being rezzed and entering edit mode
182 - BUGFIX: fixed a bug in edit mode list wrapping where advancing from the last
183 animation in a group using "NEXT" would pull the incorrect data for the next
184 (first) animation.
185 - TWEAK: set a minimal sit target on root to allow sitting on it from distances
186 greater than 10m
187 - TWEAK: When displaying animation number range in dialog menu, display the
188 actual range instead of possible range
189 - NEW: added support for the Object-Rezzing prop add-on "NC_PROP" by Neo Cortex
190 (code from Neo)
191 - NEW/TWEAK: Altered script logic to optionally allow NPCs to occupy a PMAC
192 object with no "real" avatars being present and added a user configuration
193 variable to enable or disable this feature. By default it will be set to FALSE.
194 If the NPC is to be rezzed by another object/script that script will also need
195 to handle seating it, later unseating it, and removing it from the scene.
196 Initially rezzing a PMAC NPC still requires an avatar user but if that user
197 stands when NPC flag is TRUE, the NPC will not be removed (I'll need to sit down
198 again and remove it). Be careful NOT to reset the core script when a PMAC NPC is
199 still in the scene since this will strand it.
200 - NEW/TWEAK: Added new user option to show the groups menu instead of the
201 current group's animation menu when you first initiate the dialog. Subsequently
202 if you close the dialog and re-show it, you should received whatever your most
203 recent dialog was unless someone else was in control in the interim.
204
205
206 **** UPDATING FROM PMAC 1.01 TO PMAC 1.02 *****
207
208 - if you use in-script user settings instead of the configuration notecard,
209 transfer your settings to the PMAC core 1.02 script.
210 - delete the Core 1.01 script from your object's inventory
211 - place a copy of the Core 1.02 script into the object's inventory - it should
212 compile and enable itself automatically
213 - replace the old READ ME 1 and READ ME 2 notecards with the updated versions.
214 The other three core read me notecards remain unchanged.
215 - no further changes are require...your system is ready for use
216 - OPTIONAL: if you want to use the new object-rezzer add-on by Neo Cortex, place
217 a copy of it in the object's inventory and follow the instructions he supplied
218 for setting up your animations to use it. The MLP parser by Seth Nygard does not
219 currently support conversion of MLP rezzed objects so you will need to do this
220 manually.
221
222
223 ***** LICENSE *****
224
225 All PMAC scripts and documentation are my own creation and released under
226 Creative Commons Attribution-Non-Commercial-ShareAlike 4.0 International
227 license. Please be sure you read and adhere to the terms:
228 https://creativecommons.org/licenses/by-nc-sa/4.0/
229
230 Unless otherwise specified, animations contained in any Paramour product are
231 *not* my creation but were obtained under equivalent CC terms. If you are the
232 creator of one of the animations and do not wish it distributed please notify me
233 and I will immediately remove it from all current and future offerings.
234
235 ***** CONTACT *****
236
237 You can most easily contact me in world at refugegrid.com:8002 or on G+

PMAC

PARAMOUR MULTI-ANIMATION CONTROLLER (PMAC) v1.02 (OSSL)

Category: NPC
By : Aine Caoimhe
Created: 2015-11-24 Edited: 2015-11-23
Worlds: OpenSim

1 PARAMOUR MULTI-ANIMATION CONTROLLER (PMAC) v1.0 (OSSL)
2 by Aine Caoimhe January 2015
3
4 OWNER INITIAL SET-UP AND INSTALLATION
5
6
7 **** OVERVIEW *****
8
9 A new PMAC system is usually supplied inside an object that is already set up and configured for general use,
10 containing all of the necessary animations, notecards, etc. In most cases you can simply "rez and play" right
11 away provided your region is already set up to allow the necessary script functions.
12
13 This notecard contains details configuring regions correctly to allow PMAC to work, details about some of the
14 user settings you may wish to change, and trouble-shooting tips.
15
16
17 The step-by-step overview for a new user is to set (or confirm) the following:
18
19 1. Ensure the server supports the script
20 2. Ensure the region is configured to allow the necessary functions
21 3. Rez the object
22 4. If necessary, reset or even recompile the scripts
23 5. Adjust any user settings according to preference
24
25
26 ***************************************
27 1. Ensure Server Support of the Script
28 ****************************************
29
30 PMAC uses both LSL and OSSL functions that make extensive use of vectors and rotations for positioning and text-based
31 notecards for data storage. The server must use the period (decimal point)(.) as the decimal separator for numbers that
32 require this precision (called "floats").
33
34 In almost all cases your server will already have this configuration (otherwise other scripts would also have issues)
35 but if you experience problems with odd behaviour this is the first thing to check. Some computer localizations (particularly
36 linux systems in Europe) may be configured to use the comma (,) as the decimal separator which will *not*
37 work with this system (and many other LSL scripts). Consult your operating system guide as to how to
38 configure the computer to use the decimal point separator.
39
40
41 ********************************************************************
42 2. Ensure the Region is Configured to Allow the Necessary Functions
43 ********************************************************************
44
45 The PMAC script makes extensive use of a special set of functions that are only available in Opensim: OSSL functions.
46 Depending on your region's configuration, these may or may not be available so it is essential to check and ensure that
47 the correct settings have been made. As a general rule of thumb:
48
49 - By default, stock Opensim builds will need changes to the region configuration
50 - By default, the Diva distribution is pre-configured correctly and will require no changes
51 - Most open grid configurations will require at least some changes
52 - Many region hosting services will require some changes, although this is slowly changing, and for many
53 such providers any changes to the ini files must be made by their staff. If that's the case for you, please
54 provide them with a copy of this information.
55
56 ****** NOTE: there have been recent changes to OSSL function set-up and enabling *****
57 The method for setting OSSL functions changed as of Opensim Git #2e1f5bb (r/25931 2015-04-14) so the instructions
58 below may not be fully applicable to your region's set-up. Please see:
59 http://ainetutorials.blogspot.ca/2015/04/notice-ossl-implementation-changes-for.html for further information
60 You will still need to enable NPC in the [NPC section] in either case.
61
62 Unless you are using a special custom configuration (such as the Diva distribution) the configuration changes that
63 need to be made are located in the /bin/opensim.ini file that is in the same directory as the opensim.exe for the
64 instance that runs the region.
65
66 Scroll down through the opensim.ini file and look for the [NPC] section and ensure that NPCs are enabled to allow
67 PMAC to rez them in the region
68
69 [NPC]
70 Enabled = true
71
72 Now scroll through the opensim.ini file and find the [XEngine] section -- usually it is before the [NPC] section.
73 There are a number of changes that need to be made here. First, we need to generally allow OSSL functions, then
74 we need to ensure that some of the ones used by PMAC are both available and suitably "protected" against abuse
75 by other visitors to your region.
76
77 In the [XEngine] section find the line where the AllowOSFunctions variable is set and ensure it is set to true.
78
79 [XEngine]
80 AllowOSFunctions = true
81
82 A little further down, look for OSFunctionThreatLevel which sets a global "threat level" for OSSL functions.
83 If an OSSL has this threat level or less, it will be allowed for any script that wants to use it. By default
84 Opensim has this set to VeryLow but in my opinion a global Low setting is perfectly secure.
85
86 [XEngine]
87 OSFunctionThreatLevel = Low
88
89 PMAC uses a number of functions that are "higher threat" than this, though. You could just set a far higher
90 global threat level but then you would expose yourself and your region guests to malicious visitors so I strongly
91 advise against doing so. Instead, you can set specific functions to be available only to a limited range of users.
92 The options are to specify one or more of the following to have permission to use each function (the one(s) you use
93 will depend on your specific set-up):
94
95 - ESTATE_OWNER allows scripts owned by the estate owner of the region to use those functions
96 - ESTATE_MANAGER does the same for scripts owned by one of your estate managers
97 - PARCEL_OWNER does the same for scripts owned by a parcel owner and located in that parcel
98 - PARCEL_GROUP_MEMBER does the same for scripts owned by a member of the same group that the parcel
99 - by specifying the UUID of a user (or multiple users) to allow scripts they own to use the functions
100
101 Note that in all cases it is the script owner (usually whoever rezzed the object) that is checked against
102 the permission, not the person using it.
103
104 It is beyond the scope of these instructions to go any further into this so if you wish to get absolutely all
105 of the details see: http://opensimulator.org/wiki/Threat_level
106
107 For someone who is a region owner (usually this means ESTATE_OWNER) I would suggest setting all OSSL functions
108 to be available at least for any scripts that you own, and in many cases probably for those belonging to any E
109 STATE_MANAGER you assign (since to give them those powers you probably trust them sufficiently not to abuse them).
110
111 My own regions' XEngine section includes the following lines to enable OSSL and allow scripts owned by myself
112 or my estate managers to use functions that are either used by PMAC or are likely to be used by other common
113 OSSL scripts you might wish to write/use (usually relating to NPC or animation):
114
115 [XEngine]
116 AllowOSFunctions = true
117 OSFunctionThreatLevel = Low
118 Allow_osMessageAttachments = true
119 Allow_osGetGridName = ESTATE_OWNER, ESTATE_MANAGER
120 Allow_osGetGridNick = ESTATE_OWNER, ESTATE_MANAGER
121 Allow_osOwnerSaveAppearance = ESTATE_OWNER, ESTATE_MANAGER
122 Allow_osGetLinkPrimitiveParams = ESTATE_OWNER, ESTATE_MANAGER
123 Allow_osGetPrimitiveParams = ESTATE_OWNER, ESTATE_MANAGER
124 Allow_osMakeNotecard = ESTATE_OWNER, ESTATE_MANAGER
125 Allow_osNpcCreate = ESTATE_OWNER, ESTATE_MANAGER
126 Allow_osNpcGetPos = ESTATE_OWNER, ESTATE_MANAGER
127 Allow_osNpcGetRot = ESTATE_OWNER, ESTATE_MANAGER
128 Allow_osNpcLoadAppearance = ESTATE_OWNER, ESTATE_MANAGER
129 Allow_osNpcMoveTo = ESTATE_OWNER, ESTATE_MANAGER
130 Allow_osNpcMoveToTarget = ESTATE_OWNER, ESTATE_MANAGER
131 Allow_osNpcPlayAnimation = ESTATE_OWNER, ESTATE_MANAGER
132 Allow_osNpcRemove = ESTATE_OWNER, ESTATE_MANAGER
133 Allow_osNpcSaveAppearance = ESTATE_OWNER, ESTATE_MANAGER
134 Allow_osNpcSay = ESTATE_OWNER, ESTATE_MANAGER
135 Allow_osNpcSetRot = ESTATE_OWNER, ESTATE_MANAGER
136 Allow_osNpcShout = ESTATE_OWNER, ESTATE_MANAGER
137 Allow_osNpcSit = ESTATE_OWNER, ESTATE_MANAGER
138 Allow_osNpcStand = ESTATE_OWNER, ESTATE_MANAGER
139 Allow_osNpcStopAnimation = ESTATE_OWNER, ESTATE_MANAGER
140 Allow_osNpcTouch = ESTATE_OWNER, ESTATE_MANAGER
141 Allow_osNpcWhisper = ESTATE_OWNER, ESTATE_MANAGER
142 Allow_osSetPrimitiveParams = ESTATE_OWNER, ESTATE_MANAGER
143 Allow_osSetProjectionParams = ESTATE_OWNER, ESTATE_MANAGER
144 Allow_osSetRegionWaterHeight = ESTATE_OWNER, ESTATE_MANAGER
145 Allow_osSetTerrainHeight = ESTATE_OWNER, ESTATE_MANAGER
146 Allow_osAvatarPlayAnimation = ESTATE_OWNER, ESTATE_MANAGER
147 Allow_osAvatarStopAnimation = ESTATE_OWNER, ESTATE_MANAGER
148 Allow_osForceOtherSit = ESTATE_OWNER, ESTATE_MANAGER
149 Allow_osGetNotecard = ESTATE_OWNER, ESTATE_MANAGER
150 Allow_osGetNotecardLine = ESTATE_OWNER, ESTATE_MANAGER
151 Allow_osGetNumberOfNotecardLines = ESTATE_OWNER, ESTATE_MANAGER
152 Allow_osRegionNotice = ESTATE_OWNER, ESTATE_MANAGER
153 Allow_osAgentSaveAppearance = ESTATE_OWNER, ESTATE_MANAGER
154
155 There are many other OSSL functions but the above will suffice for most people and includes the ones
156 necessary to allow PMAC to work correctly. After making your changes you will need to save the
157 opensim.ini file, then restart the simulator (restarting just the region will not suffice).
158
159
160 ******************
161 3. Rez the Object
162 ******************
163
164 Not much to say here...just rez it to ground and position it wherever you want it to be.
165
166
167 *******************************************
168 4. Reset or Recompile Scripts if Necessary
169 *******************************************
170
171 Exactly what will be required here depends on your main server configuration, your region
172 configuration, and where you obtained the PMAC object. In many cases you won't need to do
173 anything...simply rezzing the object will automatically cause the script to reset and initialize.
174 After a second or two, you'll see a message in general chat telling you that initialization is
175 complete and the object is ready to use.
176
177 If this doesn't happen, you may simply be able to right-click on the object and use the radial
178 menu to reset the script.
179
180 In some regions and/or grids, it is necessary to "recompile" a script -- particularly when the
181 object was obtained from a different grid while hypergridding -- before it will work. Although
182 a little tedious, if you don't know how to recompile a script here is a set of steps to take to
183 be absolutely sure that everything is running properly:
184
185 1. Select the object while in edit mode in your viewer
186 2. Take a copy of the PMAC Core script into your own inventory
187 3. If the object contains any add-on scripts, take copies of those too
188 4. Look for an object in inventory called "~~~positioner" and take a copy of it as well
189 5. Wear the ~~~positioner object (on your left hand), then edit it and open the script inside it
190 6. Make a tiny change to this script by adding a space at the end of one of the red comment lines
191 (or add an empty blank line at the very end)
192 7. Now "Save" this. You should simply see a "Save complete" notice and no errors...this has forced
193 that script to recompile.
194 8. Now unwear the ~~~positioner
195 9. Delete the original ~~~positioner from the main object's inventory
196 10. Now place your newly recompiled ~~~positioner from your inventory into the original object
197 11. Delete each of the scripts that you took copies of from the original object
198 12. If you have any add-on scripts, put the copies of these back into the original object first.
199 They will automatically recompile when you do this.
200 13. And finally, place the copy of the PMAC Core script back into the original object and it will
201 recompile as well.
202
203 You should now see the message telling you that initialization is complete and the object is ready to use.
204
205
206 ***********************************************
207 5. Adjust User Settings According to Preference
208 ***********************************************
209
210 In most cases this is entirely optional because the object will come pre-configured to be suitable
211 for the average user. If you want to look at the settings or change any, open the PMAC Core script
212 and you will see a section at the top where you can do this -- it's divided into a "general user
213 section" and an "advanced/builder" section.
214
215 I'll give detailed information for the basic ones, and assume that anyone using the advanced/builder
216 ones will have enough scripting knowledge to need a little less detail.
217
218 ***** General User Settings: *****
219
220 Name: defaultGroup
221 Type : string
222 Use: Identify the animation group to load when the PMAC object initializes.
223 Details:
224 The name you supply here is just the simple group name, not the full notecard name, and must be
225 enclosed in quotation marks (with a semi-colon after the last one). If your animation group's
226 notecard name is ".menu123A Groupname" then the value you'd enter just "Groupname" for this variable.
227 It will always be loaded initially, even if its permission settings wouldn't normally allow the user to do so.
228
229 ********
230
231 Name: resetOnQuit
232 Type : integer (boolean) TRUE or FALSE
233 Use: Indicate whether the script should reset and re-initialize the object when you stop using it.
234 Details:
235 If you set this to TRUE (no quotation marks!) then after you finish using the object and everyone
236 stands up, the object will completely reset itself and reload all of its default values. This is useful
237 for furniture items where you might want them to have a specific set of animations active whenever
238 someone new sits down and you don't want to have to remember to reset it back to these when you finish using it.
239
240 If you set this to FALSE it will simply leave everything the way it's currently set and when someone
241 sits down again it will resume with whatever animation was most recently loaded. If it was in auto mode
242 when you stopped using it, it will resume auto mode when the next person sits.
243
244
245 **********
246
247 Name: ownerUseReq
248 Type : integer (boolean) TRUE or FALSE
249 Use: Indicate whether the owner must be sitting on the object before anyone else is allowed to sit
250 Details:
251 If set to TRUE, the object will refuse to allow anyone other than the owner to sit on it. Once the owner
252 is seated, other people will then be allowed to sit as well. If the owner then stands, the existing users
253 will be allowed to remain there but no new user (other than the owner) will be allowed to sit. Normally
254 you'd only set this to TRUE for an object that you wish to reserve for your own private use.
255
256 If set to FALSE, anyone can sit down and start using the object even if the owner is offline or in another region.
257
258 **********
259
260 Name: ownerOnlyMenus
261 Type : integer (boolean) TRUE or FALSE
262 Use: Indicate whether only the owner is allowed to access the dialog menus
263 Details:
264 If set to TRUE, only the owner can ever touch the object to access the dialogs. Anyone else who sits will
265 simply play whatever the current animation is and continue to do so until the owner uses the menu to change it.
266 If auto mode is enabled then it will change animations based on its timer setting so you could use this as a
267 sort of "multi-person AO" for a furniture item.
268
269 If set to FALSE, anyone can touch the object and ask to be the controller in charge of the dialogs. The menu
270 options they see will depend on the permission settings of the individual group notecards and NPC notecards.
271 Only the owner ever sees the top line of the OPTIONS menu.
272
273 ***********
274
275 Name: ownerUseUnlocksPerms
276 Type : integer (boolean) TRUE or FALSE
277 Use: Indicate whether other users temporarily gain "owner" permissions for groups and NPCs if the owner seated
278 Details:
279 If set to TRUE, when the owner is one of the users currently seated on the object, all users can then access
280 animation groups and NPCs that are normally restricted to the owner. This is a setting you might use for a bed
281 where you want a different (larger) set of animations available when you're there, but want to restrict them otherwise.
282
283 If set to FALSE, only the owner will ever be able to see or load notecards where the permission is set to
284 owner-only; and only group members with the group currently active will see or be able to access notecards where
285 the permission is set to group.
286
287 *************
288
289 Name: autoOn
290 Type : integer (boolean) TRUE or FALSE
291 Use: Indicate whether the PMAC Auto mode should be turned on by default
292 Details:
293 Auto mode automatically cycles through the animations in the currently-loaded animation group, advancing based
294 on the time set for autoTimer (see below). If you set this to TRUE, this will automatically be enabled any time
295 the script is reset so it would be particularly useful if you're using the ownerOnlyMenus=TRUE setting above.
296
297 If set to FALSE, the object will be in manual mode when the script resets.
298
299 Regardless of this setting, when you stop using the object it will remember and whatever its last setting was
300 and use it when someone sits down to start using it again. If you don't want that to happen you would need to
301 set the resetOnScript=TRUE (see above) which will cause the script to reset after you stop using it and then
302 pick up and use the default value again.
303
304
305 *************
306
307 Name: autoTimer
308 Type : float
309 Use: Set a default time to use for the auto mode's timer
310 Details:
311 The value set here is a number larger than 0.0 and determines the default number of seconds to wait before
312 advancing to the next animation when PMAC is in auto mode. You can use the OPTIONS menu to change this
313 during use, too. When you stop using the object it will remember whatever value was most recently used and won't
314 pick up and use this default value again until the script is reset.
315
316 *************
317
318 Name: showGroupsMenuFirst
319 Type : integer (boolean) TRUE or FALSE
320 Use: determines which menu level to show first when initiating dialog
321 Details:
322 When FALSE, initiating the dialog will display the current group's animation selection menu (PMAC 1.01 and MLP
323 normal behavour). When TRUE, the groups menu will be shown instead. Default is FALSE.
324
325 *************
326
327 Name: allowSoloNPC
328 Type : integer (boolean) TRUE or FALSE
329 Use: Determines whether an NPC can occupy a PMAC object when no avatars are currently using it
330 Details:
331 When FALSE, PMAC will not allow a NPC to sit on a PMAC object unless an avatar user is already seated. When
332 there are no remaining "real" users, any remaining NPC are removed. When TRUE, NPCs are allowed to occupy the
333 object but it becomes your responsibility to later unseat them and remove them from the scene. DO NOT manually
334 reset the core script until you've removed any NPCs it is controlling or they will become stranded. NOTE: using
335 the dialog option "QUIT" will still remove all NPCs regardless of this setting.
336
337
338 ***** Advanced/Builder Settings: *****
339
340 There are five variables in this section, three of which are simply convenience/preference tweaks
341 that change the appearance of the "positioner" handles when you are in edit mode.
342
343 handleName is the object name in inventory for the positioning handle. If you want to use a different
344 handle than the one I supply by default, simply put it in inventory and enter its name for this
345 variable to have it be rezzed instead.
346
347 handleColours is a list of nine LSL vector colours to use for the positioning handles in edit mode.
348 You must supply exactly 9 vectors and they are rezzed in the order supplied in the list for each position.
349
350 handleSize is the vector dimension to set for each of the positioning handles. All handles will use this same size.
351
352 handleAlpha is the (float) alpha value that will be used for all of the positioning handles.
353
354 And finally, baseAn is the name of a priority 1 animation in inventory that should be used for synch.
355 I supply one in almost all animation products I use that is just a generic standing pose but if you
356 want to use a different one then drop it into inventory and change this variable to that animation's
357 name. This is a work-around method for the "fix" that was made to Opensim in 2012 that broke the old
358 behaviour of stopping and starting animations. While there are various ways to make synch work, in my
359 view this is the most reliable one for this sort of application. In normal use, you will never see it
360 or even be aware that it's running underneath the other animations. When a user stands it is
361 automatically released along with the currently playing animation.
362
363
364 After making any changes to any of the above, save and your changes will be applied.
365
366 *********************************
367 6. Optional Configuration Notecard
368 *********************************
369
370 Instead of manually setting configurations in the script directly, you can include a configuration
371 notecard in PMAC's inventory instead. Bu default it must have the name ".PMAC-CONFIG" although
372 you can change this in the script's user settings. Each line of the notecard can contain a parameter
373 name, followed by an equals sign followed by the desired value. Any that are included will override
374 the values set in the script directly, and any that omitted will use the script's current default
375 values. Commands recognized:
376 - defaultGroup or DefaultGroup
377 - resetOnQuit or ResetOnQuit
378 - ownerUseReq or OwnerUseReq
379 - ownerOnlyMenus or OwnerOnlyMenus
380 - ownerUseUnlocksPerms or OwnerUseUnlockPerms
381 - autoTimer or AutoTimerValue
382 - baseAn or BaseAnimation
383 -showGroupsMenuFirst or ShowGroupsMenuFirst
384 - allowSoloNPC or AllowSoloNPC
385
386 Example: this would change the default menu to load on startup to "Cuddles", changes the behaviour to
387 reset the script on quit, and changes the system to allow solo NPCs.
388
389 defaultGroup = Cuddles
390 ResetOnQuit = TRUE
391 allowSoloNPC = TRUE
392
393 ***************************
394 7. Other
395 ***************************
396
397 In most cases you'll now be up and ready to go and you will probably not have had to do many of the above steps.
398
399 If you use add-ons to the PMAC system, they should supply any necessary instructions for how to set them.
400 Add-ons are optional and do not need to be included in an object unless your set-up specifically uses them.
401 If a notecard supplies commands for one but the add-on script isn't present, the command is ignored but it
402 will in no way impair the operation of the core script.
403
404 Unless you are a very advanced scripter I would strongly suggest you never change anything in the script
405 other than these settings at the top (up to the place where it warns you not to change anything below it).

PMAC

PARAMOUR MULTI-ANIMATION CONTROLLER (PMAC) v1.02 (OSSL)

Category: NPC
By : Aine Caoimhe
Created: 2015-11-24 Edited: 2015-11-23
Worlds: OpenSim

1
2 // Provided under Creative Commons Attribution-Non-Commercial-ShareAlike 4.0 International license.
3 // Please be sure you read and adhere to the terms of this license: https://creativecommons.org/licenses/by-nc-sa/4.0/
4
5 PARAMOUR MULTI-ANIMATION CONTROLLER (PMAC) v1.0 (OSSL)
6 by Aine Caoimhe January 2015
7
8 USER INSTRUCTIONS
9
10
11 **** OVERVIEW *****
12
13 PMAC is designed with the intent that it be highly intuitive and easy to use
14 with absolutely no instruction at all.
15 Hopefully this will be the case for most situations, although there is one menu
16 that only the owner can access --
17 the Edit menu -- that does require detailed instructions for safe use.
18
19 **** GENERAL USE -- ANIMATIONS AND GROUPS ****
20
21 To use a PMAC object it's as simple as "sit"....that's it!
22
23 Just sit on the object and PMAC will position you and play the current
24 animation. If it's in auto mode it will change
25 animations every so often, cycling through the ones that are available based on
26 the currently loaded "animation group".
27 If it isn't in auto mode, or if you want to select animations manually, you'll
28 need to touch the object after you sit
29 on it to get the main controller menu. The first person who touches it will
30 automatically become the "controller".
31
32 Only one person can be in control of the menus and they must be seated on the
33 object. If someone else is seated and
34 touches the object, they'll be asked if they want to "take control" away from
35 whoever has it (unless the owner has
36 set it not to allow this). If you take control away from someone else you'll be
37 in charge and the other person will
38 then be along for the ride. Since nobody likes a control war, it's usually a
39 good idea to speak to the current
40 controller before simply grabbing it away from them.
41
42
43 If you've any other multi-avatar system of this type (like MLP) things will look
44 very familiar to you even though
45 it works in a rather dramatically different way at the script level. An
46 "animation" in PMAC's list is actually a
47 set of animations that are in inventory and get played together for the users
48 that are seated (typically a couple,
49 but PMAC can be set to handle up to 9 concurrent sitters). when you select an
50 animation, PMAC handles which animation
51 is played for each user and where to position them and does so based on
52 information stored in a notecard.
53
54 Usually animations are supplied in "groups" of animations -- perhaps one group
55 for sitting, one group for lying,
56 another group for cuddling, and so on -- each of which has a number of
57 appropriate animations assigned to it. You
58 pick a group, then pick the animation you want to play from that group.
59
60 When you first use PMAC it will already have a default group selected and you'll
61 see the list of animations for that group. PMAC shows up to 6 animation names,
62 and if there are more than that you can use the PREV and NEXT buttons to scroll
63 through additional pages of them (only the 6 animation names at the top change,
64 the rest of the menu stays the same).
65
66 When you pick a new animation the region has to send that to everyone's viewer
67 and some can take longer to arrive than others, so animations that are intended
68 to "fit" together can easily get out of synch the first time you use them. Even
69 if they look fine in your viewer, they might be out of synch for someone else.
70 Pressing the "SYNCH" button tells all of the viewers to restart the current
71 animation from the beginning and should fix that for everyone.
72
73 The QUIT button does *almost* what you think it does. QUIT makes *everyone*
74 stand up and stop using the object, not just you. If you want to stop using it
75 but want to leave everyone else seated, simply "stand" using your viewer. Once
76 you do so, PMAC will ignore any button you click on the still-open dialog box so
77 you can either close it or just click any button. The next person who is still
78 seated and touches the object will take control (automatically).
79
80 If you want to change to a different group of animations, press the GROUPS
81 button to get a list of the groups you can access. Just like the animation menu,
82 you may need to scroll through pages of them using the PREV and NEXT buttons to
83 find the one you want. When you pick one, you'll be taken back to the animation
84 menu with a list of the animations in the group you selected.
85
86 If you accidentally close the dialog menu, just touch the object to have it
87 re-displayed.
88
89
90 **** THE OPTIONS MENU ****
91
92 You can enter the options menu by clicking the OPTIONS button on either
93 animation or groups menu pages and what you see will depend a little on who you
94 are and what is currently happening (and how the item has been configured). The
95 buttons on the very top row (EDIT ON and MENUS LOCK/UNLOCK) are only ever
96 visible to the owner. The others can be available to any user. Here's a quick
97 look at each:
98
99 QUIT does exactly the same thing as it does in all menus -- everyone is forced
100 to stand.
101
102 SYNCH also does exactly the same thing.
103
104 BACK will usually take you back to the animations page unless you're in auto
105 mode in which case it will take you to the groups menu instead.
106
107 SWAP let's you move to another position unless the currently playing animtion is
108 only set up for a single user. If the animation is set up for two users you'll
109 immediately swap places (even if there's nobody currently occupying the other
110 spot). If the animation is for any more than two users you'll be asked what
111 position you want to move to and who (if anyone) is currently sitting there. You
112 can't directly make two other people swap places but you could either let one of
113 them take control for a moment, do the swap, then take control back; or you can
114 swap with one, then the other, then again with the first person again and you'll
115 achieve the same thing (it's a little inconvenient, granted, but much simpler on
116 the "behind the scenes" script level to do it that way).
117
118 UNSIT allows you to force someone else to stand. Obviously that might not be a
119 very polite thing to do but there are a couple cases where it's useful:
120 - if the other person has gone AFK for a long time and you simply don't want
121 them standing there
122 - if one of the other users is an NPC, when you make the NPC stand it will be
123 removed instead, freeing up the spot for someone else to occupy
124 - if you want to load an animation group that is for a smaller number of users
125 than are currently seated you can make someone stand to free up the spot and
126 allow you to load the new group (be nice about it though...)
127 If there are only 2 users (yourself and one other) clicking UNSIT will
128 immediately unsit the other user. If there are more, you'll be asked which user
129 you want to force to stand.
130
131 ADD NPC allows you to have a vacant position filled by an NPC. The button won't
132 be available if there aren't any empty positions. When you press it you'll be
133 given a list of the NPCs you can load (it's a multi-page list if there are many
134 possible options). Select one and wait a moment for the NPC to rez and join you.
135 To later remove the NPC you can either UNSIT them, or QUIT (which removes all
136 NPCs) or have all "real" users stand -- if there are no avatars still seated all
137 remaining NPCs are removed and the script treats it like a QUIT.
138
139 [Slight technical note:
140 After adding an NPC to the last available vacant position the "ADD NPC" button
141 will often still be shown until you leave the options menu and then return to
142 it, even though there isn't an available position for the NPC to occupy. If you
143 try to add another one you'll be informed that there's no room and the button
144 will update correctly too. This is due to a slight delay in the time it takes an
145 NPC to sit down and be "registered" as a user which is almost always longer than
146 the time it takes to redisplay the options menu. While the script could be
147 changed to "fix" this, it would also mean the menu wouldn't reappear for up to
148 an additional few seconds (and it is already delayed a little by the need to
149 wait for the region to register the NPC) which becomes considerably more
150 annoying than the "phantom" button so I chose to leave this minor "sort-of-bug"
151 instead.]
152
153 AUTO takes you to a menu where you can start or stop PMAC's auto mode. AUTO OFF
154 stops it. AUTO ON turns it on using whatever the last selected (or default) time
155 is. Selecting one of the time buttons will turn on auto mode if it isn't
156 already, and use that selected time.
157
158 SPECIALS is a button that is only visible if you are using an PMAC add-on that
159 needs to offer you a menu button. If you have one that does, you'll find that
160 button in the list of buttons displayed in the SPECIALS menu. If you have a lot
161 of add-ons the buttons are sorted alphabetically by name and there could be
162 multple pages of them. You'll need to consult your add-on's instructions to find
163 out what their buttons do and sometimes this will temporarily transfer you to a
164 separate menu system controlled directly by the add-on. Once finished, you
165 should be automatically returned to the PMAC menu.
166
167 For the curious, there is one additional empty button beside SPECIALS that will
168 always show "-" and be ignored. That's in case there's a need for another button
169 to do something in a future version of PMAC.
170
171
172 **** OWNER-ONLY OPTIONS ****
173
174 The very top row of the options menu is only ever displayed for the owner and
175 also contains an empty "-" button that doesn't do anything and is for possible
176 future use. The other two buttons are:
177
178 MENUS LOCK/UNLOCK. toggles between allowing other users being allowed to take
179 control and access the menus. If you LOCK menus then only you can ever take
180 control and use them. If you UNLOCK them, anyone who is seated can take control.
181 Be a little cautious with locking menus since PMAC remembers this setting even
182 after you quit. If the user settings don't cause the script to reset on quit,
183 you could accidentally leave everyone locked out of being able to use the
184 controls until you return and unlock if for them. Of course being able to
185 *intentionally* do this is the whole point of having the button there in the
186 first place. Just use with care. :)
187
188 EDIT ON is probably the most important button on the entire menu for an owner,
189 but also requires considerably more detailed instructions which aren't suitable
190 for this basic user instructions notecard. Briefly, though, this switches you
191 into edit mode, allowing you to then adjust the positions of each animation and
192 either temporarily or permanently store those changes without ever touching the
193 notecards or doing any copy-pasting. You cannot enter edit mode unless all
194 available positions are occupied -- by avatars or NPCs -- and once in edit mode,
195 you will want to ensure that nobody stands until you've finished and stored your
196 changes. For all the sordid details, see the Basic Building instructions
197 notecard.

PMAC

PARAMOUR MULTI-ANIMATION CONTROLLER (PMAC) v1.2 (OSSL)

Category: NPC
By : Aine Caoimhe
Created: 2015-11-24 Edited: 2015-11-23
Worlds: OpenSim

1
2 // Provided under Creative Commons Attribution-Non-Commercial-ShareAlike 4.0 International license.
3 // Please be sure you read and adhere to the terms of this license: https://creativecommons.org/licenses/by-nc-sa/4.0/
4 by Aine Caoimhe January 2015
5
6 BASIC TWEAKING INSTRUCTIONS
7
8
9 **** OVERVIEW *****
10
11 Under the hood, PMAC is an extremely complex scripted system that allows advanced scripters to unleash the full potential of a highly flexible command system. The vast majority of owners will neither have the skills nor inclination to do this, though, so PMAC also include a set of integrated "basic" tweaking tools that are easy for almost anyone to use.
12
13 As an owner, and using these basic tools, you can tweak or customize positions for each of the animations in your PMAC object, either temporarily or permanently. With very little additional effort you can add new NPCs and -- if you're even more adventurous -- you can even set up whole new animation groups. It's mostly a case of your comfort level, patience and willingness to devote time to doing it.
14
15 If you haven't run away in terror yet, read on...
16
17
18 **** VERY BASIC TWEAKING ****
19
20 Almost any owner will want to do some basic tweaking of positions at some point, and PMAC is designed to make this extremely easy. You'll make these adjustments using PMAC's built-in edit mode which you can access from the top row of the options menu by clicking the EDIT ON button.
21
22 You can't enter edit mode until you pick an animation group and then select an animation. If you later pick a different animation group but haven't yet selected an animtion from it, you won't be able to enter edit mode yet (there are technical reasons for this that I won't bore you with). Also, all available positions for that group have to be occupied, so unless the group is for only one person you'll need a friend or two...or just ask some extremely patient NPCs to join you instead. Once all positions are filled you'll be able to switch to edit mode and you'll see a new menu: the EDIT menu.
23
24 Once you do, you'll see PMAC rez a "positioning handle" at the location of each user. The handles are labelled and colour-coded to help easily distinguish them from one another (there are no "boy" or "girl" handles so it doesn't matter who is occupying a given position as long is it's set up the way you want it). Switch to edit in your viewer and you can then grab the handles and move them around just like any prim.
25
26 As soon as you let go of your mouse button, the avatar associated with that handle will move to this new location. If you rotate a handle, the avatar will also rotate to match this rotation as soon as you release your mouse. It takes a little bit of getting used to the "move after release" approach to positioning but this is common (and necessary) for a no-poseball system -- it's because until you release the mouse button the region doesn't know you've moved the handle so it can't move the avatar to match it.
27
28 If you need to re-synch your users, the edit menu's SYNCH button does exactly the same thing it does in all other menus.
29
30 WARNING!!! The PREV and NEXT buttons don't!!!
31
32 Before going any further, it's important to understand a tiny bit about how PMAC handles animation data. When you select an animation group the data for all animations in that group is loaded into the script's active memory. While you're editing positions, this doesn't change that stored script memory until you intentionally ask it to. Once you do, the existing data in memory is overwritten with the new positions and rotations and the old values can't be retrieved without leaving edit mode, then reloading the old values into memory from the notecard (you will need to switch to a different group first, then switch back again). However none of these stored changes in memory affect the permanent data stored in the animation group's notecard until you specifically tell PMAC to do so.
33
34 While you're using the handles to position your users for the current animation, you can press the REVERT THIS button to have them moved back to the currently stored position and rotation for each. If you press STORE THIS it will update the script's memory with their current positions and rotations. Once you've done this, the REVERT THIS button would revert to that newly stored position, not your previous one still stored on the notecard.
35
36 If you leave edit mode for any reason (using EDIT OFF or if someone stands) it also stores the current position and rotation for the animation to script memory before removing the handles so if you don't want this updated you'll need to REVERT THIS first. More on that in a moment.
37
38 While in edit mode, the NEXT and PREV buttons will first store the current position and rotation
39 for the current animation to script memory, then change to either the next or previous animation
40 in the group. If you don't want the current one to change, be sure to REVERT before you use either button.
41
42 Further complicating matters, a few PMAC add-ons may need to store special information as well and
43 the exact method they use could vary a little from one to another. If you're using add-ons you'll
44 need to double-check what their instructions require. For more complex ones there could be some
45 data that needs to be cut and pasted from chat and later inserted into a notecard (there's no way
46 to avoid this, unfortunately). A few could need you to press the "STORE ADDON" button before you
47 change to a different animation but in most cases you won't need to use this button (the button is
48 provided as a "just in case" thing for add-on scripters).
49
50 None of these changes are permanent stored yet. If you leave edit mode PMAC will remember them only
51 while the current animation group remains loaded. As soon as you select a different animation group
52 -- even if you never pick an animation from it -- those changes are lost. If you want to keep them
53 for use later, you will need to tell PMAC to do so before you leave edit mode (or you can go back
54 into edit mode as long as you haven't yet changed animation group). You have two options:
55
56 SAVE CARD will first update the current animation's position and save it to script memory. Then, after
57 a short delay, it will *overwrite" your current animation group's notecard with the new data. After
58 that, the new data will always load from that card but of course any old data will be lost.
59
60 SAVE NEW will do almost the same thing, except instead of overwriting your existing notecard it will
61 save the data to a copy of the card with a number appended to the end of the name. In effect this
62 creates a new animation group so you'll see both your old group and your new one available in the
63 list of animation groups you can load. When you "save new" PMAC also switches to this new group
64 in memory so any additional edits you make will be applied to this copy, not your original group.
65 If you "save card" again, it will overwrite the data in your card copy, not the original. If you
66 "save new" again, it would create a third group with another number appended to it.
67
68
69 This allows you to keep one copy of a card for general use, and then a customized version for use with
70 a specific partner, and load whichever one is appropriate for the moment.
71
72 Once you're done, use EDIT OFF to remove the positioning handles (they're actually rezzed and destroyed
73 as needed, not just hidden/unhidden). If for some reason you reset the PMAC script without having
74 first removed the positioning handles the script will no longer be able to remove them. You can
75 either delete them manually, or if they're still present when the region next restarts they'll be
76 automatically deleted by their own tiny script(that's all their internal script is used for...
77
78 NOTE: Any PMAC add-ons that also need to store permanent object position changes will likely need you to
79 subsequently update your saved notecard with data they supply. Please consult their instructions
80 for specific details.
81
82
83 **** ABOUT ANIMATION GROUP NOTECARDS ****
84
85 Other than possibly needing to edit a notecard's contents manually to add necessary data for add-ons, it
86 should never be necessary to edit a PMAC animation group notecard manually unless you are making
87 a major change such as adding a new animation to it or deleting an existing animation from it.
88 Details on that are in the Advanced Builders Instructions notecard.
89
90 If you're doing custom set-ups, though, you will likely want to tweak the notecard *names* though so it's
91 important to know how an animation group notecard's name must be formatted. An animation group
92 notecard name is as follows:
93
94 .menu[ss][p][r] [unique button name]
95
96 Where:
97 - it must always begin with .menu (note the period at the start)
98 - then two numbers or letter (or a mix) that are used only for sorting...groups appear in the
99 same order in your menu as they do in inventory
100 - then one number between 1 and 9 (inclusive) that indicates how many positions are used by
101 *all* animations in this group
102 - then either the letter A, G, or O
103 .....A means anyone is allowed to have it in their groups list and load it
104 .....G means only people with the same *active* group as the PMAC object can see and load it
105 .....O means only the owner can see and load it
106 .....the owner can also always see and load group ones, even if the group isn't active
107 - then there must be a single space
108 - then everything after that is the name that will be used for the group and the group's button
109 .....a group name must be unique from any other group name in inventory, even if the .manusspr part is diffent
110 .....a group name can be no more than 25 characters long but it's best to keep it as short as possible to fit on a button
111
112 An example of a correctly formatted name would be: .menu012A Sofa Sits
113 - it will be "early" in the group menu list by virtue of the 01 sort number
114 - the 2 indicates that all animations in it are for 2 positions
115 - the A indicates that anyone can see and load it
116 - the group name you'll see on the button is "Sofa Sits" and no other notecard is allowed to
117 have that exact name, even if it was called .menu123G Sofa Sits
118
119 Names are case-sensitive, though, so you could have one another one called "Sofa sits" and yet
120 another one called "sofa sits" and still another called "SOFA SITS" and they would all be treated as different names from one another (but you might later not be able to remember which one is which so it's a good idea not to).
121
122
123 **** ABOUT NPC NOTECARDS ****
124
125 You can easily add new NPCs to your PMAC object at any time (or delete ones you don't use).
126 Simply use any utility you like to create an appearance notecard, then place it into the
127 PMAC object's inventory and rename it to use the correct NPC naming format which is very
128 similar to the animation group format
129
130 .NPC[ss][r] [Firstname] [Lastname]
131
132 Where:
133 - it must begin with .NPC (note the period at the start)
134 - then two numbers or letters used for sorting
135 - then either the letter A, G or O which indicate the same viewing and loading permission restrictions as they
136 do for animation groups
137 - then a space
138 - then the first name to use for the NPC -- it *must* be a single word with no space
139 - then a space
140 - then a the last name to use for the NPC -- again it must be a single word with no space
141 ....if you don't supply a last name a tilde symbol (~) will be added instead so the NPC would be named Firstname ~
142 - the total of length of Firstname plus Lastname cannot exceed 24 characters (which becomes 25 character because of
143 the space between them)
144 .....and it's a good idea to try to keep the combined length shorter if at all possible since in most cases only the
145 first name and a bit of the last name will fit on a button
146
147 Again, the name of the NPC must be unique from any other NPC even if the first part is different.
148
149 An example of a correctly formatted NPC name would be: .NPC88A Cutie Pie
150 - it will appear fairly late in NPC list due to the 88 for its sort order
151 - the "A" means anyone can see and load it
152 - The NPC's name on the button (and when rezzed) will be "Cutie Pie"
153
154 Your newly added NPC won't appear in the list until the PMAC Core script is reset. Similarly, if you delete a NPC
155 notecard you will need to reset the PMAC core script to have it removed from the list of available NPCs.
156
157
158 **** MORE DRASTIC EDITS OF ANIMATION GROUP NOTECARDS ****
159
160 If you wish to, you can delete an entire animation from a notecard by opening it, finding the appropriate line for
161 the animation, then deleting the entire line. Be very careful when doing this, ensuring that you delete all of it
162 and without leaving a blank line or even a single extra space, either of which can cause a
163 failure to subsequently load the card and extract its data. It's safest to work with a *copy*
164 of your notecard, make the changes, then test to ensure that copy loads correctly. Only then
165 should you consider deleting your backup copy.
166
167 Similarly, you can add a new animation to the card by supplying all of the necessary information. Unless you already
168 know the correct *relative* positions and rotations, it's best to supply generic starting points and then use PMAC's
169 tweaking tool to position and store the optimized final positions.
170
171 A full line contains a pile-separated (|) list of the following (in order and with NO ADDITIONAL SPACES):
172 - animation button name (must be unique from any other animation name used in the same notecard and cannot contain
173 the pipe (|) symbol)
174 - any commands (see the Advanced Building Information notecard for details) or NO_COM for none
175 - for each position you then need to supply
176 .....the exact name of the animation in inventory
177 .....followed by a relative position (use something like <0.0,0.0,0.0> or slightly offset from that if you don't know it)
178 .....followed by a relative rotation (use something like <0.0, 0.0, 0.0, 0.0> if you don't know it)
179
180 IMPORTANT: YOU MUST USE NUMERICAL VALUES FOR THE VECTORS AND QUATERNIONS, YOU CANNOT USE THE LSL CONSTANT NAMES
181
182 JUST AS IMPORTANT: All animations in a single animation group notecard must use the exact same number of positions.
183 You can't supply a mixture.
184
185 Here is an example for an animation line in a 2-position animation group notecard assuming that the names for the
186 animations we place in the inventory are "sofa_cuddle_m" and "sofa_cuddle_f" which we want to call "Sofa Cuddle"
187 and that we're adding no special add-on commands. Because we don't know a suitable relative offset
188 we'll displace each by a small amount on the local y-axis and then later load the group, enter edit
189 mode, and position them correctly relative to one another and the main PMAC object:
190
191 Sofa Cuddle|NO_COM|sofa_cuddle_m|<0.0,0.1,0.0>|<0.0,0.0,0.0,0.0>|sofa_cuddle_f|<0.0,-0.1,0.0>|<0.0,0.0,0.0,0.0>
192
193
194 For more detailed, technical instructions geared for advanced scripters and builders, please see the final notecard
195 in this series.

PMAC

PARAMOUR MULTI-ANIMATION CONTROLLER (PMAC) v1.02 (OSSL)

Category: NPC
By : Aine Caoimhe
Created: 2015-11-24 Edited: 2015-11-23
Worlds: OpenSim

1
2 PARAMOUR MULTI-ANIMATION CONTROLLER (PMAC) v1.0 (OSSL)
3 by Aine Caoimhe January 2015
4
5 ADVANCED BUILDERS INFORMATION
6
7
8 **** OVERVIEW *****
9
10 This final notecard is more in the form of information rather than instructions and is intended as a resource for those with reasonably strong scripting skills. Before reading, you will probably wish to familiarize yourself with the contents of the Basic Tweaking Instructions notecard (particularly the final sections which talk about the notecard naming formats and the animation group card's animation line format) and take at least a cursory look through the PMAC core script.
11
12 This card is primarily for people who wish to either
13
14 (1) build an object using PMAC as its animation handler (requires only modest scripting knowledge), or
15
16 (2) script add-on utilities or other specialized customizations for the PMAC system (requires a higher level of scripting skill).
17
18 It is assumed that all of the terms and discussion points will be familiar to the reader or else fairly easily grasped.
19
20
21 **** BUILDING/CREATING A PMAC OBJECT ****
22
23 Building a new system is fairly simple, although quite time-consuming. The basic steps are:
24
25 1. Create the physical object (prim, sculpt, mesh, or linket combination thereof)
26 2. Add the 5 core READ ME notecards that should be included with all systems
27 3. Add the ~~~positioner object
28 4. Add the base priority 1 animation
29 5. Add any add-on scripts you intend to include in the setup as well as their supporting user documentation
30 6. Add one or more NPC notecards
31 7. Add one or more animation group notecards (see below for how to prepare these)
32 8. And then finally add the PMAC core script
33 9. Sit, load a group card, add some NPCs (or friends) to fill up the available positions
34 10. Enter edit mode, adjust the position of each as required, and save
35 11. Update any add-on command data that changes based on positions (I expect that many won't require it)
36 12. Repeat for each group card until they're all done and you have a finished product
37
38 While the final few steps are time-consuming, the built-in editor should greatly facilitate this process so the only really "hard" step here is #7 -- preparing the initial animation group notecards. I can offer a couple of suggestions to help with this:
39
40 - organize your animations -- if they're all in separate folders that correspond to (roughly) the groups you'd normally want to include in an object, it's a lot easier to build cards later
41 - take the time to rename most of your animations so matching sets have identical main names and then one or two (or more if absolutely necessary) standardized prefix or suffixes (like having matching pairs use the same name and then end them with _1 and _2 or _m and _f or something like that).
42 - doing the above lets you drop them into a box and then write a script to quickly build your blank notecard automatically
43 - once you have a card like that, save a copy of it in the same folder as the animations so you can simply reuse it in future objects...PMAC's design lends itself to this approach because each group has its own notecard. You might have 50 different group folders in your own inventory so simply decide which ones you want to include and drop them in
44 - don't worry about positioning when doing this unless you can easily set their positions relative to one another...positioning is very easy to do "live" in those final steps using PMAC's editor and ability to directly save to card
45 - depending on what add-ons you want to include, you can probably automate the generation of that data -- or at least starting points -- as well
46 - I will often write a script to read the PMAC notecard, parse it, automatically insert or manipulate the data as needed, then resave it
47 - on occasion it's easier to copy-paste the data to csv and manipulate it in something like Excel...just be careful not to introduce formating or spaces that will make the card unloadable after you bring it back in-world
48 - if you're familiar with other programming languages, you may find it just as easy to copy the notecard data to a csv or txt file and then manipulate it that way
49 - you will need to consult your add-on documentation for the internal structure they require for their commands
50
51
52 **** GENERAL NOTES ABOUT PMAC ****
53
54 Before I go into the command and add-on system I want to indicate the primary design goals I had when creating the PMAC system. I wanted PMAC to adhere to the following:
55
56 (1) PMAC should have an extremely low footprint on sim resources, bordering on non-existent when not in use.
57
58 The script has zero active elements until a user sits, and as soon as the last user stands once more it becomes dormant again. During use, it has a single open handler (dialog menu) and nothing else. When auto mode is engaged, it uses a llSensorRepeat() call with parameters that cannot return a successful result, and the no_sensor event acts as the "timer" handler. The timer itself is only used when in edit mode with a 0.2 sec repeat to trigger repositioning of the avatars to "chase" the handles. All avatar movement uses llSetLinkPrimitiveParamsFast() and no functions with built-in sleeps are ever called.
59
60 The only two occasions where a thread-locking function is ever employed by PMAC are two calls to llSleep: once when an avatar first sits (0.2 sec) in order to give enough time for the default LSL sit to engage; and then when doing a card save that "overwrites" a notecard that is already in inventory because it is necessary to give enough time (0.25 sec) for the old card to be deleted from inventory before storing its replacement with the same name.
61
62 In edit mode, there is no communication at all between the handles and the core script. Only upon leaving edit mode is there a single osMessageObject to each which raises a dataserver event to instruct it to llDie().
63
64 Unlike LSL-based systems, HMAC leverages OSSL notecard functions to drastically reduce the memory requirements during use as well. Rather than having to parse large menu systems and hold huge amounts of animation data in memory, PMAC relegates this to on-the-fly card-reading and no-error-check parsing. Only a single group is ever loaded into memory at any one time, and the core script simply maintains a list of possible groups it can load. By keeping the total number of animations per group to a sane number (I'd suggest 32 or less since 6 pages of animations in a group is more than most users would want to scroll through anyway) this memory consumption can be kept extremely low.
65
66 All communication with add-ons is done using llMessageLinked() and all add-ons must be in the same root prim as the core script, keeping that additional traffic to a minimum.
67
68 (2) PMAC core should offer all key features of other such systems, including the ability to handle a very large number of simultaneous users (up to 9), yet be confined to a single script.
69
70 This necessitated some rather painful decisions about what could and couldn't be made part of core. In the end, the two features I simply didn't have room for but would have liked to include (because both have some "fans") are triggering expressions for each avatar on a per-animation basis, and the ability to rez additional objects from inventory. Extended conversations with regular users led me to believe that both could be omitted from core provided a mechanism existed to allow adding back via add-ons. PMAC's integrated command system not only allows this, but is also considerably more powerful in conjunction with a creative scripter (see "Command System" below).
71
72 In the end, PMAC's entire core feature set runs on only ~1100 LOC and for many users no add-ons will be needed at all.
73
74 (3) PMAC for should maximize the speed of use, both in the initialization time and then also during regular use, and be as intuitive as possible for the average user.
75
76 An animation system like this shouldn't be something the user has to think about or wait for. The user wants to enjoy the product, not mess around with the script. users don't want to spend their time and energy focussing on button-clicking, they want to interact with their fellow users. Ideally nothing should ever be more than 3 clicks away and for the most part PMAC achieves this.
77
78 As was mentioned above, PMAC holds very little in memory and does a lot of its work on the fly using OSSL's capability to rapidly read an entire notecard in a single pull. As a result, initialization of the core system typically takes under 2 seconds even for a very extensive system, and a more general-use object is often ready in less than 1 second from reset. The only operations required are reading the animation group and NPC notecard names from memory and storing them to their respective lists.
79
80 Whenever a user selects a group, PMAC loads it from inventory in a single osGetNotecard(0 string pull, then parses it with a single parse command (and no error checking!). Even for a large notecard this takes a fraction of a second after which the dialog is ready to be displayed.
81
82 The only time the user ever has to wait for a dialog is immediately after rezzing a new NPC because the script pauses until the region is able to supply the new NPC's UUID -- typically 0.25-0.5 seconds though this can take a little longer depending on the simulator and the NPC's attachments.
83
84 (4) PMAC should impose no limits that aren't absolutely necessary.
85
86 PMAC's limits are very few:
87 - The number of animations in a single card is almost unlimited (though in practice I would keep it to 32 or fewer)
88 - The number of animation group notecards is almost unlimited (though a user isn't going to bother scrolling through too many so I'd keep this to under 32 too)
89 - The number of different NPC notecards is almost unlimited (though again, a user is unlikely to want to scroll through page after page of them)
90 - The number of simultaneous users is set to 9 for three reasons: (1) almost nobody is going to have any need to set up a system even for this number...in SL even 4-person systems are extremely few and far between; and (2) it keeps the notecard line sizes and in-script memory requirements to reasonable levels; and (3) keeping the number a single digit means it only needs one digit in the notecard name.
91 - The number of add-ons that could be active is not limited (though having too many will begin to defeat the design goal of minimizing the system's sim footprint.
92 - The core script must be in the root prim -- this is to reduce the total number of calculations needed when doing positioning -- particularly in edit mode. The math to do it from a child prim is simple, but adds to the memory footprint and increases the necessary calculations by about 20% for no good reason. Almost all systems would have placed the script and animations in the root anyway.
93
94 Beyond these design goals there are a couple other things worth mentioning:
95
96 The Options menu currently (v1.0) contains two unused buttons -- one on the top owner-only line and the other on the line below it -- which I've left for possible future development. If you have a specific use for them you can script them in a modified version of core but those additions will need to be small to fit within the total size limit of a script. Just be aware that in a future version I might use one (or bothy) of those buttons for something else...unless your addition is one that you can convince me ought to find a permanent home there and become part of core. I'm certainly open to suggestions as long as they're "general use" rather than highly case-specific ones.
97
98 My hope for PMAC was to create a new system that would be highly appealing to users by achieving all of the above design goals, and would interest serious builders due to its flexibility. I released it as a no-charge CC licensed product to help spur its adoption as well as to be another of my contributions to the community at large. I urge add-on writers and builders to give serious consideration to follow suit.
99
100
101 **** COMMANDS FOR BUILDERS ****
102
103 This section is for people using add-ons made by other people (but people interested in scripting their own add-ons need to be familiar with this as well).
104
105 As I indicated in the basic tweaking notecard, each animation is defined on a line in a group notecard as follows:
106 name|COMMANDS|POS1_name|POS1_rel_pos|POS1_rel_rot|...
107 The "COMMMANDS" part of this is the heart of the command and add-on system and is essentially a concatenated list of commands particular to any add-ons you're using assembled into what I call a "command block".
108
109 If you don't want to use any add-ons for an animation, use "NO_COM" for the command block (there must be *something* suppled). Otherwise, assemble the command block based on the requirements of each add-on you want the animation to incorporate. Command block syntax has a required structure:
110 COMMANDS=COMMAND_NAME{args}COMMAND_NAME{args}....
111 Each of your add-ons will have documentation to indicate the correct COMMAND_NAME to use and provide instructions as to what arguments it takes. It doesn't matter what order you place them in...each add-on will look for the ones it understands and ignore ones it doesn't.
112
113 DO NOT insert any extra spaces before or after command names or curly braces! That can break things.
114
115
116 **** COMMANDS FOR ADD-ON WRITERS ****
117
118 Any time a new animation is called PMAC sends the commands associated with it using:
119 llMessageLinked(LINK_THIS,0,"GLOBAL_NEXT_AN|"+llList2String(currentAn,2),llDumpList2String(positions,"|")); (llList2String(currentAn,2) is the entire command block)
120
121 Your add-on will use the link_message event as the trigger and then parse the string part of the message to find out which commands (if any) are being called. Keep in mind that you may still need to do something when a new animation is called even if it doesn't contain your add-on's specific command -- you might need to stop something your add-on is doing for the previous animation.
122
123 With only a two exceptions the key part of every link_message is the pipe-separated list of the UUID of each position's occupant, in order. If a position is empty it will contain NULL_KEY (the 32-digit key constant value, not the text string). Otherwise it will contain the UUID of the avatar (or NPC) seated there. The exceptions are when the PMAC Core script is reset or when the last user stands and it is no longer in use at which time it sends a single NULL_KEY value (the constant, not the string) for this field.
124
125 Rules for command names and arguments:
126 - the pipe symbol "|" is reserved as the field separator for animation data and all of PMAC's link message communications so it *cannot* be part of a command name or argument syntax
127 - the two curly braces symbols "{" and "}" are the command block separators so they are also reserved
128 - a COMMAND_NAME cannot be one of the reserved global commands (see below)
129 That's it...no other rules. You can structure the syntax and naming of your commands and arguments any way you want; however be at least a little careful when laying out your command name syntax since other add-ons will hear it as well. It needs to be unique enough for your add-on to know that it needs to do something without also tricking a different add-on into acting on it as well (unless you write multiple add-ons that should both respond to the same message).
130
131 I would strongly suggest not beginning your command name with GLOBAL or MAIN. To keep the likelihood of incompatibility with other add-ons to a minimum I would suggest that your add-on prefix each command name with a unique identifier (like MY_ADDON_NAME_xxxxx). Any that I write will begin with PAO_ (for Paramour Add-On).
132
133
134 ***** LINK MESSAGE STRUCTURE AND LIST OF GLOBAL COMMANDS *****
135
136 When developing an add-on, please keep the above-stated design goals in mind. Ideally your add-on shouldn't drastically impact the performance of the sim, the core script, or impede the user's enjoyment of *using* the system. PMAC does all dialog communication with the user via a listener on channel=0x80000000|(integer)("0x"+(string)llGetKey()) so DO NOT EVER have your add-on send anything on that channel (though you can eaves-drop on it if you need to).
137
138 PMAC does all of its communication with add-ons using llMessageLinked(LINK_THIS,0,str,id) so all add-ons must be in the same prim as the PMAC Core script (and PMAC Core *must* be in the root prim of a linkset).
139
140 PMAC Core ignores any message where the (integer) num!=-1 (allowing it to rapidly ignore any messages not specifically intended for it)
141
142 For the most part this is a one-way communication -- PMAC Core only listens to three inbound link_message inputs -- and the only fields that are used to convey information are the (string) str and (key) id fields. In your add-on the triggers will (presumably) always be link_message event:
143 link_message(integer sender_num ,integer num, string str, key id)
144 Where:
145 - because PMAC must be in root, sender_num will usually be 1 (although it's possible it could have the value 0)
146 - PMAC always sends num = 0
147 - PMAC only pays attention to messages where num = -1
148 - str is the primary part of the message that you'll need to examine and parse
149 - as stated above, id will almost always be a pipe-separated list of the UUID of each position occupant.
150 - most add-ons will want to parse the str field into a list using parsed=llParseStringToList(str,["|"],[])
151 - if you do this, parsed(0) will be the initial string that indicates what type of message this is and any addition data will be in subsequent fields
152 - similarly, parsing the id field will give you a list of the UUIDs of the currently seated avatars by using positionOccupants=llParseStringToList(id,["|"],[])
153
154 PMAC sends other "global" notifier messages at various times that all add-ons are likely to an interest in listening to. It's very important to keep in mind Opensim has asynchronous handling so your add-on should anticipate the likelihood of receiving messages in the incorrect order.
155
156 If your add-on needs to communicate with other scripts, be sure to make the messages unique enough that other add-on won't accidentally respond to them. I suggest using a num value greater than 0 for these as well so scripts can ignore anything where num!=0.
157
158 A full list of global commands that PMAC sends using link messages is as follows:
159
160 >>> GLOBAL COMMANDS
161
162 At the very end of state_entry when all other initialization steps are complete:
163 llMessageLinked(LINK_THIS,0,"GLOBAL_SYSTEM_RESET",NULL_KEY);
164 >> this is one of only two times PMAC sends NULL_KEY in the final message field
165 >> keep in mind that a single NULL_KEY for that field can also be sent when the current animation group is for a single user and there is nobody seated
166
167 When there are no more users and PMAC switches back to its dormant (READY) state:
168 llMessageLinked(LINK_THIS,0,"GLOBAL_SYSTEM_GOING_DORMANT",NULL_KEY);
169 >> this is the only other time NULL_KEY is the final field
170
171 When the system has been dormant (no users) and the first user sits down:
172 llMessageLinked(LINK_THIS,0,"GLOBAL_START_USING",llDumpList2String(positions,"|"));
173 >> this will be followed immediately with a new animation being played
174 >> due to asynchronous handling in Opensim, the new animation message *could* be received by an add-on before this message, although this should be fairly rare
175
176 When a new animation is called:
177 llMessageLinked(LINK_THIS,0,"GLOBAL_NEXT_AN|"+llList2String(currentAn,2),llDumpList2String(positions,"|"));
178 >> where llList2String(currentAn,2) is the entire command block for that animation
179 >> to quickly split this into a list of its individual commands use list commands=llParseString2List(commandBlock,["{","}"],[]);
180 >> keep in mind that a newly called animation might not include a command for your addon but you might still need to act on it (stop whatever your add-on was doing for the previous animation)
181 >> in some cases a new animation could be re-calling the animation that was already playing (most commonly this happens in edit mode)
182
183 When the system is already active and a new user sits down:
184 >> no message is sent because this will immediately also trigger playAnimation() of the animation that was already playing
185 >> if an add-on needs to know who sat it will need to maintain its own ongoing list of users and compare that to the key field data
186
187 When someone stands
188 llMessageLinked(LINK_THIS,0,"GLOBAL_USER_STOOD|"+(string)i+"|"+(string)who,llDumpList2String(positions,"|"));
189 >> "i" is the position number they previously occupied
190 >> "who" is the UUID of the user who stood
191 >> this would also be sent when a user disconnects or teleports (in or out of region)
192 >> it is also be sent when a NPC is removed
193 >> this message is sent because an add-on may need to know the UUID of whoever stood
194
195 When the user presses the SYNCH button on any menu:
196 llMessageLinked(LINK_THIS,0,"GLOBAL_ANIMATION_SYNCH_CALLED",llDumpList2String(positions,"|"));
197 >> the commands are not resent so an add-on may need to store the most recently-sent command
198
199 When a new user takes control of the PMAC dialog menu system:
200 llMessageLinked(LINK_THIS,0,"GLOBAL_NEW_USER_ASSUMED_CONTROL|"+(string)user,llDumpList2String(positions,"|"));
201 >> where "user" is the UUID of the new person in control
202 >> keep in mind that PMAC still works perfectly even if nobody has yet touched it to take control
203
204 When the owner first enters edit mode (by pressing EDIT ON in the options menu):
205 llMessageLinked(LINK_THIS,0,"GLOBAL_NOTICE_ENTERING_EDIT_MODE",llDumpList2String(positions,"|"));
206
207 When in edit mode any time new position data is persisted to script memory:
208 llMessageLinked(LINK_THIS,0,"GLOBAL_EDIT_PERSIST_CHANGES",llDumpList2String(positions,"|"));
209 >> most of the time this will be followed immediately with a new call to playAnimation()
210 >> it could also be immediately followed by a new call to store a notecard
211
212 When in edit mode and the "STORE ADDON" button is pressed:
213 llMessageLinked(LINK_THIS,0,"GLOBAL_STORE_ADDON_NOTICE",llDumpList2String(positions,"|"));
214 >> that is the only action taken when that button is pressed...there is no synch or persist in PMAC Core itself
215
216 When in edit mode and a notecard is being stored (either overwriting an existing notecard or creating a new one) this is sent immediately *after* osMakeNotecard() is executed (so the newly stored data will be in the notecard and an add-on could potentially now make additional changes to it):
217 llMessageLinked(LINK_THIS,0,"GLOBAL_EDIT_STORE_TO_CARD|"+cardName,llDumpList2String(positions,"|"));
218 >> where "cardName" is the full string name of the notecard (including the .menuxxxx part)
219 >> it should always have been preceded by a persist changes message but it's possible asynch handling could cause this to arrive first
220
221 Any time PMAC leaves edit mode:
222 llMessageLinked(LINK_THIS,0,"GLOBAL_NOTICE_LEAVING_EDIT_MODE",llDumpList2String(positions,"|"));
223 >> this could be a result of a user standing/disconnecting during edit, or could be the owner intentionally leaving edit mode
224 >>> END OF GLOBAL COMMANDS
225
226 ***** THE SPECIALS MENU *****
227
228 An add-on that needs to add a menu button to the user's dialog options may do so by registering the button and associated command string with the PMAC Core script. The button will then be added to the list of buttons to display in the SPECIALS menu. If there are no registered buttons the SPECIALS button will not be available in the options menu. When the user clicks a button, the command string associated with that button is sent and dialog control is passed to the addonto allow it to do whatever it needs to do (including additional dialog with the user). The add-on MUST then tell PMAC Core when to resume its own dialog (even if the add-on does something instantly without dialog).
229
230 Any time control changes to a new user the current SPECIALS button list is erased (in case a special menu button should only be displayed to specific users) so your add-on will need to re-register the button any time it receives the global message GLOBAL_NEW_USER_ASSUMED_CONTROL. The message string is followed by the pipe symbol and then the new controller's UUID.
231
232 To register a menu button your add-on script needs to send this message to the PMAC Core script:
233 llMessageLinked(LINK_THIS,-1,"MAIN_REGISTER_MENU_BUTTON|"+buttonName,commandString);
234
235 When the user selects the menu button in the specials menu the command string sent by PMAC Core is the command string along with the user key and PMAC always then transfers dialog control to the add-on (even if it doesn't use dialogs!)
236 llMessageLinked(LINK_THIS,0,commandString+"|"+user,llDumpList2String(positions,"|"));
237
238 To return to the main controller's dialog menu (which will redisplay the options menu) the add-on must send the command:
239 llMessageLinked(LINK_THIS,-1,"MAIN_RESUME_MAIN_DIALOG",NULL_KEY);
240
241 To unregister a button use:
242 llMessageLinked(LINK_THIS,-1,"MAIN_UNREGISTER_MENU_BUTTON|"+buttonName,commandString);
243
244 When registering a button please be aware that:
245 - when PMAC receives the registration command it checks to see if buttonName is already in its list of buttons for the specials menu
246 - it makes no check on the commandString
247 - if buttonName doesn't exist in the list, it (and its associated command) is added and the list is resorted in alphabetical order
248 - if buttonName already exists in the specials menu list it will replace it (allowing you to update the command string to send on pressing the button)
249 - the check is a simple search so make sure your buttonName is unique enough that it won't match another add-on's button
250 - buttonName can be up to 25 characters but should be kept short if possible (the full buttonName is displayed in the dialopg box's text)
251 - commandString has no character or length restrictions
252 - make sure your commandString is unique enough that only your own add-on will respond to it
253 - keep in mind that on button press a pipe symbol and the user's UUID is appended to the commandString
254
255 When unregistering a button please be aware that:
256 - both buttonName and commandString must exactly match the existing values
257 - if match isn't found it fails silently
258
259 CANNOT POSSIBLY STRESS STRONGLY ENOUGH:
260 PMAC ignores all message that have a num value other than -1
261 DO NOT FORGET that any time a button is pressed PMAC Core will suspend its own dialog and MUST be told when to resume by sending MAIN_RESUME_MAIN_DIALOG. If you forget to do so (or send the message with a num field value other than -1) the main dialog will never reappear and the user will probably think the system is broken.
262
263 OTHER CONSIDERATIONS:
264 Add-on scripts using buttons will need to account for the possibility of a variety of possibilities including:
265 - PMAC may be in auto mode so new animations could be called while dialog control is still with the add-on (you'll know this has happened via its link messages)
266 - the current controller might touch the object which will also bring back the core script's dialog - for many viewers this means your dialog box will be removed
267 - another user could touch the object and take control away from the current user at any time (you'll know this has happened via the global notifier)
268 - the current controller could stand and should then have all dialogs revoked (you'll know this has happened via the global notifier)
269 - the current controller could tp out of the region or disconnect
270 - a new user might sit down which will trigger an animation call but no other message (so compare user UUID lists if this is imporant)
271 - another user might stand, tp out of the region, or disconnect (you'll receive a global notifier of this)
272 - in Opensim a lot of things can unexpectedly happen asynchronously do don't bank on a strict order of events
273 - other possibilities I haven't thought of...but you should :p
274
275
276 **** A FINAL WORD ABOUT COMMANDS ****
277
278 In addition to needing to account for the possibility all of the above "OTHER CONSIDERATIONS" it's worth remembering that there are a variety of cases where the playAnimation() UDF in PMAC Core will replay the same animation that is already playing. This will frequently happen in edit mode, or when additional users sit, so your add-on will need to keep track of users to avoid de/re-rezzing objects or executing lengthy code when unnecessary.
279
280
281 **** AND A COUPLE FINAL REQUESTS ***
282
283 While not mandatory, I would ask that add-on creators give their scripts a name that begins with "..addon " (two dots, then addon then a space) followed by your addon name. That way all addons should generally appear near the top of the prim's inventory list, immediately below the PMAC core script. This will make it far easier for builders and users to tell at a glance which add-ons are included
284
285 Please include documentation with your add-ons! I know it sucks writing documentation (heck, I've just done 5 notecards worth of it!!!) but there really *are* people out there who read them. (I would also prefer not to be flooded with IMs from builders who want me to figure out how someone else's addon works!) If it requires minimal explanation you could have it as comments in the script, but in general a notecard would be preferred with the name "!!READ ME: " then the addon name (for the same reason as above).
286
287 Please keep the "chattiness" of your add-ons to a minimum or include a user-setting to control how much feedback is given. Most users want to enjoy using a product, not be flooded with text. Unless it's absolutely necessary to use general chat, please use llRegionSayTo() to communicate with users (it's the absolutely lowest resource user). DO NOT EVER USE llInstantMessage() unless it's absolutely necessary (it's a resource-hogging thread locking function).
288
289 If you write what you think is a really great add-on and would like it include as part of a standard PMAC core package please send it to me along with a *working* setup to test. I'd be only too happy to add it if it seems bug free and adheres to the overall design goals for the system.

PMAC

PARAMOUR MULTI-ANIMATION CONTROLLER (PMAC) v1.02 (OSSL)

Category: NPC
By : Aine Caoimhe
Created: 2015-11-24 Edited: 2015-11-23
Worlds: OpenSim

1 Slowdance|NO_COM|Slowdance (Female)|<0.0274,-0.0735,1.3718>|<0,0.0175,-0.9998,0>|Slowdance (Male)|<0,-0.0483,1.382>|<0.0175,0,0,0.9998>
2
3 BeautifulDream|NO_COM|BeautifulDream (Female)|<0,-0.0493,1.4136>|<0,0,0,1.>|BeautifulDream (Male)|<0,-0.0477,1.367>|<0,0,0,1.>
4 Bite Me|NO_COM|Bite-Me (Female)|<0,-0.045,1.2894>|<0,0,0,-1.>|Bite-Me (Male)|<0,-0.045,1.2894>|<0,0,0,-1.>
5 Eternal Love|NO_COM|Eternal-Love (Female)|<0.0786,-0.6648,1.3915>|<0,0,0,-1.>|Eternal-Love (Male)|<0,-0.6168,1.2796>|<0,0,0,-1.>

PMAC

PARAMOUR MULTI-ANIMATION CONTROLLER (PMAC) v1.02 (OSSL)

Category: NPC
By : Aine Caoimhe
Created: 2015-11-24 Edited: 2015-11-23
Worlds: OpenSim

1
2
3 // PARAMOUR MULTI-ANIMATION CONTROLLER (PMAC) v1.02
4 // by Aine Caoimhe (Mata Hari)(c. LACM) March 2015-May 2015
5 // Provided under Creative Commons Attribution-Non-Commercial-ShareAlike 4.0 International license.
6 // Please be sure you read and adhere to the terms of this license: https://creativecommons.org/licenses/by-nc-sa/4.0/
7 //
8 // UserConfig added by Seth Nygard March 2015
9 // modified to store positions for NC_PROPS addon Neo Cortex April 2015
10 // additional tweaks by Aine Caoimhe May 2015
11 // *** THIS SCRIPT REQUIRES (AND WILL ONLY WORK) IN REGIONS WHERE THE SCRIPT OWNER HAS OSSL FUNCTION PERMISSIONS ***
12 // *** THIS SCRIPT MUST ALWAYS BE LOCATED IN THE ROOT PRIM OF A LINKSET ***
13 //
14 // *********************************************************
15 // ***** GENERAL USER SETTINGS - ADJUST AS PREFERED *****
16 // *********************************************************
17 string defaultGroup="Dance"; // name of a group (not the full card name!) to load by default (regardless of its permission setting)
18 //
19 integer resetOnQuit=FALSE; // TRUE = when no more sitters, reset the script (will also load default group again)
20 // FALSE = leave most recently loaded animation active
21 integer ownerUseReq=FALSE; // TRUE = the owner must be the first to sit and other people can only sit while the owner is still present and seated
22 // FALSE = no restriction...anyone can sit and use it at any time
23 integer ownerOnlyMenus=FALSE; // TRUE = only the owner can access the dialog menus (can be turned off in options menu until script is reset or it is turned on again) - NOT RECOMMENDED unless ownerUseReq=TRUE
24 // FALSE = anyone can be the controller
25 integer ownerUseUnlocksPerms=FALSE; // TRUE = if owner is a current user, all users then have access to all groups and NPCs
26 // FALSE = only the owner can ever load an owner-only Group or NPC
27 integer autoOn=TRUE; // TRUE = will start in auto mode after a reset
28 // FALSE = will start in manual mode after a reset -- after use will remain in whatever state it was left in unless resetOnQuit=TRUE
29 float autoTimer=120.0; // default time to use for the autotimer (in seconds) - after use will remain at whatever timer was last set to unless resetOnQuit=TRUE;
30 string gs_ConfigName=".PMAC-CONFIG"; // Name of optional notecard with user defined configuration that overide the above values if present
31 integer showGroupsMenuFirst=FALSE; // TRUE = when first initiating dialog, show the groups menu instead of the current group's animation menu; FALSE = show current group's animation menu
32 integer allowSoloNPC=TRUE; // TRUE = NPCs can be left rezzed even if there are no avatars seated; FALSE = kill all NPCs if there are no remaining avatars
33 //
34 // ***************************************
35 // ***** ADVANCED/BUILDER SETTINGS *****
36 // ***************************************
37 string handleName="~~~positioner"; // inventory object to rez as a handle for positioning
38 list handleColours=[<1.000, 0.004, 0.667>,<0.004, 0.667, 1.000>,<0.667, 1.000, 0.004>, // colours to use for handles (must supply 9)
39 <1.000, 0.004, 0.004>,<0.004, 0.004, 1.000>,<1.000, 0.667, 0.004>,
40 <0.667, 0.004, 1.000>,<0.004, 1.000, 0.667>,<0.004, 1.000, 0.004>];
41 vector handleSize=<0.2,0.2,3.0>; // size of handles
42 float handleAlpha=0.5; // alpha of handles
43 string baseAn="~~~~~base_DO_NOT_DELETE_ME!!!!!"; // name of the P1 animation to use for synch
44 //
45 // ************************************************
46 // ***** DO NOT CHANGE ANYTHING BELOW HERE *****
47 // ***** UNLESS YOU KNOW WHAT YOU'RE DOING! *****
48 // ************************************************
49 key user;
50 list positions;
51 list invNpc; // fullName | A/G/O | buttonName
52 integer invNpcStride=3;
53 list npcList;
54 integer npcPage;
55 list invGroups; // fullName | positions | A/G/O | buttonName
56 integer invGroupStride=4;
57 list groupList;
58 integer groupPage;
59 integer myChannel;
60 integer diaHandle;
61 string menu;
62 string txtDia;
63 list butDia;
64 string currentGroup;
65 list anData; // anName | command | A1Name | A1 Pos | A1 Rot | ...
66 integer anStride;
67 list anList;
68 list currentAn; // groupName | anName | command | A1Name | A1 Pos | A1 Rot | ...
69 integer anPage;
70 list editHandles;
71 integer rezzingHandles;
72 float editTimer=0.2;
73 string myState="INITIALIZING";
74 list specials; //buttonName | stringToSend
75 integer specPage;
76 integer gi_HaveConfig=0;
77 // NC_PROP ADDON
78 integer gi_NC_PROP_CHANGED=FALSE; // global integer flag to indicate if new prop values have been sent form NC_PROPS addon
79 string gs_NC_PROP_DATA=""; // global string containing changed props data
80
81 showAnMenu()
82 {
83 integer maxAn=llGetListLength(anList);
84 integer showStart=(anPage+1);
85 integer showEnd=(anPage+6);
86 if(showEnd>maxAn) showEnd=maxAn;
87 txtDia=""+"ANIMATION MENU: Select an animation\n"+currentlyPlaying()+"\nShowing animation";
88 if(showEnd!=showStart) txtDia+="s "+(string)showStart+" to "+(string)showEnd;
89 else txtDia+=" "+(string)showStart;
90 txtDia+=" of "+(string)maxAn+ " total animations in this group\n\n"+llDumpList2String(llList2List(anList,anPage,anPage+5),"\n");
91 butDia=[]+llList2List(anList,anPage,anPage+5);
92 while(llGetListLength(butDia)<6) { butDia=[]+butDia+["-"]; }
93 butDia=[]+butDia+["< PREV","SYNCH","NEXT >","GROUPS","OPTIONS","QUIT"];
94 menu="MENU_ANIM";
95 startListening();
96 }
97 showGroupsMenu()
98 {
99 txtDia=""+"GROUPS MENU: Select a group of animations\n"+currentlyPlaying()+"\nShowing groups "+(string)(groupPage+1)+" to "+(string)(groupPage+6)+" of "+(string)llGetListLength(groupList)+ " total groups\n\n"+llDumpList2String(llList2List(groupList,groupPage,groupPage+5),"\n");
100 butDia=[]+llList2List(groupList,groupPage,groupPage+5);
101 while(llGetListLength(butDia)<6) { butDia=[]+butDia+["-"]; }
102 butDia=[]+butDia+["< PREV","SYNCH","NEXT >","<< BACK","OPTIONS","QUIT"];
103 menu="MENU_GROUPS";
104 startListening();
105 }
106 showEditMenu()
107 {
108 txtDia=""+"EDIT MODE ACTIVE!!!\n"+currentlyPlaying()+" (#"+(string)(llListFindList(anList,[llList2String(currentAn,1)])+1)+" of "+(string)llGetListLength(anList)+")\n\nPlease ensure you have read and are familiar with the PMAC instructions for using the edit menu, particularly if you use add-on modules. You have been warned...";
109 butDia=[]+["< PREV","SYNCH","NEXT >","REVERT THIS","STORE THIS","STORE ADDON","EDIT OFF","SAVE CARD","SAVE NEW"];
110 menu="MENU_EDIT";
111 startListening();
112 }
113 showOptionsMenu()
114 {
115 txtDia=""+"OPTIONS MENU:\n\n";
116 butDia=[];
117 if(user==llGetOwner())
118 {
119 if(ownerOnlyMenus)
120 {
121 txtDia+="EDIT ON enters edit mode (all positions must be filled)\nMENUS UNLOCK allows other users to take control\n";
122 butDia=[]+butDia+["EDIT ON","MENUS UNLOCK","-"];
123 }
124 else
125 {
126 txtDia+="EDIT ON enters edit mode (all positions must be filled)\nMENUS LOCK prevents other users from taking control\n";
127 butDia=[]+butDia+["EDIT ON","MENUS LOCK","-"];
128 }
129 }
130 txtDia+="AUTO is used to enable, disable or adjust auto mode\n";
131 butDia=[]+butDia+["AUTO","-"];
132 if(llGetListLength(specials)>0)
133 {
134 txtDia+="SPECIAL access special add-on menus\n";
135 butDia=[]+butDia+["SPECIAL"];
136 }
137 else butDia=[]+butDia+["-"];
138 txtDia+="SWAP to swap positions\nUNSIT to force someone to stand up or remove a npc\n";
139 butDia=[]+butDia+["SWAP","UNSIT"];
140 if(llListFindList(positions,[NULL_KEY])>-1)
141 {
142 txtDia+="ADD NPC to have a npc join you\n";
143 butDia=[]+butDia+["ADD NPC"];
144 }
145 else butDia=[]+butDia+["-"];
146 butDia=[]+butDia+["<< BACK","SYNCH","QUIT"];
147 menu="MENU_OPTIONS";
148 startListening();
149 }
150 showSpecialsMenu()
151 {
152 txtDia=""+"SPECIALS MENU\n\nThese are options supplied by any add-ons you have installed. Please consult their instructions for details.\n";
153 butDia=[];
154 integer i=specPage;
155 while(llGetListLength(butDia)<9)
156 {
157 if(i<llGetListLength(specials))
158 {
159 butDia=[]+butDia+llList2String(specials,i);
160 txtDia+="\n"+llList2String(specials,i);
161 i+=2;
162 }
163 else butDia=[]+butDia+["-"];
164 }
165 butDia=[]+butDia+["< PREV","CANCEL","NEXT >"];
166 menu="MENU_SPECIALS";
167 startListening();
168 }
169 showAutoMenu()
170 {
171 txtDia="AUTO MENU\n\nAuto mode is currently ";
172 butDia=[]+["120","300","600","30","60","90"];
173 if(autoOn)
174 {
175 txtDia+="ON and set to "+(string)llRound(autoTimer)+" seconds\n\nSelect a different time if you wish, or AUTO OFF to switch to manual mode";
176 butDia=[]+butDia+["AUTO OFF","-","CANCEL"];
177 }
178 else
179 {
180 txtDia+="OFF\n\nSelect AUTO ON to use the last preset time or pick your preferred timer";
181 butDia=[]+butDia+["AUTO ON","-","CANCEL"];
182 }
183 menu="MENU_SELECT_AUTO_MODE";
184 startListening();
185 }
186 showAddNpcMenu()
187 {
188 txtDia=""+"ADD NPC\n\nSelect the NPC to add. It will occupy the first available position\n\n"+llDumpList2String(llList2List(npcList,npcPage,npcPage+8),"\n");
189 butDia=[]+llList2List(npcList,npcPage,npcPage+8);
190 while(llGetListLength(butDia)<9) { butDia=[]+butDia+["-"]; }
191 butDia=[]+butDia+["< PREV","CANCEL","NEXT >"];
192 menu="MENU_ADD_NPC";
193 startListening();
194 }
195 playAnimation(string name)
196 {
197 if(autoOn) llSensorRemove();
198 integer i=llGetListLength(positions);
199 while(--i>=0) { if(llList2Key(positions,i)!=NULL_KEY) osAvatarStopAnimation(llList2Key(positions,i),llList2String(currentAn,3+i*3)); }
200 integer indexAn=llListFindList(anData,[name]);
201 list nextAn=[currentGroup]+llList2List(anData,indexAn,indexAn+anStride-1);
202 integer have=llGetListLength(positions);
203 integer need=llRound(((float)llGetListLength(nextAn)-3.0)/3.0);
204 while(have<need) { positions=[]+positions+[NULL_KEY];have++; }
205 while((have>need) && (llListFindList(positions,[NULL_KEY])>=0)) { positions=[]+llDeleteSubList(positions,llListFindList(positions,[NULL_KEY]),llListFindList(positions,[NULL_KEY]));have--; }
206 if(have>need)
207 {
208 llSay(0,"Encountered an error in attempting to start a new animation: there are currently too many users seated. You will need to reduce the number of seated users by "+(string)(have-need)+" to play it, or select a different group of animations");
209 return;
210 }
211 i=llGetListLength(positions);
212 while(--i>=0)
213 {
214 key who=llList2Key(positions,i);
215 if(who!=NULL_KEY)
216 {
217 osAvatarPlayAnimation(who,llList2String(nextAn,3+i*3));
218 setPosition(who,getUserLink(who),llList2Vector(nextAn,4+i*3),llList2Rot(nextAn,5+i*3));
219 if(myState=="EDIT") setHandle(llList2Key(editHandles,i),llList2Vector(nextAn,4+i*3),llList2Rot(nextAn,5+i*3));
220 }
221 }
222 currentAn=[]+nextAn;
223 llMessageLinked(LINK_THIS,0,"GLOBAL_NEXT_AN|"+llList2String(currentAn,2),llDumpList2String(positions,"|"));
224 if(autoOn) llSensorRepeat("THIS_WILL_NEVER_RETURN_A_SENSOR_RESULT",NULL_KEY,AGENT,0.001,0.0,autoTimer);
225 }
226 doSynch()
227 {
228 integer i=llGetListLength(positions);
229 while(--i>=0)
230 {
231 key who=llList2Key(positions,i);
232 if(who!=NULL_KEY)
233 {
234 osAvatarStopAnimation(who,llList2String(currentAn,3+i*3));
235 osAvatarPlayAnimation(who,llList2String(currentAn,3+i*3));
236 }
237 }
238 llMessageLinked(LINK_THIS,0,"GLOBAL_ANIMATION_SYNCH_CALLED",llDumpList2String(positions,"|"));
239 }
240 doSwapPositions(integer indFrom,integer indTo)
241 {
242 key whoFrom=llList2Key(positions,indFrom);
243 if(whoFrom!=NULL_KEY)
244 {
245 osAvatarStopAnimation(whoFrom,llList2String(currentAn,3+indFrom*3));
246 setPosition(whoFrom,getUserLink(whoFrom),llList2Vector(currentAn,4+indTo*3),llList2Rot(currentAn,5+indTo*3));
247 }
248 key whoTo=llList2Key(positions,indTo);
249 if(whoTo!=NULL_KEY)
250 {
251 osAvatarStopAnimation(whoTo,llList2String(currentAn,3+indTo*3));
252 setPosition(whoTo,getUserLink(whoTo),llList2Vector(currentAn,4+indFrom*3),llList2Rot(currentAn,5+indFrom*3));
253 }
254 positions=[]+llListReplaceList(positions,[whoFrom],indTo,indTo);
255 positions=[]+llListReplaceList(positions,[whoTo],indFrom,indFrom);
256 doSynch();
257 }
258 doChangeUser(key who)
259 {
260 if(user!=NULL_KEY) llRegionSayTo(user,0,llGetUsername(who)+" has now taken control of me");
261 user=who;
262 buildGroupList();
263 specials=[];
264 llMessageLinked(LINK_THIS,0,"GLOBAL_NEW_USER_ASSUMED_CONTROL|"+(string)user,llDumpList2String(positions,"|"));
265 if(llListFindList(groupList,[currentGroup])==-1) loadGroup(llList2String(groupList,0));
266 else if(autoOn) showGroupsMenu();
267 else if(showGroupsMenuFirst) showGroupsMenu();
268 else showAnMenu();
269 }
270 doQuit()
271 {
272 if(myState=="EDIT") removeHandles();
273 integer i=llGetListLength(positions);
274 while(--i>=0)
275 {
276 key who=llList2Key(positions,i);
277 if(who!=NULL_KEY)
278 {
279 if(osIsNpc(who)) osNpcRemove(who);
280 else
281 {
282 llRegionSayTo(who,0,"Quit called");
283 llUnSit(who);
284 }
285 }
286 }
287 }
288 loadGroup(string name)
289 {
290 if(name==currentGroup)
291 {
292 if(user!=NULL_KEY) showAnMenu();
293 return;
294 }
295 integer indToLoad=llListFindList(invGroups,[name])-3;
296 integer newUserCount=llList2Integer(invGroups,indToLoad+1);
297 if(newUserCount<llGetListLength(positions))
298 {
299 integer agents;
300 integer a=llGetListLength(positions);
301 while(--a>-1) { if(llList2Key(positions,a)!=NULL_KEY) agents++; }
302 if(agents>newUserCount)
303 {
304 llRegionSayTo(user,0,"You cannot use animations from the "+name+" group at the moment because it is for a maximum of "+(string)newUserCount+" users and there are currently "+(string)agents+" seated. Please select a different group of animations, remove NPCs, or ask someone to stand.");
305 if(user!=NULL_KEY) startListening();
306 return;
307 }
308 }
309 currentGroup=name;
310 anData=[]+llParseString2List(osGetNotecard(llList2String(invGroups,indToLoad)),["|","\n"],[""]);
311 anStride=2+(3*newUserCount);
312 anPage=0;
313 anList=llList2ListStrided(anData,0,-1,anStride);
314 if(autoOn && (myState=="RUNNING"))
315 {
316 playAnimation(llList2String(anList,0));
317 if(user!=NULL_KEY)
318 {
319 llRegionSayTo(user,0,"Now automatically playing animations from "+name);
320 showGroupsMenu();
321 }
322 }
323 else if(user!=NULL_KEY) showAnMenu();
324 }
325 loadConfig()
326 {
327 integer i;
328 float n;
329 string sParam;
330 string sValue;
331 list lParams=[];
332 for(i=0;i<osGetNumberOfNotecardLines(gs_ConfigName); i++) {
333 lParams=llParseString2List(osGetNotecardLine(gs_ConfigName,i),["="],"");
334 if(llGetListLength(lParams)==2){
335 sParam=llList2String(lParams,0);
336 sValue=llList2String(lParams,1);
337 if((sParam=="defaultGroup") || (sParam=="DefaultGroup")) {
338 defaultGroup=sValue;
339 }else if((sParam=="resetOnQuit")||(sParam=="ResetOnQuit")) {
340 resetOnQuit=(sValue=="TRUE");
341 }else if((sParam=="ownerUseReq")||(sParam=="OwnerUseReq")) {
342 ownerUseReq=(sValue=="TRUE");
343 }else if((sParam=="ownerOnlyMenus")||(sParam=="OwnerOnlyMenus")) {
344 ownerOnlyMenus=(sValue=="TRUE");
345 }else if((sParam=="ownerUseUnlocksPerms")||(sParam=="OwnerUseUnlockPerms")) {
346 ownerUseUnlocksPerms=(sValue=="TRUE");
347 }else if((sParam=="autoTimer")||(sParam=="AutoTimerValue")) {
348 autoTimer=(float)sValue;
349 }else if((sParam=="baseAn")||(sParam=="BaseAnimation")) {
350 baseAn=sValue;
351 }else if((sParam=="showGroupsMenuFirst") || (sParam=="ShowGroupsMenuFirst")) {
352 showGroupsMenuFirst=(sValue=="TRUE");
353 }else if((sParam=="allowSoloNPC") || (sParam=="AllowSoloNPC")) {
354 allowSoloNPC=(sValue=="TRUE");
355 }else {
356 llOwnerSay("Warn: Unable to parse user defined configuration "+llDumpList2String(lParams,"="));
357 }
358 }else if(llGetListLength(lParams)!=0){
359 llOwnerSay("Warn: Skipping user defined configuration line "+(string)i+" "+llDumpList2String(lParams,"="));
360 }
361 }
362 }
363 buildGroupList()
364 {
365 groupList=[];
366 groupPage=0;
367 integer i;
368 while(i<llGetListLength(invGroups))
369 {
370 if( ( llList2String(invGroups,i+2)=="A" ) || ( (llList2String(invGroups,i+2)=="G") && llSameGroup(user)) || (user==llGetOwner()) || ( ownerUseUnlocksPerms && (llListFindList(positions,[llGetOwner()])>=0) ) ) groupList=[]+groupList+[llList2String(invGroups,i+3)];
371 i+=invGroupStride;
372 }
373 }
374 buildNpcList()
375 {
376 npcList=[];
377 npcPage=0;
378 integer i;
379 while(i<llGetListLength(invNpc))
380 {
381 if( ( llList2String(invNpc,i+1)=="A" ) || ( (llList2String(invNpc,i+2)=="G") && llSameGroup(user)) || (user==llGetOwner()) || ( ownerUseUnlocksPerms && (llListFindList(positions,[llGetOwner()])>=0) ) ) npcList=[]+npcList+[llList2String(invNpc,i+2)];
382 i+=invNpcStride;
383 }
384 showAddNpcMenu();
385 }
386 buildInventoryLists()
387 {
388 invGroups=[];
389 invNpc=[];
391 while(--i>-1)
392 {
394 if(llSubStringIndex(name,".menu")==0)
395 {
396 if(llListFindList(invGroups,[llGetSubString(name,10,-1)])>-1) llOwnerSay("ERROR! You have two groups notecards where the name \""+llGetSubString(name,10,-1)+"\" is used for the button! Group button names must be unique");
397 else invGroups=[]+[name,(integer)(llGetSubString(name,7,7)),llGetSubString(name,8,8),llGetSubString(name,10,-1)]+invGroups;
398 }
399 else if(llSubStringIndex(name,".NPC")==0)
400 {
401 if(llListFindList(invNpc,[llGetSubString(name,8,-1)])>-1) llOwnerSay("ERROR! You have two NPC notecards where the NPC name is \""+llGetSubString(name,8,-1)+"\" but NPC names must be unique");
402 else invNpc=[]+[name,llGetSubString(name,6,6),llGetSubString(name,8,-1)]+invNpc;
403 }
404 else if(llSubStringIndex(name,gs_ConfigName)==0)
405 {
406 gi_HaveConfig=1;
407 }
408 }
409 }
410 saveCard(string cardName)
411 {
413 {
414 llRemoveInventory(cardName);
415 llSleep(0.25);
416 }
417 integer i;
418 integer l=llGetListLength(anData);
419 string dataToStore;
420 while(i<l)
421 {
422 dataToStore+=llDumpList2String(llList2List(anData,i,i+anStride-1),"|")+"\n";
423 i+=anStride;
424 }
425 osMakeNotecard(cardName,dataToStore);
426 llMessageLinked(LINK_THIS,0,"GLOBAL_EDIT_STORE_TO_CARD|"+cardName,llDumpList2String(positions,"|"));
427 }
428 integer getUserLink(key who)
429 {
430 integer ret=FALSE;
431 if(who!=NULL_KEY)
432 {
434 while((link>1) && (ret==FALSE))
435 {
436 key this=llGetLinkKey(link);
437 if(this==who) ret=link;
438 link--;
439 }
440 }
441 return ret;
442 }
443 startListening()
444 {
445 llDialog(user,txtDia,llList2List(butDia,9,11)+llList2List(butDia,6,8)+llList2List(butDia,3,5)+llList2List(butDia,0,2),myChannel);
446 }
447 string currentlyPlaying()
448 {
449 string strToReturn="Currently playing: "+llList2String(currentAn,0)+" > "+llList2String(currentAn,1);
450 if(autoOn) strToReturn+="\nAUTO mode is on and set to "+(string)llRound(autoTimer)+" seconds";
451 return strToReturn;
452 }
453 persistChanges()
454 {
455 integer u=llGetListLength(positions);
456 while(--u>=0)
457 {
458 list avData=getPosition(llList2Key(positions,u),getUserLink(llList2Key(positions,u)));
459 vector pos=llList2Vector(avData,0);
460 rotation rot=llList2Rot(avData,1);
461 string strPos="<"+trimF(pos.x)+","+trimF(pos.y)+","+trimF(pos.z)+">";
462 string strRot="<"+trimF(rot.x)+","+trimF(rot.y)+","+trimF(rot.z)+","+trimF(rot.s)+">";
463 currentAn=[]+llListReplaceList(currentAn,[strPos,strRot],4+u*3,5+u*3);
464 }
465 integer anIndex=llListFindList(anData,llList2List(currentAn,1,1));
466 if(gi_NC_PROP_CHANGED) { // NC_PROPS
467 string old_cmd=llList2String(currentAn,2);
468 integer NC_PROP_data_start=llSubStringIndex(old_cmd,"NC_PROP{")+8; // find start
469 // test is little weird, as i added +8 to compensate for search string length
470 if(NC_PROP_data_start != 7) {
471 // search ending delimiter in rest of string
472 integer NC_PROP_data_end=NC_PROP_data_start+llSubStringIndex(llGetSubString(old_cmd, NC_PROP_data_start,-1),"}");
473 string new_cmd=llGetSubString(old_cmd, 0, NC_PROP_data_start -1) + gs_NC_PROP_DATA + llGetSubString(old_cmd, NC_PROP_data_end,-1);
474 currentAn=[]+llListReplaceList(currentAn,[new_cmd],2,2);
475 }
476 gi_NC_PROP_CHANGED=FALSE;
477 }
478 anData=[]+llListReplaceList(anData,llList2List(currentAn,1,-1),anIndex,anIndex+anStride-1);
479 llMessageLinked(LINK_THIS,0,"GLOBAL_EDIT_PERSIST_CHANGES",llDumpList2String(positions,"|"));
480 }
481 rezHandles()
482 {
483 if(llGetListLength(editHandles)<llGetListLength(positions)) llRezObject(handleName,llGetPos(),ZERO_VECTOR,ZERO_ROTATION,0);
484 else
485 {
486 rezzingHandles=FALSE;
487 integer h=llGetListLength(editHandles);
488 while(--h>=0) { osSetPrimitiveParams( llList2Key(editHandles,h),[PRIM_SIZE,handleSize,PRIM_COLOR,ALL_SIDES,llList2Vector(handleColours,h),handleAlpha,PRIM_TEXT,"pos "+(string)(h+1),llList2Vector(handleColours,h),1.0,PRIM_NAME,"pos "+(string)(h+1)]); }
489 playAnimation(llList2String(currentAn,1));
490 llSetTimerEvent(editTimer);
491 doSynch();
492 showEditMenu();
493 }
494 }
495 removeHandles()
496 {
497 llSetTimerEvent(0.0);
498 myState="RUNNING";
499 llMessageLinked(LINK_THIS,0,"GLOBAL_NOTICE_LEAVING_EDIT_MODE",llDumpList2String(positions,"|"));
500 integer l=llGetListLength(editHandles);
501 while(--l>=0) { osMessageObject(llList2Key(editHandles,l),"HANDLE_DIE"); }
502 editHandles=[];
503 showOptionsMenu();
504 }
505 setHandle(key prim, vector relPos, rotation relRot)
506 {
507 vector pos=relPos*llGetRot()+llGetPos();
508 rotation rot=relRot*llGetRot();
510 }
511 setPosition(key who, integer link, vector pos, rotation rot)
512 {
513 vector size = llGetAgentSize(who);
514 float fAdjust = ((((0.008906 * size.z) + -0.049831) * size.z) + 0.088967) * size.z;
515 llSetLinkPrimitiveParamsFast(link,[PRIM_POS_LOCAL, ((pos + <0.0, 0.0, 0.4>) - (llRot2Up(rot) * fAdjust)), PRIM_ROT_LOCAL, rot]);
516 }
517 list getPosition(key who, integer link)
518 {
519 vector size = llGetAgentSize(who);
520 float fAdjust = ((((0.008906 * size.z) + -0.049831) * size.z) + 0.088967) * size.z;
521 list avData=llGetLinkPrimitiveParams(link,[PRIM_POS_LOCAL,PRIM_ROT_LOCAL]);
522 vector avPos=llList2Vector(avData,0);
523 rotation avRot=llList2Rot(avData,1);
524 vector avPosUnadjusted=(avPos - <0.0, 0.0, 0.4>) + (llRot2Up(avRot) * fAdjust);
525 return [avPosUnadjusted,avRot];
526 }
527 list regToRel(vector regionPos,rotation regionRot)
528 {
529 vector relPos=(regionPos - llGetPos()) / llGetRot();
530 rotation relRot=regionRot/ llGetRot();
531 return [relPos,relRot];
532 }
533 list relToReg(vector refPos,rotation refRot)
534 {
535 vector regionPos=refPos*llGetRot()+llGetPos();
536 rotation regionRot=refRot*llGetRot();
537 return [regionPos,regionRot];
538 }
539 string trimF(float value)
540 {
541 integer newVal=llRound(value*10000);
542 integer negFlag=FALSE;
543 if(newVal<0)
544 {
545 negFlag=TRUE;
546 newVal*=-1;
547 }
548 integer strLength;
549 string retStr;
550 if(newVal==0) retStr="0";
551 else if(newVal<10) retStr="0.000"+(string)newVal;
552 else if(newVal<100) retStr="0.00"+(string)newVal;
553 else if(newVal<1000) retStr="0.0"+(string)newVal;
554 else if(newVal<10000) retStr="0."+(string)newVal;
555 else
556 {
557 retStr=(string)newVal;
558 strLength=llStringLength(retStr);
559 retStr=llGetSubString(retStr,0,strLength-5)+"."+llGetSubString(retStr,strLength-4,strLength-1);
560 }
561 while(llGetSubString(retStr,strLength,strLength)=="0")
562 {
563 retStr=llGetSubString(retStr,0,strLength-1);
564 strLength-=1;
565 }
566 if(negFlag) retStr="-"+retStr;
567 return retStr;
568 }
569 default
570 {
572 {
573 if(llGetAttached()) return;
575 {
576 myState="ERROR";
577 llOwnerSay("ERROR! The main PMAC controller script must always be located in the root prim of a linkset!");
578 return;
579 }
580 myChannel=0x80000000|(integer)("0x"+(string)llGetKey());
581 user=NULL_KEY;
582 buildInventoryLists();
583 if(gi_HaveConfig)
584 loadConfig();
586 {
587 llOwnerSay("ERROR! Unable to find the base priority 1 animation to use for synch: "+baseAn);
588 myState="ERROR";
589 return;
590 }
591
592 if(llListFindList(invGroups,[defaultGroup])==-1)
593 {
594 myState="ERROR";
595 llOwnerSay("ERROR! Unable to find the specified default group \""+defaultGroup+"\" in inventory. Make sure you supplied the simple group name, not the full card name");
596 return;
597 }
598 loadGroup(defaultGroup);
599 integer i=llList2Integer(invGroups,llListFindList(invGroups,[defaultGroup])-2);
600 positions=[];
601 while(--i>=0) { positions=[]+positions+[NULL_KEY]; }
602 currentAn=[]+[currentGroup]+llList2List(anData,0,anStride-1);
603 myState="READY";
604 llMessageLinked(LINK_THIS,0,"GLOBAL_SYSTEM_RESET",NULL_KEY);
605 llSitTarget(<0.0,0,0.001>,ZERO_ROTATION);
606 llOwnerSay("Initialization complete and ready to use");
607 }
608 on_rez(integer num)
609 {
611 }
612 object_rez(key id)
613 {
614 if(!rezzingHandles) return;
615 editHandles=[]+editHandles+[id];
616 rezHandles();
617 }
618 sensor(integer num)
619 {
620 llOwnerSay("ERROR! Sensor event inexplicably returned a result!");
622 autoOn=FALSE;
623 }
624 no_sensor()
625 {
626 if(!autoOn)
627 {
628 llOwnerSay("ERROR! Sensor repeat triggered but auto mode is off. Figure out how this can happen and fix. Sensor removed");
630 return;
631 }
632 if(myState!="RUNNING")
633 {
634 llOwnerSay("ERROR! Sensor repeat triggered while not in normal running state: Please figure out how this happened and fix. Sensor removed and auto turned off.\nState was=: "+myState);
635 autoOn=FALSE;
637 return;
638 }
639 integer i=llListFindList(anList,[llList2String(currentAn,1)]);
640 if(i==-1)
641 {
642 llOwnerSay("ERROR! Auto timer was unable to determine the index of the currently playing animation!");
644 autoOn=FALSE;
645 }
646 else
647 {
648 i++;
649 if(i>=llGetListLength(anList)) i=0;
650 playAnimation(llList2String(anList,i));
651 }
652 }
653 timer()
654 {
655 if(myState=="EDIT")
656 {
657 llSetTimerEvent(0.0);
658 integer l=llGetListLength(positions);
659 while(--l>-1)
660 {
661 key who=llList2Key(positions,l);
662 if(who==NULL_KEY)
663 {
664 llOwnerSay("ERROR! NULL_KEY user while processing timer event edit mode. Leaving edit mode without saving changes.");
665 removeHandles();
666 return;
667 }
669 {
670 llOwnerSay("ERROR! Cannot detect user in region while processing timer event edit mode. Leaving edit mode without saving changes");
671 removeHandles();
672 return;
673 }
674 list handleData=llGetObjectDetails(llList2Key(editHandles,l),[OBJECT_POS,OBJECT_ROT" title="View Definition" class="tooltip">OBJECT_ROT]);
675 if(llGetListLength(handleData)==0)
676 {
677 llOwnerSay("ERROR! Unable to detect a handle! Leaving edit mode without saving changes");
678 removeHandles();
679 return;
680 }
681 handleData=[]+regToRel(llList2Vector(handleData,0),llList2Rot(handleData,1));
682 setPosition(who,getUserLink(who),llList2Vector(handleData,0),llList2Rot(handleData,1));
683 }
684 llSetTimerEvent(editTimer);
685 }
687 }
688 link_message(integer sender,integer num,string message,key command)
689 {
690 if(num!=-1) return;
691 if(message=="MAIN_RESUME_MAIN_DIALOG") showOptionsMenu();
692 else if(llSubStringIndex(message,"MAIN_REGISTER_MENU_BUTTON")==0)
693 {
694 string buttonName=llList2String(llParseString2List(message,["|"],[]),1);
695 integer locationToAdd=llListFindList(specials,[buttonName]);
696 if(locationToAdd==-1) specials=[]+specials+[buttonName,command];
697 else specials=[]+llListReplaceList(specials,[buttonName,command],locationToAdd,locationToAdd+1);
698 specials=[]+llListSort(specials,2,TRUE);
699 }
700 else if(llSubStringIndex(message,"MAIN_UNREGISTER_MENU_BUTTON")==0)
701 {
702 string buttonName=llList2String(llParseString2List(message,["|"],[]),1);
703 integer locationToKill=llListFindList(specials,[buttonName,command]);
704 if(locationToKill==-1) return;
705 else specials=[]+llDeleteSubList(specials,locationToKill,locationToKill+1);
706 specials=[]+llListSort(specials,2,TRUE);
707 }
708 else if(llSubStringIndex(message,"NC_PROP_UPDATE")==0) // NC_PROP addon
709 {
710 gi_NC_PROP_CHANGED=TRUE;
711 gs_NC_PROP_DATA=llGetSubString(message,15,-1);
712 }
713 }
714 changed (integer change)
715 {
716 if(change & CHANGED_LINK)
717 {
719 {
720 llOwnerSay("ERROR! You have changed the linkset and the PMAC main script is no longer located in the root prim!");
721 myState="ERROR";
722 return;
723 }
726 list seated;
727 integer realAvi;
728 while(i>l)
729 {
730 key who=llGetLinkKey(i);
731 if((myState=="ERROR") || (myState=="INITIALIZING"))
732 {
733 if(osIsNpc(who)) osNpcRemove(who);
734 else
735 {
736 if(myState=="ERROR") llRegionSayTo(who,0,"Sorry, I have encountered an error and must shut down until it is corrected");
737 else llRegionSayTo(who,0,"Sorry, you cannot sit while I am initializing. Please wait a moment and try again.");
738 llUnSit(who);
739 }
740 }
741 else
742 {
743 seated=[]+[who]+seated;
744 if(!osIsNpc(who)) realAvi++;
745 if(llListFindList(positions,[who])==-1)
746 {
747 // new sitter
748 integer indexToSit=llListFindList(positions,[NULL_KEY]);
749 if(ownerUseReq && (who!=llGetOwner()) && (llListFindList(positions,[llGetOwner()])==-1))
750 {
751 llRegionSayTo(who,0,"Sorry, the system is set to require that the owner is using me before anyone else may sit.");
752 llUnSit(who);
753 }
754 else if(indexToSit==-1)
755 {
756 llRegionSayTo(who,0,"Sorry, there are no available positions for you to occupy. Please wait for someone to stand");
757 llUnSit(who);
758 }
759 else
760 {
761 positions=[]+llListReplaceList(positions,[who],indexToSit,indexToSit);
762 llSleep(0.2);
763 list anToStop=llGetAnimationList(who);
764 osAvatarPlayAnimation(who,baseAn);
765 integer stop=llGetListLength(anToStop);
766 key dontStop=llGetInventoryKey(baseAn);
767 while(--stop>-1) { osAvatarStopAnimation(who,llList2Key(anToStop,stop)); }
768 if(myState=="READY")
769 {
770 if((osIsNpc(who)) && (!allowSoloNPC))
771 {
772 llSay(0,"Sorry, an NPC cannot sit until there is a human user in control. Unsitting your NPC.");
773 llUnSit(who);
774 positions=[]+llListReplaceList(positions,[NULL_KEY],indexToSit,indexToSit);
775 osAvatarStopAnimation(who,baseAn);
776 }
777 else
778 {
779 myState="RUNNING";
780 llMessageLinked(LINK_THIS,0,"GLOBAL_START_USING",llDumpList2String(positions,"|"));
781 playAnimation(llList2String(currentAn,1));
782 }
783 }
784 else playAnimation(llList2String(currentAn,1));
785 }
786 }
787 }
788 i--;
789 }
790 i=llGetListLength(positions);
791 while(--i>=0)
792 {
793 key who=llList2Key(positions,i);
794 if(who!=NULL_KEY)
795 {
796 if((!realAvi)&&osIsNpc(who))
797 {
798 if(!allowSoloNPC)
799 {
800 osNpcRemove(who);
801 positions=[]+llListReplaceList(positions,[NULL_KEY],i,i);
802 }
803 }
804 else if(llListFindList(seated,[who])==-1)
805 {
806 if(myState=="EDIT")
807 {
808 llOwnerSay("WARNING! Someone stood! Leaving edit mode and no changes will be stored to card");
809 removeHandles();
810 if(who!=llGetOwner())showOptionsMenu();
811 }
813 {
814 osAvatarPlayAnimation(who,"stand");
815 osAvatarStopAnimation(who,llList2String(currentAn,3+i*3));
816 osAvatarStopAnimation(who,baseAn);
817 if(who==user) user=NULL_KEY;
818 }
819 positions=[]+llListReplaceList(positions,[NULL_KEY],i,i);
820 llMessageLinked(LINK_THIS,0,"GLOBAL_USER_STOOD|"+(string)i+"|"+(string)who,llDumpList2String(positions,"|"));
821 }
822 }
823 }
824 if(!realAvi)
825 {
826 if(diaHandle)
827 {
828 llListenRemove(diaHandle);
829 diaHandle=FALSE;
830 }
831 if(llGetListLength(seated)<1)
832 {
833 if(autoOn) llSensorRemove();
834 myState="READY";
835 llMessageLinked(LINK_THIS,0,"GLOBAL_SYSTEM_GOING_DORMANT",NULL_KEY);
836 if(resetOnQuit) llResetScript();
837 }
838 }
839 }
840 else if(change & CHANGED_REGION_START) llResetScript();
842 }
844 {
845 if(llGetAttached()) return;
846 key who=llDetectedKey(0);
847 if(osIsNpc(llDetectedKey(0))) return;
848 if(myState=="ERROR")
849 {
850 if(who==llGetOwner()) llOwnerSay("PMAC somehow entered ERROR state. Please check your chat log for one or more messages indicating the nature of the error, correct it, then reset the script");
851 else llRegionSayTo(who,0,"Sorry, I encountered an error and am waiting for the owner to correct the issue and restart me");
852 }
853 else if(myState=="INITIALIZING") llRegionSayTo(who,0,"Please wait until initialization is complete, then try again");
854 else if(myState=="READY") llRegionSayTo(who,0,"Please sit to begin using me");
855 else if(myState=="EDIT")
856 {
857 if(who!=llGetOwner()) llRegionSayTo(who,0,"Sorry, the owner is currently editting positions and must remain in control of the dialog until this is complete.");
858 else startListening();
859 }
860 else if(myState=="RUNNING")
861 {
862 if(llListFindList(positions,[who])==-1) llRegionSayTo(who,0,"Only current users may access the controls. Please sit, then try again");
863 else if(ownerOnlyMenus && (who!=llGetOwner())) llRegionSayTo(who,0,"Sorry, this item is currently set to only allow the owner to access the controls.");
864 else if(who!=user)
865 {
866 if(user==NULL_KEY)
867 {
868 if(!diaHandle) diaHandle=llListen(myChannel,"",NULL_KEY,"");
869 doChangeUser(who);
870 }
871 else llDialog(who,"Please confirm that you want to take control of me",["TAKE CONTROL","CANCEL"],myChannel);
872 }
873 else startListening();
874 }
875 else llOwnerSay("ERROR! Unexpected state when touched. State is: "+myState);
876 }
877 listen (integer channel, string name, key who, string message)
878 {
879 if(who!=user)
880 {
881 if(message=="TAKE CONTROL")
882 {
883 if(myState!="RUNNING")
884 {
885 if(who==llGetOwner()) llOwnerSay("Cannot give you control because I am not in RUNNING state. Current state is: "+myState);
886 else llRegionSayTo(who,0,"Sorry, you cannot take control at the moment because I am not currently in normal operation mode.");
887 return;
888 }
889 doChangeUser(who);
890 }
891 else if(message=="CANCEL") return;
892 else return;
893 }
894 else if(message=="-") startListening();
895 else if(message=="SYNCH")
896 {
897 doSynch();
898 startListening();
899 }
900 else if(message=="OPTIONS") showOptionsMenu();
901 else if(message=="GROUPS") showGroupsMenu();
902 else if(message=="<< BACK") loadGroup(llList2String(currentAn,0));
903 else if(message=="QUIT") doQuit();
904 else if(menu=="MENU_GROUPS")
905 {
906 if((message=="< PREV") || (message=="NEXT >"))
907 {
908 if(message=="< PREV") groupPage-=6;
909 else groupPage+=6;
910 if(groupPage>=llGetListLength(groupList)) groupPage=0;
911 else if(groupPage<=-6) groupPage=llGetListLength(groupList)-6;
912 if(groupPage<0) groupPage=0;
913 showGroupsMenu();
914 }
915 else loadGroup(message);
916 }
917 else if(menu=="MENU_ANIM")
918 {
919 if((message=="< PREV") || (message=="NEXT >"))
920 {
921 if(message=="< PREV") anPage-=6;
922 else anPage+=6;
923 if(anPage>=llGetListLength(anList)) anPage=0;
924 else if(anPage<=-6) anPage=llGetListLength(anList)-6;
925 if(anPage<0) anPage=0;
926 showAnMenu();
927 }
928 else
929 {
930 playAnimation(message);
931 showAnMenu();
932 }
933 }
934 else if(menu=="MENU_ADD_NPC")
935 {
936 if(message=="CANCEL") showOptionsMenu();
937 else if(llListFindList(positions,[NULL_KEY])==-1)
938 {
939 llRegionSayTo(user,0,"Cannot add a NPC as there are no longer any vacant positions");
940 showOptionsMenu();
941 }
942 else if((message=="< PREV") || (message=="NEXT >"))
943 {
944 if(message=="< PREV") npcPage-=9;
945 else npcPage+=9;
946 if(npcPage>=llGetListLength(npcList)) npcPage=0;
947 else if(npcPage<=-9) npcPage=llGetListLength(npcList)-9;
948 if(npcPage<0) npcPage=0;
949 showAddNpcMenu();
950 }
951 else
952 {
953 string npcToLoad=llList2String(invNpc,llListFindList(invNpc,[message])-2);
955 {
956 llRegionSayTo(user,0,"ERROR! Unable to locate the appearance notecard for this NPC.\nButton was: "+message+"\nCard should be: "+npcToLoad);
957 startListening();
958 return;
959 }
960 list thisNames=llParseString2List(message,[" "],[]);
961 if(llGetListLength(thisNames)==1) thisNames=[]+thisNames+["~"];
962 key npc=osNpcCreate(llList2String(thisNames,0),llList2String(thisNames,1),llGetPos()+<0.0,0.0,2.0>,npcToLoad,OS_NPC_SENSE_AS_AGENT);
963 osNpcSit(npc,llGetKey(),OS_NPC_SIT_NOW);
964 showOptionsMenu();
965 }
966 }
967 else if(menu=="MENU_SWAP_TO_POSITION")
968 {
969 if(message=="CANCEL") showOptionsMenu();
970 else doSwapPositions(llListFindList(positions,[user]),((integer)message)-1);
971 showOptionsMenu();
972 }
973 else if(menu=="MENU_UNSIT_POSITION")
974 {
975 if(message=="CANCEL") showOptionsMenu();
976 else
977 {
978 integer posToUnsit=(integer)message-1;
979 key thisKey=llList2Key(positions,posToUnsit);
980 if(osIsNpc(thisKey))
981 {
982 positions=[]+llListReplaceList(positions,[NULL_KEY],posToUnsit,posToUnsit);
983 osNpcRemove(thisKey);
984 }
985 else llUnSit(thisKey);
986 showOptionsMenu();
987 }
988 }
989 else if(menu=="MENU_SELECT_AUTO_MODE")
990 {
991 if(message=="AUTO OFF")
992 {
993 autoOn=FALSE;
995 llRegionSayTo(user,0,"Auto mode now off");
996 }
997 else if(message!="CANCEL")
998 {
999 autoOn=TRUE;
1000 if(message!="AUTO ON") autoTimer=(float)message;
1001 llSensorRepeat("THIS_WILL_NEVER_RETURN_A_SENSOR_RESULT",NULL_KEY,AGENT,0.001,0.0,autoTimer);
1002 llRegionSayTo(user,0,"Auto mode is on and set to "+(string)llRound(autoTimer)+" seconds");
1003 }
1004 showOptionsMenu();
1005 }
1006 else if(menu=="MENU_SPECIALS")
1007 {
1008 if(message=="CANCEL") showOptionsMenu();
1009 else if((message=="< PREV") || (message=="NEXT >"))
1010 {
1011 if(message=="< PREV") specPage-=18;
1012 else specPage+=18;
1013 if(specPage>=llGetListLength(specials)) specPage=0;
1014 else if(specPage<=-18) specPage=llGetListLength(specials)-18;
1015 if(specPage<0) specPage=0;
1016 showSpecialsMenu();
1017 }
1018 else llMessageLinked(LINK_THIS,0,llList2String(specials,llListFindList(specials,[message])+1)+"|"+user,llDumpList2String(positions,"|"));
1019 }
1020 else if(menu=="MENU_OPTIONS")
1021 {
1022 if(message=="AUTO") showAutoMenu();
1023 else if(message=="SPECIAL") showSpecialsMenu();
1024 else if(llSubStringIndex(message,"MENUS")==0)
1025 {
1026 ownerOnlyMenus=!ownerOnlyMenus;
1027 if(ownerOnlyMenus) llRegionSayTo(who,0,"Menus are now locked. Only the owner can take control of me");
1028 else llRegionSayTo(who,0,"Menus are now unlocked. Any user can now take control of me");
1029 showOptionsMenu();
1030 }
1031 else if(message=="EDIT ON")
1032 {
1033 if(llListFindList(positions,[NULL_KEY])>=0)
1034 {
1035 llRegionSayTo(who,0,"Sorry, all positions must be filled before you can enter edit mode and there is currently at least one that is empty.");
1036 showOptionsMenu();
1037 }
1038 else if(currentGroup!=llList2String(currentAn,0))
1039 {
1040 llRegionSayTo(user,0,"Cannot enter edit mode because the animation currently playing is not from the same notecard as your currently loaded group. Either select an animation from this card or load the group that the current animation belongs to.");
1041 showOptionsMenu();
1042 }
1043 else
1044 {
1045 if(autoOn)
1046 {
1047 autoOn=FALSE;
1049 llOwnerSay("Auto mode switched off");
1050 }
1051 integer l=llGetListLength(positions);
1052 while(--l>=0) { if(!osIsNpc(llList2Key(positions,l))) llRegionSayTo(llList2Key(positions,l),0,"The Owner has entered edit mode and all functions are temporarily disabled. Please do not stand while these adjustments are being made."); }
1053 myState="EDIT";
1054 llMessageLinked(LINK_THIS,0,"GLOBAL_NOTICE_ENTERING_EDIT_MODE",llDumpList2String(positions,"|"));
1055 editHandles=[];
1056 rezzingHandles=TRUE;
1057 rezHandles();
1058 }
1059 }
1060 else if(message=="SWAP")
1061 {
1062 integer i=llGetListLength(positions);
1063 if(i==1)
1064 {
1065 llRegionSayTo(user,0,"The current animation group is for only one position so cannot swap positions.");
1066 showOptionsMenu();
1067 }
1068 else if(i==2)
1069 {
1070 doSwapPositions(0,1);
1071 showOptionsMenu();
1072 }
1073 else
1074 {
1075 txtDia="";
1076 butDia=[];
1077 while(--i>=0)
1078 {
1079 key thisKey=llList2Key(positions,i);
1080 if(thisKey==user) txtDia="\n"+(string)(i+1)+". (you are curently here)"+txtDia;
1081 else
1082 {
1083 butDia=[]+[(string)(i+1)]+butDia;
1084 if(thisKey==NULL_KEY) txtDia="\n"+(string)(i+1)+". (empty)"+txtDia;
1085 else txtDia="\n"+(string)(i+1)+". "+llGetUsername(thisKey)+txtDia;
1086 }
1087 }
1088 txtDia=""+"SWAP POSITION\n\nSelect the position number you would like to swap with\n"+txtDia;
1089 while(llGetListLength(butDia)<8) { butDia=[]+butDia+["-"]; }
1090 butDia=[]+butDia+["CANCEL"];
1091 while(llListFindList(butDia,["-","-","-"])>=0) { butDia=[]+llDeleteSubList(butDia,llListFindList(butDia,["-","-","-"]),llListFindList(butDia,["-","-","-"])+2); }
1092 menu="MENU_SWAP_TO_POSITION";
1093 startListening();
1094 }
1095 }
1096 else if(message=="UNSIT")
1097 {
1098 list whoCanUnsit;
1099 integer i=llGetListLength(positions);
1100 while(--i>=0)
1101 {
1102 key thisKey=llList2Key(positions,i);
1103 if((thisKey!=user) && (thisKey!=llGetOwner()) && (thisKey!=NULL_KEY)) whoCanUnsit=[]+[i,thisKey]+whoCanUnsit;
1104 }
1105 integer l=llGetListLength(whoCanUnsit);
1106 if(l==0)
1107 {
1108 llRegionSayTo(user,0,"There are no users you can remove");
1109 showOptionsMenu();
1110 }
1111 else if(l==2)
1112 {
1113 integer thisInd=llList2Integer(whoCanUnsit,0);
1114 key thisUser=llList2Key(whoCanUnsit,1);
1115 if(osIsNpc(thisUser))
1116 {
1117 positions=[]+llListReplaceList(positions,[NULL_KEY],thisInd,thisInd);
1118 osNpcRemove(thisUser);
1119 }
1120 else llUnSit(thisUser);
1121 showOptionsMenu();
1122 }
1123 else
1124 {
1125 txtDia="UNSIT A USER\n\nSelect the user to unsit (selecting NPC it will remove it). The number is the position they currently occupy.\n";
1126 butDia=[];
1127 i=0;
1128 while(i<l)
1129 {
1130 txtDia+="\n"+(string)(llList2Integer(whoCanUnsit,i)+1)+". "+llGetUsername(llList2Key(whoCanUnsit,i+1));
1131 butDia=[]+butDia+[(string)(llList2Integer(whoCanUnsit,i)+1)];
1132 i+=2;
1133 }
1134 while(llGetListLength(butDia)<8) { butDia=[]+butDia+["-"]; }
1135 butDia=[]+butDia+["CANCEL"];
1136 while(llListFindList(butDia,["-","-","-"])>=0) { butDia=[]+llDeleteSubList(butDia,llListFindList(butDia,["-","-","-"]),llListFindList(butDia,["-","-","-"])+2); }
1137 menu="MENU_UNSIT_POSITION";
1138 startListening();
1139 }
1140 }
1141 else if(message=="ADD NPC")
1142 {
1143 if(llListFindList(positions,[NULL_KEY])==-1)
1144 {
1145 llRegionSayTo(user,0,"Cannot add a new NPC at this time as there are no vacant positions");
1146 showOptionsMenu();
1147 }
1148 else buildNpcList();
1149 }
1150 else llOwnerSay("ERROR! Received unexpected message from Options menu: "+message);
1151 }
1152 else if(menu=="MENU_EDIT")
1153 {
1154 if(myState!="EDIT")
1155 {
1156 llOwnerSay("No longer in edit mode so unable to handle a response from that menu.");
1157 showOptionsMenu();
1158 }
1159 llSetTimerEvent(0.0);
1160 integer valid=TRUE;
1161 integer check=llGetListLength(positions);
1162 while(valid && (--check>=0)) { if(!getUserLink(llList2Key(positions,check))) valid=FALSE; }
1163 if(!valid)
1164 {
1165 llOwnerSay("ERROR! A user appears to have stood. Cannot remain in edit mode unless all positions are occupied. Ignoring your selection, reverting to stored position for this animation, and leaving edit mode without saving any changes. Once all positions are filled once more you may return to edit mode to resume work or save the stored data.");
1166 playAnimation(llList2String(currentAn,1));
1167 removeHandles();
1168 return;
1169 }
1170 if(message=="STORE ADDON") llMessageLinked(LINK_THIS,0,"GLOBAL_STORE_ADDON_NOTICE",llDumpList2String(positions,"|"));
1171 else if(message=="REVERT THIS") playAnimation(llList2String(currentAn,1));
1172 else
1173 {
1174 persistChanges();
1175 if(message=="EDIT OFF") removeHandles();
1176 else if((message=="STORE THIS") || (message=="< PREV") || (message=="NEXT >"))
1177 {
1178 integer anToPlay=llListFindList(anList,[llList2String(currentAn,1)]);
1179 if(message=="< PREV") anToPlay--;
1180 else if(message=="NEXT >") anToPlay++;
1181 if(anToPlay<0) anToPlay=llGetListLength(anList)-1;
1182 if(anToPlay>=llGetListLength(anList)) anToPlay=0;
1183 playAnimation(llList2String(anList,anToPlay));
1184 }
1185 else if((message=="SAVE CARD") || (message=="SAVE NEW"))
1186 {
1187 string cardName=llList2String(invGroups,llListFindList(invGroups,[currentGroup])-3);
1188 string strToSay;
1189 if(message=="SAVE NEW")
1190 {
1191 integer num=2;
1192 while(llGetInventoryType(cardName+(string)num)==INVENTORY_NOTECARD) { num++; }
1193 cardName=""+cardName+(string)num;
1194 currentGroup=llGetSubString(cardName,10,-1);
1195 invGroups=[]+invGroups+[cardName,(integer)(llGetSubString(cardName,7,7)),llGetSubString(cardName,8,8),currentGroup];
1196 groupList=[]+groupList+[currentGroup];
1197 currentAn=[]+llListReplaceList(currentAn,[currentGroup],0,0);
1198 strToSay="All data stored to a new notecard and now working with the newly created group: "+currentGroup;
1199 }
1200 else strToSay="All data now updated in notecard for the group: "+currentGroup;
1201 saveCard(cardName);
1202 llRegionSayTo(user,0,strToSay);
1203 }
1204 else llOwnerSay("ERROR! Message not expected in Edit menu handling: "+message);
1205 }
1206 if(myState=="EDIT")
1207 {
1208 llSetTimerEvent(editTimer);
1209 showEditMenu();
1210 }
1211 }
1212 }
1213 }

PMAC

Animation Handle for Avatars

Category: NPC
By : Aine Caoimhe
Created: 2015-11-24 Edited: 2015-11-23
Worlds: OpenSim

1 // PARAMOUR MULTI-ANIMATION CONTROLLER (PMAC) v1.0 POSITIONING HANDLE SCRIPT
2 // by Aine Caoimhe January 2015
3
4 // Provided under Creative Commons Attribution-Non-Commercial-ShareAlike 4.0 International license.
5 // Please be sure you read and adhere to the terms of this license: https://creativecommons.org/licenses/by-nc-sa/4.0/
6 //
7 // There are no settings in this script that would normally require adjustment by the user.
8 // Edit at your own risk
9
10 default
11 {
12 dataserver(key who,string message)
13 {
14 if(message=="HANDLE_DIE") llDie();
15 }
16 changed(integer change)
17 {
18 if(change & CHANGED_REGION_START) llDie();
19 }
20 }

PMAC

PARAMOUR MULTI-ANIMATION CONTROLLER (PMAC) v1.02(OSSL)

Category: NPC
By : Aine Caoimhe
Created: 2015-11-24 Edited: 2015-11-23
Worlds: OpenSim

1
2
3
4
5 // by Aine Caoimhe January 2015
6 // Provided under Creative Commons Attribution-Non-Commercial-ShareAlike 4.0 International license.
7 // Please be sure you read and adhere to the terms of this license: https://creativecommons.org/licenses/by-nc-sa/4.0/
8 //
9 // rev 1.1 Added GIF capability - Ferd Frederix
10 // Images from the Gif2SL program can be used. These have the name Texture;X;Y;FPS. The script detects the X, Y and FPS
11 // numbers and animates the face of the prim to match
12 // Example: PMAC002;5;4;10 will animate a 5 X 4 GIF at 10 FPS
13
14 integer display=1; // face for the main display
15 integer page;
16 integer showPageNum=FALSE;
17 vector floatyColour=<0.0, 1.0, 0.0>;
18 integer tCount;
19 integer init=TRUE;
20
21 integer textStep;
22 string textToSay;
23 list said;
24
25 sayText()
26 {
27 if(textToSay=="")
28 {
29 llOwnerSay("Text finished. Next: slide "+(string)(page+1));
30 return;
31 }
32 string thisText;
33 integer stop=llSubStringIndex(textToSay,"*DELAY*");
34 if(stop>-1)
35 {
36 thisText=llGetSubString(textToSay,0,stop-1);
37 float delay=(float)llGetSubString(textToSay,stop+7,stop+11);
38 textToSay=""+llGetSubString(textToSay,stop+12,-1);
39 llSetTimerEvent(delay);
40 }
41 else
42 {
43 thisText=textToSay;
44 textToSay="";
45 llSetTimerEvent(0.25);
46 }
47 llSay(0," \n"+thisText);
48 }
49
50
51 gif(integer face, string t)
52 {
53
54 list gif = llParseString2List(t,[";"],[]);
55
56
57 string aname = llList2String(gif,0);
58
59 integer X = (integer) llList2String(gif,1);
60 integer Y = (integer) llList2String(gif,2);
61 float FPS = (float) llList2String(gif,3);
62
63 float product = X * Y;
64
65 // Set the new prim texture
66 llSetLinkPrimitiveParamsFast(LINK_THIS,[PRIM_TEXTURE,face,t,<1,1,0>,ZERO_VECTOR,0.0]);
67
68 if(X && face == 1) {
69 llSetTextureAnim( ANIM_ON | LOOP, 1, X, Y, 0.0, product, FPS);
70 } else if(!X && face == 1) {
71 llSetTextureAnim( LOOP, 1, 1,1, 0.0, 1 , FPS);
72 }
73
74 }
75
76
77 showPage()
78 {
79 integer face;
80 while(face<4)
81 {
82 integer tInd=page-1+face;
83 if((tInd>=0) && (tInd<tCount)) {
85 gif(face,Texture);
86 }
87 face++;
88 }
89 if(showPageNum) llSetText("Page "+(string)(page+1),floatyColour,1.0);
90 if(init)
91 {
92 init=FALSE;
93 return;
94 }
95 string card="~text"+(string)page;
96 if(llListFindList(said,[card])==-1)
97 {
98 said=[]+said+[card];
99 textToSay=""+osGetNotecard(card);
100 sayText();
101 }
102 }
103 default
104 {
106 {
107 page=0;
108 said=[];
109 if(!showPageNum) llSetText("",ZERO_VECTOR,0.0);
111 if(tCount<1) llOwnerSay("No textures to display");
112 //showPage();
113 }
114 timer()
115 {
116 llSetTimerEvent(0.0);
117 sayText();
118 }
119 on_rez(integer foo)
120 {
121 if(llGetAttached()!=0) return; // ignore if worn
123 }
124 link_message(integer sender, integer val, string message, key users)
125 {
126 //llOwnerSay("Heard: "+message);
127 list data=llParseString2List(message,["|"],[]);
128 string command=llList2String(data,0);
129 if(command=="GLOBAL_SYSTEM_RESET") return;
130 else if(command=="GLOBAL_SYSTEM_GOING_DORMANT") return;
131 else if(command=="GLOBAL_START_USING") llResetScript();
132 else if(command=="GLOBAL_USER_STOOD") return;
133 else if(command=="GLOBAL_ANIMATION_SYNCH_CALLED") return;
134 else if(command=="GLOBAL_NEW_USER_ASSUMED_CONTROL") return;
135 else if(command=="GLOBAL_NOTICE_ENTERING_EDIT_MODE") return;
136 else if(command=="GLOBAL_EDIT_PERSIST_CHANGES") return;
137 else if(command=="GLOBAL_STORE_ADDON_NOTICE") return;
138 else if(command=="GLOBAL_EDIT_STORE_TO_CARD") return;
139 else if(command=="GLOBAL_NOTICE_LEAVING_EDIT_MODE") return;
140 else
141 {
142 list coms=llParseString2List(llList2String(data,1),["{","}"],[]);
143 string addon=llList2String(coms,0);
144 if(addon=="ainepres")
145 {
146 page=llList2Integer(coms,1);
147 showPage();
148 }
149 }
150 }
151 }

Back to the Best Free Tools in Second Life and OpenSim.