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

[Table of Contents]

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 HyperGrid Story Nine  

HyperGrid Story Nine

Triggers the NPC controller to play the Greet notecard when collided.

Category: NPC
By : Shin Ingen
Created: 2015-11-24 Edited: 2015-11-23
Worlds: Second Life

the Zip file

Download all files for HyperGrid Story Nine
Contents are in zip format, with .LSL (text) source code and LSLEdit (text + Solution) formats.
Get file # 1. Collider.lsl
Get file # 2. NPC Recorder Script v 4.5.lsl
Get file # 3. Direction button.lsl
Get file # 4. Doorway.lsl
Get file # 5. N&D controller.lsl
Get file # 6. Red Button.lsl
Get file # 7. Sequence.txt
Get file # 8. Sequencer.lsl
Get file # 9. Shins Script.lsl
Get file # 10. Timer.lsl
Get file # 11. Collider.lsl
Get file # 12. Effect.lsl
Get file # 13. NPC 9.3.lsl
Get file # 14. NPC Custom for Dylan.lsl
Get file # 15. back-in-time.wav
Get file # 16. coins_bag_2_3.wav
Get file # 17. Controller.lsl
Get file # 18. Direction button.lsl
Get file # 19. Doorway.lsl
Get file # 20. footstepmuffled.wav
Get file # 21. Go.lsl
Get file # 22. Light effect Bar.lsl
Get file # 23. magic-string-spell-2.wav
Get file # 24. Script.lsl
Get file # 25. NPC Custom for Namaka.lsl
Get file # 26. Shins Script.lsl
Get file # 27. Timer.lsl
Get file # 28. [238614 bytes] Image1.jpg
Get file # 29. [82289 bytes] Image1noted.jpg
Get file # 30. [321396 bytes] Image2.jpg
Get file # 31. [295803 bytes] Image3.jpg
Get file # 32. [235697 bytes] Image4.jpg
1
2 string message = "@notecard=Greet";
3
4 Reset() {
5 llSetStatus(STATUS_PHANTOM, FALSE); // rev 2.0
7 llSleep(0.1);
9 }
10
11 default{
13 Reset();
14 }
17 return;
18 }
19 llMessageLinked(2,0,message,"");
20
22 }
23 timer()
24 {
25 Reset();
27 }
28 on_rez(integer p){
30 }
31 changed(integer what){
32 if(what & CHANGED_REGION_START){
34 }
35 }
36 }

HyperGrid Story Nine

All in one NPC recorder player.
Supports both absolute and relative paths and many new commands
Add animations named "Fly, Walk, Stand and Run"
Click Prim to use.
Should be worn as a HUD to record.
Put it on the ground and click Sensor or Start NPC when done.

Category: NPC
By : Shin Ingen
Created: 2015-11-24 Edited: 2015-11-23
Worlds: Second Life

1 // This is Rev 9.3, based on rev 4.6 - skipped way up because of custom tweaks.
2
3 // Revision History
4 // Rev 1.1 10-2-2014 @Sit did not work. Minor tweaks to casting for lslEditor
5 // Rev 1.2 10-14-2014 @ sit had wrong type.
6 // Rev 1.3 relative movement fixed for @fly
7 // Rev 1.4 4-3-2014 allow anyone to use this, non owners and non group members can only start and stop.
8 // Rev 1.5 5-17-2014 set sensor to auto start on reboot of sim
9 // Rev 1.6 5-24-2014 move menu so you can get it by touching, removed many of the KeyValues to RAM for efficiency
10 // Rev 1.7 CHANGED_REGION_START, not CHANGED_REGION_START (Opensim difference)
11 // Rev 1.8 tuned up Kill NPC, added more flexible upgrader
12 // Rev 1.9 Better script injection by link message// Rev 2.0 Added osSetSpeed so you can speed up or slow down an NPC.
13 // Rev 2.1 No laggy sensor used exept to sit on stuff
14 // Rev 2.2 Various sensor fixes
15 // Rev 2.3 Sets No Sensor in menu, must be started by hand
16 // Rev 2.4 - reserved for patches to 2.3 if needed
17 // Rev 3.0 Refactor out into subs, not states to make command injection easier
18 // New command: @appearance=Notecardname so you can switch to a new notecard on the fly
19 // New command: @speed=1.0 which slows up ( < 1 ) or speeds up ( > 1)
20 // Rev 3.1 Commands are not interruptible by Link Message
21 // Rev 3.2 Sensor patches for consistency in removing the NPC
22 // Rev 3.3 Added Touch command by Neo.Cortex@hbase42/hopto/org:8002
23 // Added Menu 3 for notecard and appearance commands
24 // Rev 3.4 animation timer cannot be zero or it shuts off timer tweaked
25 // solves the NPC starting up when no sensor is set.
26 // Rev 3.5 fixes saving to !Path notecard
27 // Rev 3.6 08-11-2015 @delete acts like @stop. TYjhe NPC now rezzes after an @go back in where it was deleted
28 // Rev 3.7 08-11-2015 @attach command added to load an attachment from the inventory to the NPC
29 // Rev 3.8 08-17-2015 process queued commands one at a time without calling ProcessNPCLine on link message
30 // Rev 3.9 08-23-2011 Queued command fixes including @delete which were not always working
31 // Rev 4.0 09-15-2015 Fixes for Sensor functions which continually rezzed a NPC when no one was around.
32 // Rev 4.1 09-20-2015 Added a Listener so link messages are not needed
33 // Rev 4.2 09-23-2015 Added @teleport=<vector>
34 // Rev 4.3 09-24-2015 Added @reset to restart the NPC at the very start of the !Path notecard
35 // @teleport works for relative and absolute modes
36 // Rev 4.4 09-26-2015 if it could not find the (deleted) NPC, it could not restart
37 // Rev 4.5 09-29-2015 remove wait for STATE == 0
38 //*******************************************************************//
39
40 // Instructions on how to use this are at http://www.outworldz.com/opensim/posts/NPC/
41 // This is an OpenSim-only script.
42 // Author: Ferd Frederix aka Fred Beckhusen - fred@mitsi.com
43
44 ////////////////////////////////////////////////////////////////////////////////////////////
45 // Original code was Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 //
46 ///////////////////////////////////////////////////////////////////////////////////////////
47 // Please see: http://www.gnu.org/licenses/gpl.html for legal details, //
48 // rights of fair usage, the disclaimer and warranty conditions. //
49 ///////////////////////////////////////////////////////////////////////////////////////////
50 // The original NPC controller was from http://was.fm/opensim:npc
51 // Extensive additions and bug fixes by Fred Beckhusen, aka Ferd Frederix
52 // llSensor had two params swapped
53 // @Wander would wander where it had rezzed, not where it was.
54 // There was no 'no_sensor' event in sit, so if a @sit failed, the NPC got stuck
55 // The animation and walks always stopped old, then started new. It should be start new, then stop old so the default stand would be suppressed.
56 // New code:
57 // Merged with new Route recorder and notecard writer
58 // If the NPC failed to reach a destination it never moved on.
59 // Added WAIT global to tune this
60 // Exposed many tunable variables and ported the code
61 // Added floating point to times in notecard.
62 // Added @sound, @randsound, @whisper, @shout, and @cmd controls.
63 // notecards integers are not floats for better control
64 //
65 // Link Messages may be used to perform external control by injecting @commands into the stream of actions
66 // Example:
67 // To chat something, such as with a chat robot
68 // llMessageLinked(LINK_SET,0,"@npc_say=Hello","");
69
70 // This script assumes that NPCs and OSSl scripting is enabled in the OpenSim configuration.
71 // In order to enable them, the following changes must be made in the OpenSim.ini configuration file:
72 //
73 // ; Turn on OSSL
74 // AllowOSFunctions = true
75 // OSFunctionThreatLevel = Severe
76
77 //[NPC]
78 // ;# {Enabled} {} {Enable Non Player Character (NPC) facilities} {true false}
79 // Enabled = true
80 //
81 // and then the server has to be restarted.
82 // please note that there are better ways to enable NPC in the latest Opensim.
83
84 // Commands: All commands begin with an @ sign. All other lines are ignored
85 // @commands may have optional parameters. The syntax is always:
86 // @cmd=parm1|parm2
87 // NaN in the table below meand Not a Number. This means there is no parameter
88
89 //Command First Parameter Second Parameter Description
90 //@spawn name location (vector) Rezzes an NPC with name at a location.
91 //@appearance NoteCardName NaN switch the NPC appearance to a new notecard
92 //@walk destination (vector) NaN Makes the NPC walk to destination.
93 //@fly destination (vector) NaN Makes the NPC fly to destination.
94 //@land destination (vector) NaN Makes the NPC land at destination.
95 //@say string NaN Makes the NPC speak a phrase.
96 //@whisper string NaN Makes the NPC whisper a phrase.
97 //@shout string NaN Makes the NPC shout a phrase.
98 //@pause seconds (float) NaN Makes the NPC wait for a multiple of seconds.
99 //@wander radius (float) cycles (integer) Makes the NPC wander in radius, for cycles seconds.
100 //@delete NaN NaN Removes the NPC. Requires a link message to continue
101 //@goto label (string) NaN Jump to the label label in the script.
102 //@animate animation (string) time (float) Makes the NPC trigger the animation animation for time seconds.
103 //@sound sound_name NaN plays a sound from inventory
104 //@randsound NaN NaN Plays a random sound from inventory
105 //@rotate degrees (float) NaN Rotate the NPC degrees around the Z axis.
106 //@sit primitive name NaN Sit on a primitive with a given name.
107 //@touch primitive name NaN Touch on a primitive with a given name.
108 //@stand NaN NaN If sitting on a primitive, stand up.
109 //@cmd channel (integer) string Says string on channel, for controlling external gadgets
110 //@stop NaN NaN Halts the NPC script indefinitely. Can be started with a link message
111 //@go NaN NaN Continues on next notecard line, for use in link messages
112 //@speed speed (float) NaN from 0 to N, where 1.0 ius a normal speed of an avatar. 0.2 is a turtle.
113 //@notecard notename (string) NaN load a new Path notecard
114 //@attach InventoryName attachmentPoint load an attachment from the inventory to the NPC onto point
115 //@teleport destination (vector) NaN Makes the NPC teleport to destination in the same sim. They cannot tp to another sim or across the HG
116 //@reset NaN NaN Deletes the NPC, starts the !Path notecard over.
117
118 // Constant attachmentPoint Comment
119 // ATTACH_CHEST 1 chest/sternum
120 // ATTACH_HEAD 2 head
121 // ATTACH_LSHOULDER 3 left shoulder
122 // ATTACH_RSHOULDER 4 right shoulder
123 // ATTACH_LHAND 5 left hand
124 // ATTACH_RHAND 6 right hand
125 // ATTACH_LFOOT 7 left foot
126 // ATTACH_RFOOT 8 right foot
127 // ATTACH_BACK 9 back
128 // ATTACH_PELVIS 10 pelvis
129 // ATTACH_MOUTH 11 mouth
130 // ATTACH_CHIN 12 chin
131 // ATTACH_LEAR 13 left ear
132 // ATTACH_REAR 14 right ear
133 // ATTACH_LEYE 15 left eye
134 // ATTACH_REYE 16 right eye
135 // ATTACH_NOSE 17 nose
136 // ATTACH_RUARM 18 right upper arm
137 // ATTACH_RLARM 19 right lower arm
138 // ATTACH_LUARM 20 left upper arm
139 // ATTACH_LLARM 21 left lower arm
140 // ATTACH_RHIP 22 right hip
141 // ATTACH_RULEG 23 right upper leg
142 // ATTACH_RLLEG 24 right lower leg
143 // ATTACH_LHIP 25 left hip
144 // ATTACH_LULEG 26 left upper leg
145 // ATTACH_LLLEG 27 left lower leg
146 // ATTACH_BELLY 28 belly/stomach/tummy
147 // ATTACH_LEFT_PEC 29 left pectoral
148 // ATTACH_RIGHT_PEC 30 right pectoral
149 // ATTACH_HUD_CENTER_2 31 HUD Center 2
150 // ATTACH_HUD_TOP_RIGHT 32 HUD Top Right
151 // ATTACH_HUD_TOP_CENTER 33 HUD Top
152 // ATTACH_HUD_TOP_LEFT 34 HUD Top Left
153 // ATTACH_HUD_CENTER_1 35 HUD Center
154 // ATTACH_HUD_BOTTOM_LEFT 36 HUD Bottom Left
155 // ATTACH_HUD_BOTTOM 37 HUD Bottom
156 // ATTACH_HUD_BOTTOM_RIGHT 38 HUD Bottom Right
157 // ATTACH_NECK 39 neck
158 // ATTACH_AVATAR_CENTER 40 avatar center/root
159
160
161
162 //////////////////////////////////////////////////////////
163 // DEBUG //
164 //////////////////////////////////////////////////////////
165 integer debug = FALSE; // set to TRUE or FALSE for debug chat on various actions
166 integer LSLEditor = FALSE; // set to to TRUE to working in LSLEditor, FALSE for in-world.
167 // you must also include the NPC commands found in the other script since LSLEditor does not support OpenSim
168 integer iTitleText = FALSE; // set to TRUE to see debug info in text above the controller
169
170 //////////////////////////////////////////////////////////
171 // TUNABLE CONFIGURATION //
172 //////////////////////////////////////////////////////////
173 integer allowListener = TRUE; // set to TRUE to anable a command listener. Usually, this is setto FALSE
174 integer link_Channel = 4223; // some random number you want to talk to this gadget on. Best if large and negative
175 float TIMER = 2; // faster = less jerky stopping. How often the system checks the distance traveled. Fastest you can go is 0.5 seconds
176 float QUICK = 1; // when we need to move to the next state, we use a QUICK timer
177 string Appearance = "!Appearance"; // The name of the recorded Appearance notecard
178 string Notecard = "!Path"; // The name of the recorded routes
179 integer allowUsers = FALSE; // If true, any user can get a Start NPC and Stop NPC menu. Only groups and owners can get all commands if TRUE, or FALSE
180 float MAXDIST = 2.0; // how close a NPC has to get to a dest pos to continue to next state. Do not lower this too much, as it may miss the target
181 integer WANDERRAND = TRUE; // set to TRUE and they will pause during wanders a random number of seconds
182 float WANDERTIME = 3.0; // how long they stand after each @wander,if WANDERRAND is FALSE. If WANDERRAND is TRUE, this is the max time
183 integer WAIT = 30; // wait for this number of seconds for the NPC to reach a destination (for safety). If it fails to reach a target, it will move on after this time.
184 float RANGE = 150; // 1 to N meters - anyone this close to the controller will start NPCS if Sensor button is clicked
185 float REZTIME = 2.0; // wait this long for NPC to rez in, then start the process
186 string STAND = "Stand"; // the name of the default Stand animation
187 string WALK = "Walk"; // the name of the default Walk animation
188 string FLY = "Fly"; // the name of the default Fly animation
189 string RUN = "Run"; // the name of the default Run animation
190 string LAND = "Land"; // the name of the default land animation ( for birds only)
191 float OffsetZ = 0.5; // appear 0.5 meter above ground, this is added to all destinations to keep them from sinking in.
192 float SPEEDMULT =0.8; // 1.0 = regular avatar speed. Smaller numbers slow down walks. Large numbers speed them up.
193 integer FLIGHT = 299; // For controlling wings. A channel that is shouted at when flight starts and ends. "flying" or "landing"
194
195 // DESCRIPTIONS FIELDS HAVE TO SURVIVE A RESET
196 // These vars are stored by saving them with KeyValueSet
197 // "pr" is a 0 if it is set for Owner Only, 1 for Group control
198 // "se" is "on" if Started
199 // "co" = "R" or "A" for relative or absolute addressing mode
200 // "key" = NPC key
201
202 // These Globals used to be stored in description. Moved to RAM in V1.6
203 float RAMPause; // @pause param
204 float RAMwd ; // @wander distance
205 integer RAMwc; // @wander count
206 float RAMrot; // @rotate
207 string RAMsit; // @sit primname
208 string RAMtouch; // @touch primname
209 string RAManimationName; // @animate animation (string) time (float)
210 float RAManimationTime;
211
212 // other globals section
213 integer iChannel; // a listen channel, randomly assigned
214 integer iHandle; // the handle to it
215
216 // NPC controls
217 vector newDest ; // tmp storage for the walks
218 integer iWaitCounter ; // wait for this number of seconds for the NPC to reach a desrtination
219 string sNPCName; // the name of the NPC that may be in world. So we can remove it.
220 integer bNPC_STOP = FALSE; // boolean to reuse a listener
221 integer Stopped = FALSE; // set to TRUE by link messages so we do not remember them
222 float fTimerVal ; // how long we wait when wandering (calculated)
223 float NPCEnabled; // true if the NPC is suppodes to be running
224
225 // OS_NPC_CREATOR_OWNED will create an 'owned' NPC that will only respond to osNpc* commands issued from scripts that have the same owner as the one that created the NPC.
226 // OS_NPC_NOT_OWNED will create an 'unowned' NPC that will respond to any script that has OSSL permissions to call osNpc* commands.
227 integer NPCOptions = OS_NPC_CREATOR_OWNED; // only yhe owner of this box can control this NPC.
228
229 integer walkstate = 0; // helps us reshare the walk state for run, fly and land - a bit of a hack, but it saves RAM. Has to be done this way because some bits of NPCWalkOption are asserted as 0
230
231 integer NPCWalkOption; // Some notes for what happens to NPCWalkOption:
232 // OS_NPC_FLY - Fly the avatar to the given position. The avatar will not land unless the OS_NPC_LAND_AT_TARGET option is also given.
233 // OS_NPC_NO_FLY - Do not fly to the target. The NPC will attempt to walk to the location. If it's up in the air then the avatar will keep bouncing hopeless until another move target is given or the move is stopped
234 //OS_NPC_LAND_AT_TARGET - If given and the avatar is flying, then it will land when it reaches the target. If OS_NPC_NO_FLY is given then this option has no effect.
235 // OS_NPC_RUNNING - if given, NPC avatar moves at running/fast flying speed, otherwise moves at walking/slow flying speed.
236
237 // menus
238 string mSensor="Sense is Off"; // Sensor or "No Sensor"
239
240 list lAtButtons = ["Menu","-", ">>", "@run", "@walk", "@fly", "@land", "@wander", "@sit", "@stand","@animate","@rotate"];
241 list lMenu2 = ["<<", "@comment", ">>>", "@stop", "@say", "@whisper","@shout","@sound","@randsound","@cmd", "@pause", "@delete"];
242 list lMenu3 = ["<<<","@notecard","@appearance", "@touch", "@speed", "@attach", "@teleport","-", "-", "-", "-", "-" ];
243
244 string sCommand; // place to store a command for two-prompted ones
245 string sParam2; // place to store a prompt for two-prompted ones
246 string priPub = "Owner Only"; // Private or Group
247 key kUserKey; // the person who is controlling the avatar, not the Owner
248 // the command lists
249 list lCommands; // commands are stored here
250 list lNpcCommandList; // Storage for the NPC script.
251 string npcAction; // Storage for the next action. @cmd=0|hello, this becomes @cmd
252 string npcParams; // Storage for the param, @cmd=0|hello, this becomes 0|hello
253
254 // misc vars
255 string sNotecard; // commands are stored here temporarily for dumping
256 vector vWanderPos; // a place to wander to
257 string lastANIM ; // last animation run
258 // Sensor
259 integer avatarPresent; // Sensor sets this flag when people are within Range.
260
261 // Coordinate control
262 vector vInitialPos ; // Vector that will be filled by the script with the initial starting position in region coordinates.
263 vector vDestPos = ZERO_VECTOR; // Storage for destination position.
264 string relAbs = "Relative"; // absolute vs relative positioning
265
266
267 // STATES
268 integer MENU ; // processing a dialog box state, may be concurrent with STATE
269 integer STATE; // state storage
270 integer NULL = 0; // the null state
271 integer MakeNotecard = 1; // displaying a text box for NPC name
272 integer RecordPath = 2; // displaying a path notecard menu
273 integer NobodyHome = 3; // looking for an avatar
274 integer Spawning = 4; // spawning an avatar
275 integer Animate = 5; // animation timer needed
276 integer Walking = 6; // Hey! I am walking here!
277 integer Wander = 7; // Wandering around neeeds a timer, too
278 integer WanderHold = 8; // We reached a wander point
279 integer DoProcess = 9; // Set this to make it process a new command
280 integer Touch = 10; // Timer is busy sensing something to touch
281 integer Sit = 11; // Timer is busy sensing something to sit on
282 integer Paused = 12; // Timer is busy pausing
283
284 key gNpcKey = NULL_KEY; // global key storage for the one NPC, to save CPU cycles
285 list Stack ; // a command stack from link message input
286
287 ///////////////////////////////////////////////////////////////////////////
288 // FUNCTIONS //
289 ///////////////////////////////////////////////////////////////////////////
290
291
292 TimerEvent(float timesent)
293 {
294 if(LSLEditor)
295 timesent *= 5; // slow thinggs doen when the LSLEDITOR is in use
296
297 DEBUG("Setting timer: " + (string) timesent);
298 llSetTimerEvent(timesent);
299 }
300
301 // for 4.1 parse a message from a Listen or a Link message
302 ParseMsg(string str) {
303 DEBUG("Command In:" + str);
304 if(str=="@go") {
305 SetStop(FALSE); // Let's run the notecard
306 DEBUG("@go running");
307 DoProcessNPCLine();
308 } else {
309 Stack += [str]; // take anything, the controller will filter away non @ stuff
310 if(STATE == NULL)
311 DoProcessNPCLine(); // v 4.5 remove wait for STATE == 0
312 }
313 }
314
315 SetStop(integer what)
316 {
317 DEBUG("Stopped set to " + (string ) what);
318 Stopped = what;
319 }
320 // Do* functions are much like states from the old V2 scripts.
321
322 // Save a Path notecard
323 DoSave()
324 {
325 STATE = MakeNotecard;
326 makeText("Stand where you want the NPC to appear, and enter the NPC Name");
327 }
328
329 // This function is used to record the path for the NPC
330 // Each command can take 0, 1, or 2 params
331 DoMenuForCommands() {
332 makeMenu(lAtButtons);
333 }
334
335
336 // No one is here when sensors were on, so we kill off the NPC
337 DoNobodyHome()
338 {
339 DEBUG("Nobody Home");
340 STATE = NobodyHome;
341 if(NPCKey() != NULL_KEY) {
342 osNpcRemove(NPCKey());
343 SaveKey(NULL_KEY);
344 }
345 TimerEvent(5); // keep ticking to sense avatars
346 }
347
348
349 /////////////////////// STATELIKE BEHAVIOUR /////////////
350 // these StateXX functions need to wait on a timer to fire.
351
352 // Create a NPC
353 StateSpawn() {
354 DEBUG("state spawn " + sNPCName);
355
356 NPCEnabled = TRUE; // in world
357 // see if there is already one out there.
358 if(NPCKey() != NULL_KEY) {
359 DEBUG("Already living");
360 return;
361 }
362
363 STATE = Spawning;
364 list name = llParseString2List(sNPCName, [" "], []);
365
366 vector vRezPos = vInitialPos;
367 if(relAbs == "Relative"){
368 vRezPos += llGetPos();
369 }
370
371 // llSay(0,llDumpList2String(name,","));
372
373 DEBUG("Rezzing NPC name " +llList2String(name, 0)+ llList2String(name, 1) + " at "+ (string) vRezPos);
374 key aKey = osNpcCreate(llList2String(name, 0), llList2String(name, 1), vRezPos, Appearance, NPCOptions);
375
376 llMessageLinked(LINK_SET,-10,(string) llGetLinkNumber() + "|" + (string) llGetPos() ,aKey); // broadcast the key on num = -1
377 SaveKey(aKey); // save in description and global, too
378
379 osSetSpeed(aKey,SPEEDMULT); // 1.9 speed multiplier
380 TimerEvent(REZTIME);
381 NPCAnimate(STAND);
382 }
383
384 StateSit() {
385 DEBUG ("state sit - looking for " + RAMsit);
386 STATE=Sit;
387 llSensor(RAMsit, "", PASSIVE|ACTIVE|SCRIPTED, 96, PI);
388 }
389
390 StateTouch() {
391 DEBUG ("state touch - looking for " + RAMtouch);
392 STATE = Touch;
393 llSensor(RAMtouch, "", PASSIVE|ACTIVE|SCRIPTED, 96, PI);
394 }
395
396 DoStand() {
397 DEBUG("state stand");
398 osNpcStand(NPCKey());
399 }
400
401
402 StateAnimate() {
403
404 DEBUG("state animate");
405 STATE = Animate;
406 NPCAnimate(RAManimationName);
407 if(RAManimationTime <=0 ) // V 3.4 tweak
408 RAManimationTime = 1;
409 TimerEvent(RAManimationTime);
410 }
411
412 StateWalk() {
413
414 DEBUG("Start Walk");
415 //DEBUG("NPCWalkOption = " + (string) NPCWalkOption);
416 STATE = Walking;
417
418 // walk, fly, run, land
419 if(walkstate == 1) {
420 NPCAnimate(WALK);
421 } else if(walkstate == 2) {
422 llShout(FLIGHT,"flying");
423 NPCAnimate(FLY);
424 } else if(walkstate == 3) {
425 NPCAnimate(RUN);
426 } else if(walkstate == 4) {
427 NPCAnimate(LAND);
428 }
429 newDest = vDestPos ;
430 newDest.z += OffsetZ;
431
432 // notecard is stored as offsets from this box with relative addressing. Convert to absolute
433 if(relAbs == "Relative"){
434 newDest += llGetPos();
435 }
436
437 DEBUG("Moveto:" + (string) newDest);
438 osNpcMoveToTarget(NPCKey(), newDest, NPCWalkOption);
439 iWaitCounter = WAIT; // wait 60 seconds to get to a destination.
440 TimerEvent(TIMER);
441 }
442
443
444 StateWander(){
445 DEBUG("state wander");
446 STATE = Wander;
447
448 vector point = CirclePoint(RAMwd);
449 DEBUG("CirclePoint:" + (string) point);
450 vWanderPos = vDestPos + point;
451 DEBUG("vWanderPos:" + (string) vWanderPos);
452
453 fTimerVal = WANDERTIME; // default time to pause after each wander
454 if(WANDERRAND)
455 fTimerVal = llFrand(WANDERTIME) + 1; // override, they want random times
456
457 NPCAnimate(WALK);
458
459 DEBUG("Wander to:" + (string) vWanderPos);
460
461 osNpcMoveToTarget(NPCKey(), vWanderPos, NPCWalkOption);
462 iWaitCounter = WAIT; // wait 60 seconds to get to a destination.
463 TimerEvent(TIMER);
464 }
465
466 StateWanderhold() {
467
468 DEBUG("Wander Hold");
469 STATE = WanderHold;
470
471 // now that we have reached a wander spot, slow the timer down to the desired value
472 TimerEvent(fTimerVal);
473 }
474
475
476
477 DoRotate() {
478 DEBUG("@rotate=" + (string) RAMrot);
479 osNpcSetRot(NPCKey(), llEuler2Rot(<0,0,RAMrot> * DEG_TO_RAD));
480 }
481
482
483
484 // @pause=10 will do nothing for 10 seconds
485 DoPause() {
486 STATE = Paused;
487 if(RAMPause < 0.1)
488 RAMPause = 0.1;
489 DEBUG("@pause=" + (string)RAMPause);
490 TimerEvent(RAMPause);
491 }
492
493
494 // @stop makes the NPC stop moving in whatever state it is in. You have to linkmessage to get moving again
495 DoStop() {
496 DEBUG("NPC is Stopped");
497 STATE = 0; // accept commands
498 SetStop(TRUE); // Link controlled - we mnust have a @go to continue with notecards
499 TimerEvent(0);
500 Stack = []; // v3.8
501 }
502
503 // @delete removes the NPC forever. Next command starts it up again at the beginning
504 DoDelete() {
505 DEBUG("state delete");
506 osNpcRemove(NPCKey());
507 SaveKey(NULL_KEY);
508 TimerEvent(0);
509 Stack = []; // v3.8
510 STATE = NULL; // accept commands
511 }
512
513 // change the appearance of the NPC
514 DoAppearance(string notecard) {
515 DEBUG("state appearance");
517 DEBUG("Load appearance " + notecard);
518 osNpcLoadAppearance(NPCKey(),notecard);
519 }
520 STATE = NULL; // accept commands
521 }
522
523 // Change the avatar speed
524 DoSpeed(string speed) {
525 float newspeed = (float) speed;
526 if(newspeed > 0.1 && newspeed < 5.0) {// sanity check
527 osSetSpeed(NPCKey(),newspeed);
528 }
529 STATE = NULL; // accept commands
530 }
531
532 DoTeleport(string params) {
533 list Data = llParseString2List(params, ["|"], []);
534 string itemName = llList2String(Data, 0);
535 vector Dest = (vector) itemName;
536 if(Dest != ZERO_VECTOR) {
537 if(relAbs == "Relative"){
538 Dest += llGetPos();
539 }
540 osTeleportAgent( NPCKey(), llGetRegionName(), Dest, ZERO_VECTOR );
541
542 } else {
543 llSay(DEBUG_CHANNEL,"Attempt to teleport to <0,0,0> probably not what you intended: @teleport=<vector>");
544 }
545 STATE = NULL; // accept commands
546 }
547
548
549
550 DoNewNote (string card) {
551 DEBUG("Load Notecard " + card);
552 NPCReadNoteCard(card);
553 SetStop(FALSE);
554 STATE = NULL; // accept commands
555 }
556 DoAttach(string params) {
557
558 list Data = llParseString2List(params, ["|"], []);
559 string itemName = llList2String(Data, 0);
560 integer attachmentPoint = (integer) llList2String(Data, 1);
561 if(attachmentPoint > 0
562 && attachmentPoint < 40
564 )
565 {
566 osForceAttachToOtherAvatarFromInventory(NPCKey(),itemName,attachmentPoint);
567 }
568 STATE = NULL; // accept commands
569 }
570
571 // This loops over the notecard, processing each command
572 DoProcessNPCLine() {
573 DEBUG("ProcessNPCLine, stopped = " + (string) Stopped);
574
575 STATE = DoProcess;
576
577 // auto load a notecard
578 if(! llGetListLength(lNpcCommandList)) {
579 DEBUG("Read Notecard");
580 NPCReadNoteCard(Notecard);
581 }
582
583 // look for link messages on the stack
584 string next = llList2String(Stack,0); // lets see if there is anything from a link message
585 if(llStringLength(next))
586 {
587 Stack = llDeleteSubList(Stack,0,0);
588 ProcessCmd(next); //lets do this command instead.
589 return;
590 }
591
592 // @stop issued?
593 if(Stopped) {
594 TimerEvent(0);
595 DEBUG("Stopped, waiting for input");
596 STATE = NULL;
597 return;
598 }
599
600 // No, we have an @go for liftoff
601 next = llList2String(lNpcCommandList, 0); // get the next command
602 DEBUG("Execute:" + next);
603 lNpcCommandList = llDeleteSubList(lNpcCommandList, 0, 0); // delete it
604
605 if(llGetListLength(lNpcCommandList) == 0) {
606 DEBUG("EOF");
607 }
608 ProcessCmd(next);
609 }
610
611
612
613 ProcessCmd(string cmd) {
614
615 DEBUG("ProcessCmd:" + cmd);
616
617 if(llGetSubString(cmd, 0, 0) != "@") {
618 DEBUG("ignoring");
619 TimerEvent(QUICK); // this is so we do not recurse the stack
620 STATE = NULL;
621 return;
622 }
623
624 list data = llParseString2List(cmd, ["="], []);
625 npcAction = llToLower(llStringTrim(llList2String(data, 0), STRING_TRIM));
626
627 DEBUG("Action:" + npcAction);
628 npcParams = llStringTrim(llList2String(data, 1), STRING_TRIM);
629 DEBUG("Params:" + npcParams);
630
631 @commands;
632
633 ProcessSensor();
634
635 if(npcAction == "@spawn") {
636 DEBUG("@spawn npcParams ");
637 list spawnData = llParseString2List(npcParams, ["|"], []);
638 sNPCName =llList2String(spawnData, 0); // V 1.6 name in RAM
639
640 vInitialPos = (vector) llList2String(spawnData, 1);
641 DEBUG("Coords for NPC at " + (string) vInitialPos);
642 StateSpawn();
643 return;
644 }
645
646 if(! avatarPresent){
647 DoNobodyHome();
648 DEBUG("No avatar nearby");
649 STATE = NULL;
650 return;
651 } else {
652 if( NPCKey() == NULL_KEY) {
653 StateSpawn();
654 }
655 }
656
657
658
659
660 if(npcAction == "@stop") {
661 DoStop();
662 STATE = NULL;
663 return;
664 }
665 else if(npcAction == "@goto") {
666 DEBUG("goto");
667 integer lastIdx = llGetListLength(lNpcCommandList)-1;
668 lNpcCommandList = llDeleteSubList(lNpcCommandList, lastIdx, lastIdx);
669 // Wind commands till goto label.
670 @wind;
671 string next1 = llList2String(lNpcCommandList, 0);
672 lNpcCommandList = llDeleteSubList(lNpcCommandList, 0, 0);
673 lNpcCommandList += next1;
674 if(next1 != npcParams) jump wind;
675 // Wind the label too.
676 next1 = llList2String(lNpcCommandList, 0);
677 lNpcCommandList = llDeleteSubList(lNpcCommandList, 0, 0);
678 lNpcCommandList += next1;
679 // Get next command.
680 list data1 = llParseString2List(next1, ["="], []);
681 npcAction = llToLower(llStringTrim(llList2String(data1, 0), STRING_TRIM));
682 npcParams = llStringTrim(llList2String(data1, 1), STRING_TRIM);
683 // Reschedule.
684 jump commands;
685 }
686 else if(npcAction == "@sound") {
687 DEBUG("sound");
688 llTriggerSound(npcParams,1.0);
689 }
690 else if(npcAction == "@randsound") {
691 DEBUG("@randsound");
693 integer rand = llCeil(llFrand(N)) -1; // pick a random sound
695 llTriggerSound(toPlay,1.0);
696 }
697 else if(npcAction == "@walk") {
698 DEBUG("@walk");
699 GetDest(npcParams);
700 walkstate = 1;// walking
701 NPCWalkOption = OS_NPC_NO_FLY ;
702 StateWalk();
703 return;
704 }
705 else if(npcAction == "@fly") {
706 GetDest(npcParams);
707 walkstate = 2;// flying
708 NPCWalkOption = OS_NPC_FLY ;
709 StateWalk();
710 return;
711 }
712 else if(npcAction == "@run") {
713 DEBUG("@run");
714 GetDest(npcParams);
715 walkstate = 3;// running
716 NPCWalkOption = OS_NPC_NO_FLY | OS_NPC_RUNNING;
717 StateWalk();
718 return;
719 }
720 else if(npcAction == "@land") {
721 DEBUG("@land");
722 GetDest(npcParams);
723 walkstate = 4;// landing
724 NPCWalkOption= OS_NPC_FLY | OS_NPC_LAND_AT_TARGET ;
725 StateWalk();
726 return;
727 }
728 else if(npcAction == "@say") {
729 DEBUG("@say " + npcParams);
730 osNpcSay(NPCKey(), 0, npcParams);
731 }
732 else if(npcAction == "@shout") {
733 DEBUG("@shout");
734 osNpcShout(NPCKey(),0, npcParams);
735 }
736 else if(npcAction == "@whisper") {
737 DEBUG("@whisper " + npcParams);
738 osNpcWhisper(NPCKey(),0, npcParams);
739 }
740 // speak a command on a channel, so you can open doors and control stuff.
741 else if(npcAction == "@cmd") {
742 DEBUG("@cmd");
743 list dataToSpeak = llParseString2List(npcParams, ["|"], []);
744 string channel = llList2String(dataToSpeak,0);
745 DEBUG("Channel:"+(string) channel);
746 integer iChannel = (integer) channel;
747 string stringToSpeak = llList2String(dataToSpeak,1);
748 llSay(iChannel, stringToSpeak);
749 }
750 // stop everything
751 else if(npcAction == "@pause") {
752 RAMPause = (float) npcParams;
753 DoPause();
754 return;
755 }
756 else if(npcAction == "@wander") {
757 list wanderData = llParseString2List(npcParams, ["|"], []);
758 RAMwd = (float) llList2String(wanderData, 0);
759 RAMwc = (integer) llList2String(wanderData, 1);
760 vDestPos = osNpcGetPos(NPCKey()); // set the wander start
761 DEBUG("Starting at " + (string) vDestPos);
762 StateWander();
763 return;
764 }
765 else if(npcAction == "@rotate") {
766 RAMrot = (float) npcParams;
767 DoRotate();
768 }
769 else if(npcAction == "@sit") {
770 RAMsit= npcParams;
771 StateSit();
772 return;
773 }
774 else if(npcAction == "@touch") {
775 RAMtouch= npcParams;
776 StateTouch();
777 return;
778 }
779 else if(npcAction == "@stand") {
780 DoStand();
781 }
782 else if(npcAction == "@delete") {
783 DoDelete();
784 SetStop(TRUE); // Link controlled - we mnust have a @go to continue with notecards
785 return;
786 }
787 else if(npcAction == "@animate") {
788 list animateData = llParseString2List(npcParams, ["|"], []);
789 RAManimationName = llList2String(animateData, 0);
790 RAManimationTime = (float) llList2String(animateData, 1);
791 StateAnimate();
792 return;
793 }
794 else if(npcAction == "@appearance" ){
795 DoAppearance(npcParams);
796 }
797 else if(npcAction =="@speed") {
798 DoSpeed(npcParams);
799 }
800 else if(npcAction =="@notecard") {
801 DoNewNote(npcParams);
802 Notecard = npcParams;
803 }
804 else if(npcAction == "@attach")
805 {
806 DoAttach(npcParams);
807 }
808 else if(npcAction == "@teleport")
809 {
810 DoTeleport(npcParams);
811 }
812 else if(npcAction == "@reset")
813 {
814 DoDelete();
815 SetStop(FALSE); // a @resst will restart the original !Path after deleting the notecard.
816 }
817
818 STATE = NULL;
819 TimerEvent(QUICK); // yeah I know, not possible this fast, we just go as fast as we can go - this is so we do not recurse the stack
820 }
821
822
823
824 /////////////////// UTILITY Functions, not state-like //////////////////
825
826 // DEBUG(string) will chat a string or display it as hovertext if debug == TRUE
827 DEBUG(string str) {
828 if(debug && ! LSLEditor)
829 llOwnerSay( str); // Send the owner debug info
830 if(debug && LSLEditor)
831 llSay(0, str); // Send to the Console in LSLEDitor
832 if(iTitleText) {
833 llSetText(str,<1.0,1.0,1.0>,1.0); // show hovertext
834
835 }
836 }
837
838 GetDest(string npcParams) {
839 list dest = llParseString2List(npcParams, ["<", ",", ">"], []);
840 vDestPos.x = llList2Float(dest, 0);
841 vDestPos.y = llList2Float(dest, 1);
842 vDestPos.z = llList2Float(dest, 2);
843 }
844
845 NPCReadNoteCard(string Note) {
846 DEBUG("NPCReadNoteCard");
847 lNpcCommandList = llParseString2List(osGetNotecard(Note), ["\n"], []);
848 }
849
850 integer SenseAvatar()
851 {
852 //Returns a strided list of the UUID, position, and name of each avatar in the region
853 list avatars = llGetAgentList(AGENT_LIST_REGION ,[]);
854 integer numOfAvatars = llGetListLength(avatars);
855 if(numOfAvatars == 0)
856 {
857 DEBUG("No people");
858 return 0;
859 }
860 //DEBUG("Located " + (string)numOfAvatars + " avatars and NPC's");
861
862 integer nAvatars;
863 integer i;
864 for( i = 0;i < numOfAvatars; i++) {
865 key aviKey = llList2Key(avatars,i);
866 if(!osIsNpc(aviKey)) {
867 list detail = llGetObjectDetails(aviKey,[OBJECT_POS]);
868 vector pos = llList2Vector(detail,0);
869 float dist = llVecDist(pos, llGetPos());
870 if(dist < RANGE)
871 {
872 nAvatars++;
873 DEBUG("In range:" + llKey2Name(aviKey));
874 }
875 }
876 }
877 //DEBUG("Located " + (string) nAvatars + " avatars");
878 return nAvatars;
879 }
880
881 // return TRUE if the avatar is owner when private is set, or TRUE if the avatar is in the same group and GROUP is set.
882 integer checkPerms() {
883
884 integer group = (integer) KeyValueGet("pr");
885 if(! group)
886 priPub = "Owner Only";
887 else
888 priPub = "Group";
889
890
892 kUserKey = llDetectedKey(0);
893 return TRUE;
894 }
895
896 if( group && llDetectedGroup(0)) {
897 kUserKey = llDetectedKey(0);
898 return TRUE;
899 }
900 kUserKey = llDetectedKey(0);
901 return FALSE;
902 }
903
904
905
906 NPCAnimate(string anim)
907 {
908 DEBUG("Start Anim: " + anim);
910
911 if(lastANIM != anim) {
912 if(llStringLength(lastANIM)) {
913 osNpcStopAnimation(NPCKey(), lastANIM);
914 }
915 osNpcPlayAnimation(NPCKey(), anim);
916 lastANIM = anim;
917 }
918 } else {
919 llSay(DEBUG_CHANNEL, "No animation named " + anim);
920 }
921 }
922
923 // Kill a NPC by Name
924 Kill(string param)
925 {
926 integer count;
927 list avatars = osGetAvatarList(); // Returns a strided list of the UUID, position, and name of each avatar in the region except the owner.\
928 integer i;
929 integer j = llGetListLength(avatars);
930 for (i=0 ; i <= j; i+=3){
931
932 string desired = llList2String(avatars,i+2);
933 desired = llStringTrim(desired,STRING_TRIM); // should not be needed but is needed
934
935 if(desired == param){
936 vector v = llList2Vector(avatars,i+1);
937 key target = llList2Key(avatars,i); // get the UUID of the avatar
938 osNpcRemove(target);
939
940 llOwnerSay("Removed " + param+ " at location " + (string) v);
941 count++;
942 }
943 }
944
945 NPCEnabled = FALSE; // not in world
946 SaveKey(NULL_KEY ); // Rev 4.4
947
948 if(count)
949 llOwnerSay("Removed " + (string) count + " NPC's");
950 else
951 llOwnerSay("Could not locate " + param);
952 }
953
954
955 // return a String for the position we are at. Strings used as the caller wants strings
956 string Pos()
957 {
958 vector where = llGetPos(); // find the box position
959
960 where.z += OffsetZ; // use the ground position + an offset
961
962 if(LSLEditor)
963 where = <128,128,31 + llFrand(1)>; // force center of sim when editing
964
965 // if attached the height will be too high by 1/2 the agent size
966 if(llGetAttached()) {
968 float Z = size.z;
969 where.z -= Z/2;
970 }
971
972 // DEBUG("Pos= " + (string) where);
973 return (string) where;
974 }
975
976 // setup a menu with a timer for timeouts, called by all make*()
977 menu()
978 {
979 llListenRemove(iHandle);
980 iChannel = llCeil(llFrand(100000) + 20000);
981 iHandle = llListen(iChannel,"","","");
982 TimerEvent(30.0);
983 MENU = TRUE;
984 }
985
986 // make a text box
987 makeText(string Param)
988 {
989 menu();
990 llTextBox(kUserKey, Param, iChannel);
991 }
992
993 // top level menu
994 makeMainMenu()
995 {
996 menu();
997 list buttons = ["Appearance","Recording","Save","Help","-","Erase RAM", priPub,relAbs,"-","Stop NPC",mSensor,"Start NPC"];
998 llDialog(kUserKey,(string) llGetListLength(lCommands) + " Records",buttons,iChannel);
999 }
1000
1001
1002 // Rev 1.4
1003 // top level menu for non group/ non owners
1004 makeUserMenu()
1005 {
1006 if(!allowUsers) return;
1007
1008 menu();
1009 list buttons = ["Start NPC","Stop NPC"];
1010 llDialog(kUserKey,"Choose",buttons,iChannel);
1011 }
1012
1013
1014
1015 // programmable menu for @commands
1016 makeMenu(list buttons)
1017 {
1018 menu();
1019 llDialog(kUserKey,(string) llGetListLength(lCommands) + " Record",buttons,iChannel);
1020 }
1021
1022
1023 // make one or two text boxes with prompts
1024 Text(string cmd, string p1, string p2)
1025 {
1026 sCommand = cmd;
1027 sParam2 = "";
1028 if(llStringLength(p2))
1029 sParam2 = p2;
1030
1031 makeText(p1);
1032 }
1033
1034 // Set the Avatar Present flag - if sensors are off and we are force run, there will be one present.
1035 ProcessSensor()
1036 {
1037 integer SensorOn;
1038 if("on" == KeyValueGet("se"))
1039 {
1040 SensorOn = TRUE; // we need to scan for avatars
1041 } else {
1042 SensorOn = FALSE; // we need to scan for avatars
1043 }
1044 DEBUG("Sensor:" + (string) SensorOn);
1045
1046 integer n = SenseAvatar();
1047
1048 DEBUG("Avatars:" + (string) n);
1049 if(SensorOn && n)
1050 avatarPresent = TRUE; // someone is here and we need to tell the system to run
1051 else if(SensorOn && !n)
1052 avatarPresent = FALSE; // someone is not here and we need to tell the system to stop
1053 else { // sensor is off, lete see if there is a NPC. If so, we are ON
1054 // DEBUG("NPCEnabled:" + (string) NPCEnabled);
1055
1056 if(NPCEnabled)
1057 avatarPresent = TRUE;
1058 else
1059 avatarPresent = FALSE;
1060 }
1061
1062 // start up from when when no one is near
1063 if(avatarPresent && STATE == NobodyHome)
1064 STATE = NULL;
1065
1066 DEBUG("Avatar Present: " + (string) avatarPresent);
1067 }
1068
1069 vector CirclePoint(float radius) {
1070 float x = llFrand(radius *2) - radius; // +/- radius, randomized
1071 float y = llFrand(radius *2) - radius; // +/- radius, randomized
1072 return <x, y, 0>; // so this should always happen
1073 }
1074
1075 string KeyValueGet(string var) {
1076 list dVars = llParseString2List(llGetObjectDesc(), ["&"], []);
1077 do {
1078 list data = llParseString2List(llList2String(dVars, 0), ["="], []);
1079 string k = llList2String(data, 0);
1080 if(k != var) jump continue;
1081 //DEBUG("got " + var + " = " + llList2String(data, 1));
1082 return llList2String(data, 1);
1083 @continue;
1084 dVars = llDeleteSubList(dVars, 0, 0);
1085 } while(llGetListLength(dVars));
1086 return "";
1087 }
1088
1089 KeyValueSet(string var, string val) {
1090
1091 //DEBUG("set " + var + " = " + val);
1092 list dVars = llParseString2List(llGetObjectDesc(), ["&"], []);
1093 if(llGetListLength(dVars) == 0)
1094 {
1095 llSetObjectDesc(var + "=" + val);
1096 return;
1097 }
1098 list result = [];
1099 do {
1100 list data = llParseString2List(llList2String(dVars, 0), ["="], []);
1101 string k = llList2String(data, 0);
1102 if(k == "") jump continue;
1103 if(k == var && val == "") jump continue;
1104 if(k == var) {
1105 result += k + "=" + val;
1106 val = "";
1107 jump continue;
1108 }
1109 string v = llList2String(data, 1);
1110 if(v == "") jump continue;
1111 result += k + "=" + v;
1112 @continue;
1113 dVars = llDeleteSubList(dVars, 0, 0);
1114 } while(llGetListLength(dVars));
1115 if(val != "") result += var + "=" + val;
1117 }
1118
1119
1120 // clear RAM
1121 Clr() {
1122
1123 lCommands = [];
1124 llOwnerSay("RAM Memory cleared. Notecards, if any, are not modified.");
1125 makeMainMenu();
1126 }
1127
1128 integer checkNoteCards()
1129 {
1130 // Check that they have saved an Appeaance and Path notecard
1131 integer num = llGetInventoryNumber(INVENTORY_NOTECARD); // how many notecards overall
1132
1133 integer i;
1134 integer count;
1135 for (; i < num; i++){
1137 count++;
1139 count++;
1140 }
1141 DEBUG("Checked " + (string) count + " Notecards");
1142 // if we have both, run the NPC
1143 return count;
1144 }
1145
1146 Update(string SName) {
1147
1148 // delete all NPC* scripts except myself
1149 integer i;
1151 for (i = 0; i < j; i++) {
1153 string match = llGetSubString(targetName,0,2);
1154
1155 if(match == SName && llGetScriptName() != targetName){
1156 llOwnerSay("Upgrading " + targetName);
1157 if(! LSLEditor){ // lets not kill the editor
1158 llRemoveInventory(targetName);
1159 }
1160 }
1161 }
1162 }
1163
1164 // Get all default saved params from the Description
1165 GetSwitches()
1166 {
1167 string rA = KeyValueGet("co"); // Get the remembered menu setting for Abs Vs Relative
1168 if(rA == "A")
1169 relAbs = "Absolute";
1170 else if(rA == "R")
1171 relAbs = "Relative";
1172 else
1173 relAbs = "Absolute";
1174
1175
1176 // reenable NPC if sensor is on.
1177 if("on" == KeyValueGet("se"))
1178 {
1179 NPCEnabled = TRUE;
1180 mSensor = "Sense is On";
1181 ProcessSensor(); // fake 1 avatar to get it rezzed
1182 } else {
1183 mSensor = "Sense is Off";
1184 }
1185 }
1186
1187
1188 SaveKey(key akey)
1189 {
1190 DEBUG("Saving Key of " + (string) akey);
1191 KeyValueSet("key", akey);
1192 if(akey != (key) KeyValueGet("key") )
1193 {
1194 DEBUG("Fatal error, cannot save key");
1195 }
1196 gNpcKey = akey;
1197 }
1198
1199
1200 key NPCKey()
1201 {
1202 key akey = gNpcKey; // from cached copy
1203 // gNpcKey saves a lot of CPU processing by caching the key, if blank we get it from the description
1204 if(gNpcKey == NULL_KEY)
1205 {
1206 //DEBUG("Get DKey");
1207 akey = KeyValueGet("key"); // from Description of the prim
1208 }
1209 // DEBUG("NPC KEY:" + (string) akey);
1210 return akey;
1211 }
1212
1213
1214 /////////////////// CODE BEGINS //////////////////
1215
1216
1217 default
1218 {
1219 changed(integer change) {
1220 if(change & CHANGED_REGION_START) {
1222 }
1223 }
1224
1225 on_rez(integer start_param)
1226 {
1228 }
1229
1230 state_entry() {
1231
1232 llSetText("",<1,1,1>,1.0); // clr all hovertext- we may not be using it.
1233 DoDelete(); // kill any NPC that is out running
1234 Update("NPC"); // If dragged and ropped into a prim with any script named "NPC...", this will replace it.
1235 GetSwitches(); // Get all default saved params from the Description
1236
1237 // 4.1 allow listeners to send us commands
1238 if(allowListener)
1239 llListen(link_Channel,"","","");
1240 TimerEvent(TIMER);
1241 }
1242
1243
1245 { // if touched, make a menu
1246
1247 if(checkPerms()) {
1248 if(RecordPath == STATE) {
1249 makeMenu(lAtButtons);
1250 } else {
1251 makeMainMenu();
1252 }
1253 } else {
1254 makeUserMenu();
1255 }
1256 }
1257
1258 // menu listener
1259 listen(integer iChannel, string name, key id, string message) {
1260
1261 // process @commands that come in via the listener
1262 if(iChannel == link_Channel)
1263 {
1264 ParseMsg(message);
1265 return;
1266 }
1267
1268 if(MENU) {
1269 llListenRemove(iHandle);
1270 MENU = 0; // menu is off
1271 iHandle = 0;
1272 }
1273
1274 if(message == "Stop NPC")
1275 {
1276 lNpcCommandList = []; // force reload of notecard
1277 NPCEnabled = FALSE;
1278 if(NPCKey() != NULL_KEY){
1279 Kill(sNPCName);
1280 sNPCName = "";
1281 } else {
1282 bNPC_STOP = TRUE;
1283 makeText("Enter name of an NPC to stop");
1284 }
1285 }
1286 else if(message == "Menu" ) {
1287 makeMainMenu();
1288 }
1289 else if(message == "Erase RAM"){
1290 Clr();
1291 }
1292 else if(message == "Relative"){
1293 relAbs = "Absolute";
1294 KeyValueSet("co","A"); // remember coordinates = A
1295 Clr();
1296 }
1297 else if(message == "Absolute"){
1298 relAbs = "Relative";
1299 KeyValueSet("co","R"); // remember coordinates = R
1300 Clr();
1301 }
1302 else if(message == "Recording"){
1303 DoMenuForCommands(); // show them the recording menu
1304 }
1305 else if(message == "Owner Only") {
1306 priPub = "Group";
1307 KeyValueSet("pr","1");
1308
1309 llOwnerSay("Group members have control");
1310 makeMainMenu();
1311 }
1312 else if(message == "Group") {
1313 priPub = "Owner Only";
1314 KeyValueSet("pr","0");
1315 llOwnerSay("Only you have control");
1316 makeMainMenu();
1317 }
1318 else if(message == "Sense is On") {
1319 mSensor ="Sense is Off";
1320 KeyValueSet("se", "off");
1321 llOwnerSay(mSensor);
1322 makeMainMenu();
1323 }
1324 else if(message == "Sense is Off") {
1325 mSensor ="Sense is On";
1326 llOwnerSay(mSensor);
1327 KeyValueSet("se", "on");
1328
1329 NPCEnabled = TRUE;
1330
1331 integer count = checkNoteCards();
1332 if(count >= 2) {
1333 DEBUG("Notecards ok, DoProcessNPCLine");
1334 DoProcessNPCLine();
1335 return;
1336 }
1337 if(LSLEditor) {
1338 DoProcessNPCLine();
1339 return;
1340 }
1341
1342 llOwnerSay("You have not saved a recording and/or appearance, so you cannot start a NPC");
1343 makeMainMenu();
1344 }
1345 else if(message == "Appearance") {
1346 llRemoveInventory(Appearance); // delete the notecard
1347 osAgentSaveAppearance(kUserKey,Appearance); // make the ntecard
1348 llOwnerSay("Your outfit has been saved");
1349 makeMainMenu();
1350 }
1351 else if(message == "Save") {
1352 if(llGetListLength(lCommands) == 0) {
1353 llOwnerSay("Nothing recorded, you need to make a recording first");
1354 makeMainMenu();
1355 return;
1356 }
1357 DoSave();
1358 }
1359 else if(message == "Help"){
1360 llLoadURL(kUserKey,"Click to view help","http://www.outworldz.com/opensim/posts/NPC/");
1361 makeMainMenu();
1362 }
1363 else if(message == "Start NPC") {
1364 integer count = checkNoteCards();
1365
1366 NPCEnabled = TRUE;
1367
1368 if(LSLEditor) {
1369 DoProcessNPCLine();
1370 return;
1371 }
1372
1373 if(count >= 2) {
1374 DEBUG("Notecards approved , calling DoProcessNPCLine");
1375 SetStop(FALSE); // Let's run the notecard
1376 DoProcessNPCLine();
1377 return;
1378 }
1379
1380 llOwnerSay("You have not saved a recording or maybe an appearance, so we cannot start a NPC");
1381
1382 }
1383 else if(bNPC_STOP){
1384 bNPC_STOP = FALSE;
1385 Kill(message);
1386 }
1387 else if(message == ">>"){
1388 makeMenu(lMenu2);
1389 }
1390 else if(message == ">>>"){
1391 makeMenu(lMenu3);
1392 }
1393 else if(message == "<<") {
1394 makeMenu(lAtButtons);
1395 }
1396 else if(message == "<<<") {
1397 makeMenu(lMenu2);
1398 }
1399 else if(message == "@comment"){
1400 Text("# ","Enter a comment","");
1401 }
1402 else if(message == "@stop"){
1403 lCommands += "@stop"+ "\n";
1404 makeMenu(lAtButtons);
1405 }
1406 else if(message == "@run"){
1407 lCommands += "@run=" + Pos() + "\n";
1408 llOwnerSay("Recorded position: " + Pos());
1409 makeMenu(lAtButtons);
1410 }
1411 else if(message == "@fly"){
1412 lCommands += "@fly=" + Pos() + "\n";
1413 llOwnerSay("Recorded position: " + Pos());
1414 makeMenu(lAtButtons);
1415 }
1416 else if(message == "@land"){
1417 lCommands += "@land=" + Pos() + "\n";
1418 llOwnerSay("Recorded position: " + Pos());
1419 makeMenu(lAtButtons);
1420 }
1421 else if(message == "@walk") {
1422 lCommands += "@walk=" + Pos() + "\n";
1423 llOwnerSay("Recorded position: " + Pos());
1424 makeMenu(lAtButtons);
1425 }
1426 else if(message == "@stop"){
1427 lCommands += "@stop"+ "\n";
1428 makeMenu(lAtButtons);
1429 }
1430 else if(message == "@sound"){
1431 Text("@sound=","Enter a sound name or UUID to trigger","");
1432 }
1433 else if(message == "@randsound"){
1434 lCommands += "@randsound"+ "\n";
1435 makeMenu(lAtButtons);
1436 }
1437 else if(message == "@say") {
1438 Text("@say=","Enter what the NPC will say","");
1439 }
1440 else if(message == "@whisper"){
1441 Text("@whisper=","Enter what the NPC will whisper","");
1442 }
1443 else if(message == "@shout"){
1444 Text("@shout=","Enter what the NPC will shout","");
1445 }
1446 else if(message == "@wander") {
1447 Text("@wander=","Enter radius to wander","Enter number of wanders");
1448 }
1449 else if(message == "@pause") {
1450 Text("@pause=","Enter time to pause","");
1451 }
1452 else if(message == "@rotate") {
1453 Text("@rotate=","Enter degrees to rotate","");
1454 }
1455 else if(message == "@sit"){
1456 Text("@sit=","Enter name of object to sit on","");
1457 }
1458 else if(message == "@teleport"){
1459 lCommands += "@teleport=" + Pos() + "\n";
1460 llOwnerSay("teleport to position: " + Pos());
1461 makeMenu(lMenu3);
1462 }
1463 else if(message == "@touch"){
1464 Text("@touch=","Enter name of object to touch","");
1465 }
1466 else if(message == "@cmd"){
1467 Text("@cmd=","Enter cjhannel to speak on","Enter text to speak");
1468 }
1469 else if(message == "@stand"){
1470 lCommands += "@stand\n";
1471 llOwnerSay("Stand Recorded");
1472 makeMenu(lAtButtons);
1473 }
1474 else if(message == "@animate"){
1475 Text("@animate=","Enter animation name to play","Enter time to play the animation");
1476 }
1477 else if(message == "@attach"){
1478 Text("@animate=","Enter inventory name to attach","Enter number of the attachment point (1-40)");
1479 }
1480 else if(message == "@speed"){
1481 Text("@speed=","Enter a speed for the NPC, 1=100% normal speed, 0.5=50% speed","");
1482 }
1483
1484
1485 // Save NPC name
1486 else if(MakeNotecard == STATE) {
1487 sNPCName = message; // in case we need to kill it.
1488
1489 vector vDest = (vector) Pos();
1490
1491 if(relAbs == "Relative")
1492 {
1493 vDest -= llGetPos(); // just an offset for relative
1494 }
1495 sNotecard = "@spawn=" + message + "|" + (string) vDest + "\n";
1496 integer i;
1497 integer j = llGetListLength(lCommands);
1498 for (; i < j; i++){
1499 // get the command to save to the notecard
1500 string line = llList2String(lCommands,i);
1501 if(relAbs == "Absolute") {
1502 sNotecard += line; // add the un-modified string to the notecard
1503 } else {
1504 // since we have to record absolute coords since we do not know where the box goes until they press Save,
1505 // we process the absolute to relative conversion for walks here
1506 list parts = llParseString2List(line,["="],[]); //get the @command
1507
1508 if(llList2String(parts,0) == "@walk") {
1509 vector vec = (vector) llList2String(parts,1) - llGetPos();
1510 sNotecard += "@walk=" + (string) vec + "\n";
1511 }
1512 else if(llList2String(parts,0) == "@fly") {
1513 vector vec = (vector) llList2String(parts,1) - llGetPos();
1514 sNotecard += "@fly=" + (string) vec + "\n";
1515 }
1516 else if(llList2String(parts,0) == "@run") {
1517 vector vec = (vector) llList2String(parts,1) - llGetPos();
1518 sNotecard += "@run=" + (string) vec + "\n";
1519 }
1520 else if(llList2String(parts,0) == "@land") {
1521 vector vec = (vector) llList2String(parts,1) - llGetPos();
1522 sNotecard += "@land=" + (string) vec + "\n";
1523 }
1524 else {
1525 sNotecard += line; // add the un-modified string to the notecard
1526 }
1527 }
1528 }
1529 llRemoveInventory(Notecard); // delete the old notecard
1530 osMakeNotecard(Notecard,sNotecard); // Makes the notecard.
1531 llSay(0,sNotecard);
1532 llOwnerSay("Commands notecard has been written");
1533 STATE = NULL;
1534 } // MakeNotecard
1535
1536 else if(! llStringLength(sParam2)) {
1537 lCommands += sCommand + message + "\n";
1538 llOwnerSay("Recorded");
1539 makeMenu(lAtButtons);
1540 }
1541 else if(llStringLength(sParam2)){
1542 sCommand = sCommand + message + "|";
1543 llOwnerSay("Recorded");
1544 makeText(sParam2);
1545 sParam2 = "";
1546 }
1547
1548 }
1549
1550
1551
1552 timer(){
1553 // DEBUG("tick");
1554
1555 // if llDialog is up, kill the listener for the dialog box.
1556 if(iHandle) {
1557 llOwnerSay("Menu timed out");
1558 llListenRemove(iHandle);
1559 iHandle = 0;
1560 return; // ^^^^^^^^^^^^^^^^^^^^^^^
1561 }
1562 // if NoBodyHome, we are sensing for an avatar
1563 else if(NobodyHome == STATE) {
1564 ProcessSensor();
1565 return;
1566 }
1567 // if we are spawning, we need time to rez the NPC, then start processing NPC Commands.
1568 else if(Spawning == STATE) {
1569 STATE = NULL;
1570 TimerEvent(TIMER);
1571 }
1572 // We end aniamtions with a timer
1573 else if(Animate == STATE){
1574 NPCAnimate(STAND);
1575 TimerEvent(TIMER);
1576 }
1577
1578 else if(Walking == STATE) {
1579 if(--iWaitCounter) {
1580 DEBUG("still walking...");
1581 if(llVecDist(osNpcGetPos(NPCKey()), newDest) > MAXDIST) {
1582 return;
1583 }
1584 }
1585
1586 DEBUG("At Destination: " + (string) newDest);
1587
1588 // walk, fly, run, land
1589 if(walkstate == 1) {
1590 NPCAnimate(STAND);
1591 NPCWalkOption = OS_NPC_NO_FLY;
1592 } else if(walkstate == 2) {
1593 // nothing
1594 } else if(walkstate == 3) {
1595 NPCAnimate(STAND);
1596 NPCWalkOption = OS_NPC_NO_FLY;
1597 } else if(walkstate == 4) {
1598 llShout(FLIGHT,"landing");
1599 NPCAnimate(STAND);
1600 NPCWalkOption = OS_NPC_NO_FLY;
1601 }
1602 }
1603 // Wandering timer
1604 else if(Wander == STATE) {
1605 if(--iWaitCounter) { // wait 60 seconds to get to a destination.
1606 if(llVecDist(osNpcGetPos(NPCKey()), vWanderPos) > MAXDIST)
1607 return;
1608 }
1609
1610 // see if wander counter == 0, if so, stop walking, go to stand and process next line
1611 if(RAMwc == 0) {
1612 NPCAnimate(STAND);
1613 DEBUG("Wander ended, calling DoProcessNPCLine");
1614 STATE = NULL;
1615 return;
1616 }
1617 // one less time to wander around
1618 RAMwc--;
1619 NPCAnimate(STAND);
1620 TimerEvent(TIMER);
1621 StateWanderhold();
1622 return;
1623 }
1624 // Wandering requires us to re-wander when we reach a destination
1625 else if(WanderHold == STATE) {
1626 StateWander();
1627 }
1628 else if(DoProcess == STATE) {
1629 TimerEvent(QUICK);
1630 }
1631
1632 STATE = NULL;
1633
1634 // We always process a NPC line at end of timer.
1635 DEBUG("Tick end, calling DoProcessNPCLine");
1636 DoProcessNPCLine();
1637 }
1638
1639 // sensors are used for sitting on prims
1640 // Neo Cortex: added different states to trigger sit or touch
1641 sensor(integer num) {
1642 if(Sit == STATE ) {
1643 osNpcSit(NPCKey(), llDetectedKey(0), OS_NPC_SIT_NOW);
1644 DEBUG("Seated, calling DoProcessNPCLine");
1645
1646 STATE = 0;
1647 } else if(Touch == STATE) {
1648 osNpcTouch(NPCKey(), llDetectedKey(0), LINK_THIS);
1649 DEBUG("Touched, calling DoProcessNPCLine");
1650 STATE = 0;
1651 }
1652 DoProcessNPCLine();
1653 }
1654 no_sensor(){
1655 DEBUG ("no target prim located, calling DoProcessNPCLine");
1656 DoProcessNPCLine();
1657 STATE = NULL;
1658 }
1659
1660
1661 link_message(integer sender, integer num, string str, key id){
1662
1663 if(num == 0) {
1664 ParseMsg(str);
1665 llMessageLinked(LINK_SET,-10,(string) llGetLinkNumber() + "|" + (string) llGetPos() ,NPCKey()); // broadcast the key on num = -1
1666 }
1667 if(num == 99)
1669 }
1670
1671 }
1672
1673 // __ END__

HyperGrid Story Nine

NPC helmet controller
Accepts a single link message to make the cyber being helmet flash for a second or two.
worn by the NPC

Category: NPC
By : Shin Ingen
Created: 2015-11-24 Edited: 2015-11-23
Worlds: Second Life

1
2 // TUNABLES
3 integer debug = 0;
4
5
6
7
8 // GLOBALS
9 integer counter = 0;
10
11
12 // FUNCTIONS
13 DEBUG(string msg)
14 {
15 if(debug & 1)
16 llSay(0,llGetScriptName() + ":" + msg);
17 if(debug & 2)
18 llSetText(msg, <1,0,0>,1.0);
19 }
20 NpcSaySomething(){
21
22 list sayings = ["The helmet begins to glow","A beam of light appears","The helmet begins to hum","The cyberbeing helmet is activated", "bzzz", "Zap","KaPow!"];
23 key NpcKey = llGetOwner();
24 DEBUG("Owner Key = " + (string) NpcKey);
25
26 llSay(0, llList2String(sayings,counter++));
27 if(counter > llGetListLength(sayings)) {
28 counter = 0;
29 }
30 }
31
32 Go()
33 {
34 NpcSaySomething();
35 llSetLinkPrimitiveParamsFast(llGetLinkNumber(),[PRIM_GLOW,ALL_SIDES,.2, PRIM_SIZE, <2,2,2>,PRIM_COLOR,ALL_SIDES,<1,1,1>,0.5 ]);
37
38 }
39
40 integer Helmet_Channel = 576;
41
42 default
43 {
45 {
47 }
48
49 changed(integer what)
50 {
51 if(what & CHANGED_REGION_START)
52 {
54 }
55 }
56
57
59 {
60 llSetText("", <1,0,0>,1.0);
61 llListenRemove(listener);
62
63 llSetTextureAnim(ANIM_ON | SMOOTH | LOOP , ALL_SIDES, 1, 1, 1.0, 1.0, 1.0);
64 }
65
66 link_message(integer total_number, integer Num, string text, key id)
67 {
68 if( text =="BOOM")
69 Go();
70 }
71
72
73 timer()
74 {
75 llSetLinkPrimitiveParamsFast(llGetLinkNumber(),[PRIM_GLOW,ALL_SIDES,0.2, PRIM_SIZE, <1.5,1.5,1> ] );
76 llSleep(0.25);
77 llSetLinkPrimitiveParamsFast(llGetLinkNumber(),[PRIM_GLOW,ALL_SIDES,0.2, PRIM_SIZE, <1.2,1.2,1> ] );
78
79 llSetLinkPrimitiveParamsFast(llGetLinkNumber(),[PRIM_GLOW,ALL_SIDES,0, PRIM_SIZE, <1,1,1> ] );
80 llSleep(0.25);
81 llSetLinkPrimitiveParamsFast(llGetLinkNumber(),[PRIM_GLOW,ALL_SIDES,0, PRIM_SIZE, <1,1,1>,PRIM_COLOR,ALL_SIDES,<1,1,1>,0 ] );
83
84 }
85
86 }

HyperGrid Story Nine

All in one NPC recorder player.
Supports both absolute and relative paths and many new commands
Add animations named "Fly, Walk, Stand and Run"
Click Prim to use.
Should be worn as a HUD to record.
Put it on the ground and click Sensor or Start NPC when done.

Category: NPC
By : Shin Ingen
Created: 2015-11-24 Edited: 2015-11-23
Worlds: Second Life

1 // This is Rev 4.5 09/26/2015
2
3 // Revision History
4 // Rev 1.1 10-2-2014 @Sit did not work. Minor tweaks to casting for lslEditor
5 // Rev 1.2 10-14-2014 @ sit had wrong type.
6 // Rev 1.3 relative movement fixed for @fly
7 // Rev 1.4 4-3-2014 allow anyone to use this, non owners and non group members can only start and stop.
8 // Rev 1.5 5-17-2014 set sensor to auto start on reboot of sim
9 // Rev 1.6 5-24-2014 move menu so you can get it by touching, removed many of the KeyValues to RAM for efficiency
10 // Rev 1.7 CHANGED_REGION_START, not CHANGED_REGION_START (Opensim difference)
11 // Rev 1.8 tuned up Kill NPC, added more flexible upgrader
12 // Rev 1.9 Better script injection by link message// Rev 2.0 Added osSetSpeed so you can speed up or slow down an NPC.
13 // Rev 2.1 No laggy sensor used exept to sit on stuff
14 // Rev 2.2 Various sensor fixes
15 // Rev 2.3 Sets No Sensor in menu, must be started by hand
16 // Rev 2.4 - reserved for patches to 2.3 if needed
17 // Rev 3.0 Refactor out into subs, not states to make command injection easier
18 // New command: @appearance=Notecardname so you can switch to a new notecard on the fly
19 // New command: @speed=1.0 which slows up ( < 1 ) or speeds up ( > 1)
20 // Rev 3.1 Commands are not interruptible by Link Message
21 // Rev 3.2 Sensor patches for consistency in removing the NPC
22 // Rev 3.3 Added Touch command by Neo.Cortex@hbase42/hopto/org:8002
23 // Added Menu 3 for notecard and appearance commands
24 // Rev 3.4 animation timer cannot be zero or it shuts off timer tweaked
25 // solves the NPC starting up when no sensor is set.
26 // Rev 3.5 fixes saving to !Path notecard
27 // Rev 3.6 08-11-2015 @delete acts like @stop. TYjhe NPC now rezzes after an @go back in where it was deleted
28 // Rev 3.7 08-11-2015 @attach command added to load an attachment from the inventory to the NPC
29 // Rev 3.8 08-17-2015 process queued commands one at a time without calling ProcessNPCLine on link message
30 // Rev 3.9 08-23-2011 Queued command fixes including @delete which were not always working
31 // Rev 4.0 09-15-2015 Fixes for Sensor functions which continually rezzed a NPC when no one was around.
32 // Rev 4.1 09-20-2015 Added a Listener so link messages are not needed
33 // Rev 4.2 09-23-2015 Added @teleport=<vector>
34 // Rev 4.3 09-24-2015 Added @reset to restart the NPC at the very start of the !Path notecard
35 // @teleport works for relative and absolute modes
36 // Rev 4.4 09-26-2015 if it could not find the (deleted) NPC, it could not restart
37 // Rev 4.5 09-29-2015 remove wait for STATE == 0
38 //*******************************************************************//
39
40 // Instructions on how to use this are at http://www.outworldz.com/opensim/posts/NPC/
41 // This is an OpenSim-only script.
42 // Author: Ferd Frederix aka Fred Beckhusen - fred@mitsi.com
43
44 ////////////////////////////////////////////////////////////////////////////////////////////
45 // Original code was Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 //
46 ///////////////////////////////////////////////////////////////////////////////////////////
47 // Please see: http://www.gnu.org/licenses/gpl.html for legal details, //
48 // rights of fair usage, the disclaimer and warranty conditions. //
49 ///////////////////////////////////////////////////////////////////////////////////////////
50 // The original NPC controller was from http://was.fm/opensim:npc
51 // Extensive additions and bug fixes by Fred Beckhusen, aka Ferd Frederix
52 // llSensor had two params swapped
53 // @Wander would wander where it had rezzed, not where it was.
54 // There was no 'no_sensor' event in sit, so if a @sit failed, the NPC got stuck
55 // The animation and walks always stopped old, then started new. It should be start new, then stop old so the default stand would be suppressed.
56 // New code:
57 // Merged with new Route recorder and notecard writer
58 // If the NPC failed to reach a destination it never moved on.
59 // Added WAIT global to tune this
60 // Exposed many tunable variables and ported the code
61 // Added floating point to times in notecard.
62 // Added @sound, @randsound, @whisper, @shout, and @cmd controls.
63 // notecards integers are not floats for better control
64 //
65 // Link Messages may be used to perform external control by injecting @commands into the stream of actions
66 // Example:
67 // To chat something, such as with a chat robot
68 // llMessageLinked(LINK_SET,0,"@npc_say=Hello","");
69
70 // This script assumes that NPCs and OSSl scripting is enabled in the OpenSim configuration.
71 // In order to enable them, the following changes must be made in the OpenSim.ini configuration file:
72 //
73 // ; Turn on OSSL
74 // AllowOSFunctions = true
75 // OSFunctionThreatLevel = Severe
76
77 //[NPC]
78 // ;# {Enabled} {} {Enable Non Player Character (NPC) facilities} {true false}
79 // Enabled = true
80 //
81 // and then the server has to be restarted.
82 // please note that there are better ways to enable NPC in the latest Opensim.
83
84 // Commands: All commands begin with an @ sign. All other lines are ignored
85 // @commands may have optional parameters. The syntax is always:
86 // @cmd=parm1|parm2
87 // NaN in the table below meand Not a Number. This means there is no parameter
88
89 //Command First Parameter Second Parameter Description
90 //@spawn name location (vector) Rezzes an NPC with name at a location.
91 //@appearance NoteCardName NaN switch the NPC appearance to a new notecard
92 //@walk destination (vector) NaN Makes the NPC walk to destination.
93 //@fly destination (vector) NaN Makes the NPC fly to destination.
94 //@land destination (vector) NaN Makes the NPC land at destination.
95 //@say string NaN Makes the NPC speak a phrase.
96 //@whisper string NaN Makes the NPC whisper a phrase.
97 //@shout string NaN Makes the NPC shout a phrase.
98 //@pause seconds (float) NaN Makes the NPC wait for a multiple of seconds.
99 //@wander radius (float) cycles (integer) Makes the NPC wander in radius, for cycles seconds.
100 //@delete NaN NaN Removes the NPC. Requires a link message to continue
101 //@goto label (string) NaN Jump to the label label in the script.
102 //@animate animation (string) time (float) Makes the NPC trigger the animation animation for time seconds.
103 //@sound sound_name NaN plays a sound from inventory
104 //@randsound NaN NaN Plays a random sound from inventory
105 //@rotate degrees (float) NaN Rotate the NPC degrees around the Z axis.
106 //@sit primitive name NaN Sit on a primitive with a given name.
107 //@touch primitive name NaN Touch on a primitive with a given name.
108 //@stand NaN NaN If sitting on a primitive, stand up.
109 //@cmd channel (integer) string Says string on channel, for controlling external gadgets
110 //@stop NaN NaN Halts the NPC script indefinitely. Can be started with a link message
111 //@go NaN NaN Continues on next notecard line, for use in link messages
112 //@speed speed (float) NaN from 0 to N, where 1.0 ius a normal speed of an avatar. 0.2 is a turtle.
113 //@notecard notename (string) NaN load a new Path notecard
114 //@attach InventoryName attachmentPoint load an attachment from the inventory to the NPC onto point
115 //@teleport destination (vector) NaN Makes the NPC teleport to destination in the same sim. They cannot tp to another sim or across the HG
116 //@reset NaN NaN Deletes the NPC, starts the !Path notecard over.
117
118 // Constant attachmentPoint Comment
119 // ATTACH_CHEST 1 chest/sternum
120 // ATTACH_HEAD 2 head
121 // ATTACH_LSHOULDER 3 left shoulder
122 // ATTACH_RSHOULDER 4 right shoulder
123 // ATTACH_LHAND 5 left hand
124 // ATTACH_RHAND 6 right hand
125 // ATTACH_LFOOT 7 left foot
126 // ATTACH_RFOOT 8 right foot
127 // ATTACH_BACK 9 back
128 // ATTACH_PELVIS 10 pelvis
129 // ATTACH_MOUTH 11 mouth
130 // ATTACH_CHIN 12 chin
131 // ATTACH_LEAR 13 left ear
132 // ATTACH_REAR 14 right ear
133 // ATTACH_LEYE 15 left eye
134 // ATTACH_REYE 16 right eye
135 // ATTACH_NOSE 17 nose
136 // ATTACH_RUARM 18 right upper arm
137 // ATTACH_RLARM 19 right lower arm
138 // ATTACH_LUARM 20 left upper arm
139 // ATTACH_LLARM 21 left lower arm
140 // ATTACH_RHIP 22 right hip
141 // ATTACH_RULEG 23 right upper leg
142 // ATTACH_RLLEG 24 right lower leg
143 // ATTACH_LHIP 25 left hip
144 // ATTACH_LULEG 26 left upper leg
145 // ATTACH_LLLEG 27 left lower leg
146 // ATTACH_BELLY 28 belly/stomach/tummy
147 // ATTACH_LEFT_PEC 29 left pectoral
148 // ATTACH_RIGHT_PEC 30 right pectoral
149 // ATTACH_HUD_CENTER_2 31 HUD Center 2
150 // ATTACH_HUD_TOP_RIGHT 32 HUD Top Right
151 // ATTACH_HUD_TOP_CENTER 33 HUD Top
152 // ATTACH_HUD_TOP_LEFT 34 HUD Top Left
153 // ATTACH_HUD_CENTER_1 35 HUD Center
154 // ATTACH_HUD_BOTTOM_LEFT 36 HUD Bottom Left
155 // ATTACH_HUD_BOTTOM 37 HUD Bottom
156 // ATTACH_HUD_BOTTOM_RIGHT 38 HUD Bottom Right
157 // ATTACH_NECK 39 neck
158 // ATTACH_AVATAR_CENTER 40 avatar center/root
159
160
161
162 //////////////////////////////////////////////////////////
163 // DEBUG //
164 //////////////////////////////////////////////////////////
165 integer debug = FALSE; // set to TRUE or FALSE for debug chat on various actions
166 integer LSLEditor = FALSE; // set to to TRUE to working in LSLEditor, FALSE for in-world.
167 // you must also include the NPC commands found in the other script since LSLEditor does not support OpenSim
168 integer iTitleText = FALSE; // set to TRUE to see debug info in text above the controller
169
170 //////////////////////////////////////////////////////////
171 // TUNABLE CONFIGURATION //
172 //////////////////////////////////////////////////////////
173 integer keyNum = -2; // (dylan) special number for link message to broadcast the NPC key
174 integer allowListener = TRUE; // set to TRUE to anable a command listener. Usually, this is setto FALSE
175 integer link_Channel = 4223; // some random number you want to talk to this gadget on. Best if large and negative
176 float TIMER = 2; // faster = less jerky stopping. How often the system checks the distance traveled. Fastest you can go is 0.5 seconds
177 float QUICK = 1; // when we need to move to the next state, we use a QUICK timer
178 string Appearance = "!Appearance"; // The name of the recorded Appearance notecard
179 string Notecard = "!Path"; // The name of the recorded routes
180 integer allowUsers = FALSE; // If true, any user can get a Start NPC and Stop NPC menu. Only groups and owners can get all commands if TRUE, or FALSE
181 float MAXDIST = 2.0; // how close a NPC has to get to a dest pos to continue to next state. Do not lower this too much, as it may miss the target
182 integer WANDERRAND = TRUE; // set to TRUE and they will pause during wanders a random number of seconds
183 float WANDERTIME = 3.0; // how long they stand after each @wander,if WANDERRAND is FALSE. If WANDERRAND is TRUE, this is the max time
184 integer WAIT = 30; // wait for this number of seconds for the NPC to reach a destination (for safety). If it fails to reach a target, it will move on after this time.
185 float RANGE = 150; // 1 to N meters - anyone this close to the controller will start NPCS if Sensor button is clicked
186 float REZTIME = 2.0; // wait this long for NPC to rez in, then start the process
187 string STAND = "Stand"; // the name of the default Stand animation
188 string WALK = "Walk"; // the name of the default Walk animation
189 string FLY = "Fly"; // the name of the default Fly animation
190 string RUN = "Run"; // the name of the default Run animation
191 string LAND = "Land"; // the name of the default land animation ( for birds only)
192 float OffsetZ = 0.5; // appear 0.5 meter above ground, this is added to all destinations to keep them from sinking in.
193 float SPEEDMULT =0.8; // 1.0 = regular avatar speed. Smaller numbers slow down walks. Large numbers speed them up.
194 integer FLIGHT = 299; // For controlling wings. A channel that is shouted at when flight starts and ends. "flying" or "landing"
195
196 // DESCRIPTIONS FIELDS HAVE TO SURVIVE A RESET
197 // These vars are stored by saving them with KeyValueSet
198 // "pr" is a 0 if it is set for Owner Only, 1 for Group control
199 // "se" is "on" if Started
200 // "co" = "R" or "A" for relative or absolute addressing mode
201 // "key" = NPC key
202
203 // These Globals used to be stored in description. Moved to RAM in V1.6
204 float RAMPause; // @pause param
205 float RAMwd ; // @wander distance
206 integer RAMwc; // @wander count
207 float RAMrot; // @rotate
208 string RAMsit; // @sit primname
209 string RAMtouch; // @touch primname
210 string RAManimationName; // @animate animation (string) time (float)
211 float RAManimationTime;
212
213 // other globals section
214 integer iChannel; // a listen channel, randomly assigned
215 integer iHandle; // the handle to it
216
217 // NPC controls
218 vector newDest ; // tmp storage for the walks
219 integer iWaitCounter ; // wait for this number of seconds for the NPC to reach a desrtination
220 string sNPCName; // the name of the NPC that may be in world. So we can remove it.
221 integer bNPC_STOP = FALSE; // boolean to reuse a listener
222 integer Stopped = FALSE; // set to TRUE by link messages so we do not remember them
223 float fTimerVal ; // how long we wait when wandering (calculated)
224 float NPCEnabled; // true if the NPC is suppodes to be running
225
226 // OS_NPC_CREATOR_OWNED will create an 'owned' NPC that will only respond to osNpc* commands issued from scripts that have the same owner as the one that created the NPC.
227 // OS_NPC_NOT_OWNED will create an 'unowned' NPC that will respond to any script that has OSSL permissions to call osNpc* commands.
228 integer NPCOptions = OS_NPC_CREATOR_OWNED; // only yhe owner of this box can control this NPC.
229
230 integer walkstate = 0; // helps us reshare the walk state for run, fly and land - a bit of a hack, but it saves RAM. Has to be done this way because some bits of NPCWalkOption are asserted as 0
231
232 integer NPCWalkOption; // Some notes for what happens to NPCWalkOption:
233 // OS_NPC_FLY - Fly the avatar to the given position. The avatar will not land unless the OS_NPC_LAND_AT_TARGET option is also given.
234 // OS_NPC_NO_FLY - Do not fly to the target. The NPC will attempt to walk to the location. If it's up in the air then the avatar will keep bouncing hopeless until another move target is given or the move is stopped
235 //OS_NPC_LAND_AT_TARGET - If given and the avatar is flying, then it will land when it reaches the target. If OS_NPC_NO_FLY is given then this option has no effect.
236 // OS_NPC_RUNNING - if given, NPC avatar moves at running/fast flying speed, otherwise moves at walking/slow flying speed.
237
238 // menus
239 string mSensor="Sense is Off"; // Sensor or "No Sensor"
240
241 list lAtButtons = ["Menu","-", ">>", "@run", "@walk", "@fly", "@land", "@wander", "@sit", "@stand","@animate","@rotate"];
242 list lMenu2 = ["<<", "@comment", ">>>", "@stop", "@say", "@whisper","@shout","@sound","@randsound","@cmd", "@pause", "@delete"];
243 list lMenu3 = ["<<<","@notecard","@appearance", "@touch", "@speed", "@attach", "@teleport","-", "-", "-", "-", "-" ];
244
245 string sCommand; // place to store a command for two-prompted ones
246 string sParam2; // place to store a prompt for two-prompted ones
247 string priPub = "Owner Only"; // Private or Group
248 key kUserKey; // the person who is controlling the avatar, not the Owner
249 // the command lists
250 list lCommands; // commands are stored here
251 list lNpcCommandList; // Storage for the NPC script.
252 string npcAction; // Storage for the next action. @cmd=0|hello, this becomes @cmd
253 string npcParams; // Storage for the param, @cmd=0|hello, this becomes 0|hello
254
255 // misc vars
256 string sNotecard; // commands are stored here temporarily for dumping
257 vector vWanderPos; // a place to wander to
258 string lastANIM ; // last animation run
259 // Sensor
260 integer avatarPresent; // Sensor sets this flag when people are within Range.
261
262 // Coordinate control
263 vector vInitialPos ; // Vector that will be filled by the script with the initial starting position in region coordinates.
264 vector vDestPos = ZERO_VECTOR; // Storage for destination position.
265 string relAbs = "Relative"; // absolute vs relative positioning
266
267
268 // STATES
269 integer MENU ; // processing a dialog box state, may be concurrent with STATE
270 integer STATE; // state storage
271 integer NULL = 0; // the null state
272 integer MakeNotecard = 1; // displaying a text box for NPC name
273 integer RecordPath = 2; // displaying a path notecard menu
274 integer NobodyHome = 3; // looking for an avatar
275 integer Spawning = 4; // spawning an avatar
276 integer Animate = 5; // animation timer needed
277 integer Walking = 6; // Hey! I am walking here!
278 integer Wander = 7; // Wandering around neeeds a timer, too
279 integer WanderHold = 8; // We reached a wander point
280 integer DoProcess = 9; // Set this to make it process a new command
281 integer Touch = 10; // Timer is busy sensing something to touch
282 integer Sit = 11; // Timer is busy sensing something to sit on
283 integer Paused = 12; // Timer is busy pausing
284
285 key gNpcKey = NULL_KEY; // global key storage for the one NPC, to save CPU cycles
286 list Stack ; // a command stack from link message input
287
288 ///////////////////////////////////////////////////////////////////////////
289 // FUNCTIONS //
290 ///////////////////////////////////////////////////////////////////////////
291
292
293 TimerEvent(float timesent)
294 {
295 if(LSLEditor)
296 timesent *= 5; // slow thinggs doen when the LSLEDITOR is in use
297
298 DEBUG("Setting timer: " + (string) timesent);
299 llSetTimerEvent(timesent);
300 }
301
302 // for 4.1 parse a message from a Listen or a Link message
303 ParseMsg(string str) {
304 DEBUG("Command In:" + str);
305 if(str=="@go") {
306 SetStop(FALSE); // Let's run the notecard
307 DEBUG("@go running");
308 DoProcessNPCLine();
309 } else {
310 Stack += [str]; // take anything, the controller will filter away non @ stuff
311 if(STATE == NULL)
312 DoProcessNPCLine(); // v 4.5 remove wait for STATE == 0
313 }
314 }
315
316 SetStop(integer what)
317 {
318 DEBUG("Stopped set to " + (string ) what);
319 Stopped = what;
320 }
321 // Do* functions are much like states from the old V2 scripts.
322
323 // Save a Path notecard
324 DoSave()
325 {
326 STATE = MakeNotecard;
327 makeText("Stand where you want the NPC to appear, and enter the NPC Name");
328 }
329
330 // This function is used to record the path for the NPC
331 // Each command can take 0, 1, or 2 params
332 DoMenuForCommands() {
333 makeMenu(lAtButtons);
334 }
335
336
337 // No one is here when sensors were on, so we kill off the NPC
338 DoNobodyHome()
339 {
340 DEBUG("Nobody Home");
341 STATE = NobodyHome;
342 if(NPCKey() != NULL_KEY) {
343 osNpcRemove(NPCKey());
344 SaveKey(NULL_KEY);
345 }
346 TimerEvent(5); // keep ticking to sense avatars
347 }
348
349
350 /////////////////////// STATELIKE BEHAVIOUR /////////////
351 // these StateXX functions need to wait on a timer to fire.
352
353 // Create a NPC
354 StateSpawn() {
355 DEBUG("state spawn " + sNPCName);
356
357 NPCEnabled = TRUE; // in world
358 // see if there is already one out there.
359 if(NPCKey() != NULL_KEY) {
360 DEBUG("Already living");
361 return;
362 }
363
364 STATE = Spawning;
365 list name = llParseString2List(sNPCName, [" "], []);
366
367 vector vRezPos = vInitialPos;
368 if(relAbs == "Relative"){
369 vRezPos += llGetPos();
370 }
371
372 // llSay(0,llDumpList2String(name,","));
373
374 DEBUG("Rezzing NPC name " +llList2String(name, 0)+ llList2String(name, 1) + " at "+ (string) vRezPos);
375 key aKey = osNpcCreate(llList2String(name, 0), llList2String(name, 1), vRezPos, Appearance, NPCOptions);
376
377 llMessageLinked(LINK_SET,keyNum,"",aKey); // bboradcast the key on num = -1
378 SaveKey(aKey); // save in description and global, too
379
380 osSetSpeed(aKey,SPEEDMULT); // 1.9 speed multiplier
381 TimerEvent(REZTIME);
382 NPCAnimate(STAND);
383 }
384
385 StateSit() {
386 DEBUG ("state sit - looking for " + RAMsit);
387 STATE=Sit;
388 llSensor(RAMsit, "", PASSIVE|ACTIVE|SCRIPTED, 96, PI);
389 }
390
391 StateTouch() {
392 DEBUG ("state touch - looking for " + RAMtouch);
393 STATE = Touch;
394 llSensor(RAMtouch, "", PASSIVE|ACTIVE|SCRIPTED, 96, PI);
395 }
396
397 DoStand() {
398 DEBUG("state stand");
399 osNpcStand(NPCKey());
400 }
401
402
403 StateAnimate() {
404
405 DEBUG("state animate");
406 STATE = Animate;
407 NPCAnimate(RAManimationName);
408 if(RAManimationTime <=0 ) // V 3.4 tweak
409 RAManimationTime = 1;
410 TimerEvent(RAManimationTime);
411 }
412
413 StateWalk() {
414
415 DEBUG("Start Walk");
416 //DEBUG("NPCWalkOption = " + (string) NPCWalkOption);
417 STATE = Walking;
418
419 // walk, fly, run, land
420 if(walkstate == 1) {
421 NPCAnimate(WALK);
422 } else if(walkstate == 2) {
423 llShout(FLIGHT,"flying");
424 NPCAnimate(FLY);
425 } else if(walkstate == 3) {
426 NPCAnimate(RUN);
427 } else if(walkstate == 4) {
428 NPCAnimate(LAND);
429 }
430 newDest = vDestPos ;
431 newDest.z += OffsetZ;
432
433 // notecard is stored as offsets from this box with relative addressing. Convert to absolute
434 if(relAbs == "Relative"){
435 newDest += llGetPos();
436 }
437
438 DEBUG("Moveto:" + (string) newDest);
439 osNpcMoveToTarget(NPCKey(), newDest, NPCWalkOption);
440 iWaitCounter = WAIT; // wait 60 seconds to get to a destination.
441 TimerEvent(TIMER);
442 }
443
444
445 StateWander(){
446 DEBUG("state wander");
447 STATE = Wander;
448
449 vector point = CirclePoint(RAMwd);
450 DEBUG("CirclePoint:" + (string) point);
451 vWanderPos = vDestPos + point;
452 DEBUG("vWanderPos:" + (string) vWanderPos);
453
454 fTimerVal = WANDERTIME; // default time to pause after each wander
455 if(WANDERRAND)
456 fTimerVal = llFrand(WANDERTIME) + 1; // override, they want random times
457
458 NPCAnimate(WALK);
459
460 DEBUG("Wander to:" + (string) vWanderPos);
461
462 osNpcMoveToTarget(NPCKey(), vWanderPos, NPCWalkOption);
463 iWaitCounter = WAIT; // wait 60 seconds to get to a destination.
464 TimerEvent(TIMER);
465 }
466
467 StateWanderhold() {
468
469 DEBUG("Wander Hold");
470 STATE = WanderHold;
471
472 // now that we have reached a wander spot, slow the timer down to the desired value
473 TimerEvent(fTimerVal);
474 }
475
476
477
478 DoRotate() {
479 DEBUG("@rotate=" + (string) RAMrot);
480 osNpcSetRot(NPCKey(), llEuler2Rot(<0,0,RAMrot> * DEG_TO_RAD));
481 }
482
483
484
485 // @pause=10 will do nothing for 10 seconds
486 DoPause() {
487 STATE = Paused;
488 if(RAMPause < 0.1)
489 RAMPause = 0.1;
490 DEBUG("@pause=" + (string)RAMPause);
491 TimerEvent(RAMPause);
492 }
493
494
495 // @stop makes the NPC stop moving in whatever state it is in. You have to linkmessage to get moving again
496 DoStop() {
497 DEBUG("NPC is Stopped");
498 STATE = 0; // accept commands
499 SetStop(TRUE); // Link controlled - we mnust have a @go to continue with notecards
500 TimerEvent(0);
501 Stack = []; // v3.8
502 }
503
504 // @delete removes the NPC forever. Next command starts it up again at the beginning
505 DoDelete() {
506 DEBUG("state delete");
507 osNpcRemove(NPCKey());
508 SaveKey(NULL_KEY);
509 TimerEvent(0);
510 Stack = []; // v3.8
511 STATE = NULL; // accept commands
512 }
513
514 // change the appearance of the NPC
515 DoAppearance(string notecard) {
516 DEBUG("state appearance");
518 DEBUG("Load appearance " + notecard);
519 osNpcLoadAppearance(NPCKey(),notecard);
520 }
521 STATE = NULL; // accept commands
522 }
523
524 // Change the avatar speed
525 DoSpeed(string speed) {
526 float newspeed = (float) speed;
527 if(newspeed > 0.1 && newspeed < 5.0) {// sanity check
528 osSetSpeed(NPCKey(),newspeed);
529 }
530 STATE = NULL; // accept commands
531 }
532
533 DoTeleport(string params) {
534 list Data = llParseString2List(params, ["|"], []);
535 string itemName = llList2String(Data, 0);
536 vector Dest = (vector) itemName;
537 if(Dest != ZERO_VECTOR) {
538 if(relAbs == "Relative"){
539 Dest += llGetPos();
540 }
541 osTeleportAgent( NPCKey(), llGetRegionName(), Dest, ZERO_VECTOR );
542
543 } else {
544 llSay(DEBUG_CHANNEL,"Attempt to teleport to <0,0,0> probably not what you intended: @teleport=<vector>");
545 }
546 STATE = NULL; // accept commands
547 }
548
549
550
551 DoNewNote (string card) {
552 DEBUG("Load Notecard " + card);
553 NPCReadNoteCard(card);
554 SetStop(FALSE);
555 STATE = NULL; // accept commands
556 }
557 DoAttach(string params) {
558
559 list Data = llParseString2List(params, ["|"], []);
560 string itemName = llList2String(Data, 0);
561 integer attachmentPoint = (integer) llList2String(Data, 1);
562 if(attachmentPoint > 0
563 && attachmentPoint < 40
565 )
566 {
567 osForceAttachToOtherAvatarFromInventory(NPCKey(),itemName,attachmentPoint);
568 }
569 STATE = NULL; // accept commands
570 }
571
572 // This loops over the notecard, processing each command
573 DoProcessNPCLine() {
574 DEBUG("ProcessNPCLine, stopped = " + (string) Stopped);
575
576 STATE = DoProcess;
577
578 // auto load a notecard
579 if(! llGetListLength(lNpcCommandList)) {
580 DEBUG("Read Notecard");
581 NPCReadNoteCard(Notecard);
582 }
583
584 // look for link messages on the stack
585 string next = llList2String(Stack,0); // lets see if there is anything from a link message
586 if(llStringLength(next))
587 {
588 Stack = llDeleteSubList(Stack,0,0);
589 ProcessCmd(next); //lets do this command instead.
590 return;
591 }
592
593 // @stop issued?
594 if(Stopped) {
595 TimerEvent(0);
596 DEBUG("Stopped, waiting for input");
597 STATE = NULL;
598 return;
599 }
600
601 // No, we have an @go for liftoff
602 next = llList2String(lNpcCommandList, 0); // get the next command
603 DEBUG("Execute:" + next);
604 lNpcCommandList = llDeleteSubList(lNpcCommandList, 0, 0); // delete it
605
606 if(llGetListLength(lNpcCommandList) == 0) {
607 DEBUG("EOF");
608 }
609 ProcessCmd(next);
610 }
611
612
613
614 ProcessCmd(string cmd) {
615
616 DEBUG("ProcessCmd:" + cmd);
617 llMessageLinked(LINK_SET,keyNum,"",NPCKey()); // bboradcast the key on num = -1
618 if(llGetSubString(cmd, 0, 0) != "@") {
619 DEBUG("ignoring");
620 TimerEvent(QUICK); // this is so we do not recurse the stack
621 STATE = NULL;
622 return;
623 }
624
625 list data = llParseString2List(cmd, ["="], []);
626 npcAction = llToLower(llStringTrim(llList2String(data, 0), STRING_TRIM));
627
628 DEBUG("Action:" + npcAction);
629 npcParams = llStringTrim(llList2String(data, 1), STRING_TRIM);
630 DEBUG("Params:" + npcParams);
631
632 @commands;
633
634 ProcessSensor();
635
636 if(npcAction == "@spawn") {
637 DEBUG("@spawn npcParams ");
638 list spawnData = llParseString2List(npcParams, ["|"], []);
639 sNPCName =llList2String(spawnData, 0); // V 1.6 name in RAM
640
641 vInitialPos = (vector) llList2String(spawnData, 1);
642 DEBUG("Coords for NPC at " + (string) vInitialPos);
643 StateSpawn();
644 return;
645 }
646
647 if(! avatarPresent){
648 DoNobodyHome();
649 DEBUG("No avatar nearby");
650 STATE = NULL;
651 return;
652 } else {
653 if( NPCKey() == NULL_KEY) {
654 StateSpawn();
655 }
656 }
657
658
659
660
661 if(npcAction == "@stop") {
662 DoStop();
663 STATE = NULL;
664 return;
665 }
666 else if(npcAction == "@goto") {
667 DEBUG("goto");
668 integer lastIdx = llGetListLength(lNpcCommandList)-1;
669 lNpcCommandList = llDeleteSubList(lNpcCommandList, lastIdx, lastIdx);
670 // Wind commands till goto label.
671 @wind;
672 string next1 = llList2String(lNpcCommandList, 0);
673 lNpcCommandList = llDeleteSubList(lNpcCommandList, 0, 0);
674 lNpcCommandList += next1;
675 if(next1 != npcParams) jump wind;
676 // Wind the label too.
677 next1 = llList2String(lNpcCommandList, 0);
678 lNpcCommandList = llDeleteSubList(lNpcCommandList, 0, 0);
679 lNpcCommandList += next1;
680 // Get next command.
681 list data1 = llParseString2List(next1, ["="], []);
682 npcAction = llToLower(llStringTrim(llList2String(data1, 0), STRING_TRIM));
683 npcParams = llStringTrim(llList2String(data1, 1), STRING_TRIM);
684 // Reschedule.
685 jump commands;
686 }
687 else if(npcAction == "@sound") {
688 DEBUG("sound");
689 llTriggerSound(npcParams,1.0);
690 }
691 else if(npcAction == "@randsound") {
692 DEBUG("@randsound");
694 integer rand = llCeil(llFrand(N)) -1; // pick a random sound
696 llTriggerSound(toPlay,1.0);
697 }
698 else if(npcAction == "@walk") {
699 DEBUG("@walk");
700 GetDest(npcParams);
701 walkstate = 1;// walking
702 NPCWalkOption = OS_NPC_NO_FLY ;
703 StateWalk();
704 return;
705 }
706 else if(npcAction == "@fly") {
707 GetDest(npcParams);
708 walkstate = 2;// flying
709 NPCWalkOption = OS_NPC_FLY ;
710 StateWalk();
711 return;
712 }
713 else if(npcAction == "@run") {
714 DEBUG("@run");
715 GetDest(npcParams);
716 walkstate = 3;// running
717 NPCWalkOption = OS_NPC_NO_FLY | OS_NPC_RUNNING;
718 StateWalk();
719 return;
720 }
721 else if(npcAction == "@land") {
722 DEBUG("@land");
723 GetDest(npcParams);
724 walkstate = 4;// landing
725 NPCWalkOption= OS_NPC_FLY | OS_NPC_LAND_AT_TARGET ;
726 StateWalk();
727 return;
728 }
729 else if(npcAction == "@say") {
730 DEBUG("@say " + npcParams);
731 osNpcSay(NPCKey(), 0, npcParams);
732 }
733 else if(npcAction == "@shout") {
734 DEBUG("@shout");
735 osNpcShout(NPCKey(),0, npcParams);
736 }
737 else if(npcAction == "@whisper") {
738 DEBUG("@whisper " + npcParams);
739 osNpcWhisper(NPCKey(),0, npcParams);
740 }
741 // speak a command on a channel, so you can open doors and control stuff.
742 else if(npcAction == "@cmd") {
743 DEBUG("@cmd");
744 list dataToSpeak = llParseString2List(npcParams, ["|"], []);
745 string channel = llList2String(dataToSpeak,0);
746 DEBUG("Channel:"+(string) channel);
747 integer iChannel = (integer) channel;
748 string stringToSpeak = llList2String(dataToSpeak,1);
749 llSay(iChannel, stringToSpeak);
750 }
751 // stop everything
752 else if(npcAction == "@pause") {
753 RAMPause = (float) npcParams;
754 DoPause();
755 return;
756 }
757 else if(npcAction == "@wander") {
758 list wanderData = llParseString2List(npcParams, ["|"], []);
759 RAMwd = (float) llList2String(wanderData, 0);
760 RAMwc = (integer) llList2String(wanderData, 1);
761 vDestPos = osNpcGetPos(NPCKey()); // set the wander start
762 DEBUG("Starting at " + (string) vDestPos);
763 StateWander();
764 return;
765 }
766 else if(npcAction == "@rotate") {
767 RAMrot = (float) npcParams;
768 DoRotate();
769 }
770 else if(npcAction == "@sit") {
771 RAMsit= npcParams;
772 StateSit();
773 return;
774 }
775 else if(npcAction == "@touch") {
776 RAMtouch= npcParams;
777 StateTouch();
778 return;
779 }
780 else if(npcAction == "@stand") {
781 DoStand();
782 }
783 else if(npcAction == "@delete") {
784 DoDelete();
785 SetStop(TRUE); // Link controlled - we mnust have a @go to continue with notecards
786 return;
787 }
788 else if(npcAction == "@animate") {
789 list animateData = llParseString2List(npcParams, ["|"], []);
790 RAManimationName = llList2String(animateData, 0);
791 RAManimationTime = (float) llList2String(animateData, 1);
792 StateAnimate();
793 return;
794 }
795 else if(npcAction == "@appearance" ){
796 DoAppearance(npcParams);
797 }
798 else if(npcAction =="@speed") {
799 DoSpeed(npcParams);
800 }
801 else if(npcAction =="@notecard") {
802 DoNewNote(npcParams);
803 Notecard = npcParams;
804 }
805 else if(npcAction == "@attach")
806 {
807 DoAttach(npcParams);
808 }
809 else if(npcAction == "@teleport")
810 {
811 DoTeleport(npcParams);
812 }
813 else if(npcAction == "@reset")
814 {
815 DoDelete();
816 SetStop(FALSE); // a @resst will restart the original !Path after deleting the notecard.
817 }
818
819 STATE = NULL;
820 TimerEvent(QUICK); // yeah I know, not possible this fast, we just go as fast as we can go - this is so we do not recurse the stack
821 }
822
823
824
825 /////////////////// UTILITY Functions, not state-like //////////////////
826
827 // DEBUG(string) will chat a string or display it as hovertext if debug == TRUE
828 DEBUG(string str) {
829 if(debug && ! LSLEditor)
830 llOwnerSay( str); // Send the owner debug info
831 if(debug && LSLEditor)
832 llSay(0, str); // Send to the Console in LSLEDitor
833 if(iTitleText) {
834 llSetText(str,<1.0,1.0,1.0>,1.0); // show hovertext
835
836 }
837 }
838
839 GetDest(string npcParams) {
840 list dest = llParseString2List(npcParams, ["<", ",", ">"], []);
841 vDestPos.x = llList2Float(dest, 0);
842 vDestPos.y = llList2Float(dest, 1);
843 vDestPos.z = llList2Float(dest, 2);
844 }
845
846 NPCReadNoteCard(string Note) {
847 DEBUG("NPCReadNoteCard");
848 lNpcCommandList = llParseString2List(osGetNotecard(Note), ["\n"], []);
849 }
850
851 integer SenseAvatar()
852 {
853 //Returns a strided list of the UUID, position, and name of each avatar in the region
854 list avatars = llGetAgentList(AGENT_LIST_REGION ,[]);
855 integer numOfAvatars = llGetListLength(avatars);
856 if(numOfAvatars == 0)
857 {
858 DEBUG("No people");
859 return 0;
860 }
861 //DEBUG("Located " + (string)numOfAvatars + " avatars and NPC's");
862
863 integer nAvatars;
864 integer i;
865 for( i = 0;i < numOfAvatars; i++) {
866 key aviKey = llList2Key(avatars,i);
867 if(!osIsNpc(aviKey)) {
868 list detail = llGetObjectDetails(aviKey,[OBJECT_POS]);
869 vector pos = llList2Vector(detail,0);
870 float dist = llVecDist(pos, llGetPos());
871 if(dist < RANGE)
872 {
873 nAvatars++;
874 DEBUG("In range:" + llKey2Name(aviKey));
875 }
876 }
877 }
878 //DEBUG("Located " + (string) nAvatars + " avatars");
879 return nAvatars;
880 }
881
882 // return TRUE if the avatar is owner when private is set, or TRUE if the avatar is in the same group and GROUP is set.
883 integer checkPerms() {
884
885 integer group = (integer) KeyValueGet("pr");
886 if(! group)
887 priPub = "Owner Only";
888 else
889 priPub = "Group";
890
891
893 kUserKey = llDetectedKey(0);
894 return TRUE;
895 }
896
897 if( group && llDetectedGroup(0)) {
898 kUserKey = llDetectedKey(0);
899 return TRUE;
900 }
901 kUserKey = llDetectedKey(0);
902 return FALSE;
903 }
904
905
906
907 NPCAnimate(string anim)
908 {
909 DEBUG("Start Anim: " + anim);
911
912 if(lastANIM != anim) {
913 if(llStringLength(lastANIM)) {
914 osNpcStopAnimation(NPCKey(), lastANIM);
915 }
916 osNpcPlayAnimation(NPCKey(), anim);
917 lastANIM = anim;
918 }
919 } else {
920 llSay(DEBUG_CHANNEL, "No animation named " + anim);
921 }
922 }
923
924 // Kill a NPC by Name
925 Kill(string param)
926 {
927 integer count;
928 list avatars = osGetAvatarList(); // Returns a strided list of the UUID, position, and name of each avatar in the region except the owner.\
929 integer i;
930 integer j = llGetListLength(avatars);
931 for (i=0 ; i <= j; i+=3){
932
933 string desired = llList2String(avatars,i+2);
934 desired = llStringTrim(desired,STRING_TRIM); // should not be needed but is needed
935
936 if(desired == param){
937 vector v = llList2Vector(avatars,i+1);
938 key target = llList2Key(avatars,i); // get the UUID of the avatar
939 osNpcRemove(target);
940
941 llOwnerSay("Removed " + param+ " at location " + (string) v);
942 count++;
943 }
944 }
945
946 NPCEnabled = FALSE; // not in world
947 SaveKey(NULL_KEY ); // Rev 4.4
948
949 if(count)
950 llOwnerSay("Removed " + (string) count + " NPC's");
951 else
952 llOwnerSay("Could not locate " + param);
953 }
954
955
956 // return a String for the position we are at. Strings used as the caller wants strings
957 string Pos()
958 {
959 vector where = llGetPos(); // find the box position
960
961 where.z += OffsetZ; // use the ground position + an offset
962
963 if(LSLEditor)
964 where = <128,128,31 + llFrand(1)>; // force center of sim when editing
965
966 // if attached the height will be too high by 1/2 the agent size
967 if(llGetAttached()) {
969 float Z = size.z;
970 where.z -= Z/2;
971 }
972
973 // DEBUG("Pos= " + (string) where);
974 return (string) where;
975 }
976
977 // setup a menu with a timer for timeouts, called by all make*()
978 menu()
979 {
980 llListenRemove(iHandle);
981 iChannel = llCeil(llFrand(100000) + 20000);
982 iHandle = llListen(iChannel,"","","");
983 TimerEvent(30.0);
984 MENU = TRUE;
985 }
986
987 // make a text box
988 makeText(string Param)
989 {
990 menu();
991 llTextBox(kUserKey, Param, iChannel);
992 }
993
994 // top level menu
995 makeMainMenu()
996 {
997 menu();
998 list buttons = ["Appearance","Recording","Save","Help","-","Erase RAM", priPub,relAbs,"-","Stop NPC",mSensor,"Start NPC"];
999 llDialog(kUserKey,(string) llGetListLength(lCommands) + " Records",buttons,iChannel);
1000 }
1001
1002
1003 // Rev 1.4
1004 // top level menu for non group/ non owners
1005 makeUserMenu()
1006 {
1007 if(!allowUsers) return;
1008
1009 menu();
1010 list buttons = ["Start NPC","Stop NPC"];
1011 llDialog(kUserKey,"Choose",buttons,iChannel);
1012 }
1013
1014
1015
1016 // programmable menu for @commands
1017 makeMenu(list buttons)
1018 {
1019 menu();
1020 llDialog(kUserKey,(string) llGetListLength(lCommands) + " Record",buttons,iChannel);
1021 }
1022
1023
1024 // make one or two text boxes with prompts
1025 Text(string cmd, string p1, string p2)
1026 {
1027 sCommand = cmd;
1028 sParam2 = "";
1029 if(llStringLength(p2))
1030 sParam2 = p2;
1031
1032 makeText(p1);
1033 }
1034
1035 // Set the Avatar Present flag - if sensors are off and we are force run, there will be one present.
1036 ProcessSensor()
1037 {
1038 integer SensorOn;
1039 if("on" == KeyValueGet("se"))
1040 {
1041 SensorOn = TRUE; // we need to scan for avatars
1042 } else {
1043 SensorOn = FALSE; // we need to scan for avatars
1044 }
1045 DEBUG("Sensor:" + (string) SensorOn);
1046
1047 integer n = SenseAvatar();
1048
1049 DEBUG("Avatars:" + (string) n);
1050 if(SensorOn && n)
1051 avatarPresent = TRUE; // someone is here and we need to tell the system to run
1052 else if(SensorOn && !n)
1053 avatarPresent = FALSE; // someone is not here and we need to tell the system to stop
1054 else { // sensor is off, lete see if there is a NPC. If so, we are ON
1055 // DEBUG("NPCEnabled:" + (string) NPCEnabled);
1056
1057 if(NPCEnabled)
1058 avatarPresent = TRUE;
1059 else
1060 avatarPresent = FALSE;
1061 }
1062
1063 // start up from when when no one is near
1064 if(avatarPresent && STATE == NobodyHome)
1065 STATE = NULL;
1066
1067 DEBUG("Avatar Present: " + (string) avatarPresent);
1068 }
1069
1070 vector CirclePoint(float radius) {
1071 float x = llFrand(radius *2) - radius; // +/- radius, randomized
1072 float y = llFrand(radius *2) - radius; // +/- radius, randomized
1073 return <x, y, 0>; // so this should always happen
1074 }
1075
1076 string KeyValueGet(string var) {
1077 list dVars = llParseString2List(llGetObjectDesc(), ["&"], []);
1078 do {
1079 list data = llParseString2List(llList2String(dVars, 0), ["="], []);
1080 string k = llList2String(data, 0);
1081 if(k != var) jump continue;
1082 //DEBUG("got " + var + " = " + llList2String(data, 1));
1083 return llList2String(data, 1);
1084 @continue;
1085 dVars = llDeleteSubList(dVars, 0, 0);
1086 } while(llGetListLength(dVars));
1087 return "";
1088 }
1089
1090 KeyValueSet(string var, string val) {
1091
1092 //DEBUG("set " + var + " = " + val);
1093 list dVars = llParseString2List(llGetObjectDesc(), ["&"], []);
1094 if(llGetListLength(dVars) == 0)
1095 {
1096 llSetObjectDesc(var + "=" + val);
1097 return;
1098 }
1099 list result = [];
1100 do {
1101 list data = llParseString2List(llList2String(dVars, 0), ["="], []);
1102 string k = llList2String(data, 0);
1103 if(k == "") jump continue;
1104 if(k == var && val == "") jump continue;
1105 if(k == var) {
1106 result += k + "=" + val;
1107 val = "";
1108 jump continue;
1109 }
1110 string v = llList2String(data, 1);
1111 if(v == "") jump continue;
1112 result += k + "=" + v;
1113 @continue;
1114 dVars = llDeleteSubList(dVars, 0, 0);
1115 } while(llGetListLength(dVars));
1116 if(val != "") result += var + "=" + val;
1118 }
1119
1120
1121 // clear RAM
1122 Clr() {
1123
1124 lCommands = [];
1125 llOwnerSay("RAM Memory cleared. Notecards, if any, are not modified.");
1126 makeMainMenu();
1127 }
1128
1129 integer checkNoteCards()
1130 {
1131 // Check that they have saved an Appeaance and Path notecard
1132 integer num = llGetInventoryNumber(INVENTORY_NOTECARD); // how many notecards overall
1133
1134 integer i;
1135 integer count;
1136 for (; i < num; i++){
1138 count++;
1140 count++;
1141 }
1142 DEBUG("Checked " + (string) count + " Notecards");
1143 // if we have both, run the NPC
1144 return count;
1145 }
1146
1147 Update(string SName) {
1148
1149 // delete all NPC* scripts except myself
1150 integer i;
1152 for (i = 0; i < j; i++) {
1154 string match = llGetSubString(targetName,0,2);
1155
1156 if(match == SName && llGetScriptName() != targetName){
1157 llOwnerSay("Upgrading " + targetName);
1158 if(! LSLEditor){ // lets not kill the editor
1159 llRemoveInventory(targetName);
1160 }
1161 }
1162 }
1163 }
1164
1165 // Get all default saved params from the Description
1166 GetSwitches()
1167 {
1168 string rA = KeyValueGet("co"); // Get the remembered menu setting for Abs Vs Relative
1169 if(rA == "A")
1170 relAbs = "Absolute";
1171 else if(rA == "R")
1172 relAbs = "Relative";
1173 else
1174 relAbs = "Absolute";
1175
1176
1177 // reenable NPC if sensor is on.
1178 if("on" == KeyValueGet("se"))
1179 {
1180 NPCEnabled = TRUE;
1181 mSensor = "Sense is On";
1182 ProcessSensor(); // fake 1 avatar to get it rezzed
1183 } else {
1184 mSensor = "Sense is Off";
1185 }
1186 }
1187
1188
1189 SaveKey(key akey)
1190 {
1191 DEBUG("Saving Key of " + (string) akey);
1192 KeyValueSet("key", akey);
1193 if(akey != (key) KeyValueGet("key") )
1194 {
1195 DEBUG("Fatal error, cannot save key");
1196 }
1197 gNpcKey = akey;
1198 }
1199
1200
1201 key NPCKey()
1202 {
1203 key akey = gNpcKey; // from cached copy
1204 // gNpcKey saves a lot of CPU processing by caching the key, if blank we get it from the description
1205 if(gNpcKey == NULL_KEY)
1206 {
1207 //DEBUG("Get DKey");
1208 akey = KeyValueGet("key"); // from Description of the prim
1209 }
1210 // DEBUG("NPC KEY:" + (string) akey);
1211 return akey;
1212 }
1213
1214
1215 /////////////////// CODE BEGINS //////////////////
1216
1217
1218 default
1219 {
1220 changed(integer change) {
1221 if(change & CHANGED_REGION_START) {
1223 }
1224 }
1225
1226 on_rez(integer start_param)
1227 {
1229 }
1230
1231 state_entry() {
1232
1233 llSetText("",<1,1,1>,1.0); // clr all hovertext- we may not be using it.
1234 DoDelete(); // kill any NPC that is out running
1235 Update("NPC"); // If dragged and ropped into a prim with any script named "NPC...", this will replace it.
1236 GetSwitches(); // Get all default saved params from the Description
1237
1238 // 4.1 allow listeners to send us commands
1239 if(allowListener)
1240 llListen(link_Channel,"","","");
1241 TimerEvent(TIMER);
1242 }
1243
1244
1246 { // if touched, make a menu
1247
1248 if(checkPerms()) {
1249 if(RecordPath == STATE) {
1250 makeMenu(lAtButtons);
1251 } else {
1252 makeMainMenu();
1253 }
1254 } else {
1255 makeUserMenu();
1256 }
1257 }
1258
1259 // menu listener
1260 listen(integer iChannel, string name, key id, string message) {
1261
1262 // process @commands that come in via the listener
1263 if(iChannel == link_Channel)
1264 {
1265 ParseMsg(message);
1266 return;
1267 }
1268
1269 if(MENU) {
1270 llListenRemove(iHandle);
1271 MENU = 0; // menu is off
1272 iHandle = 0;
1273 }
1274
1275 if(message == "Stop NPC")
1276 {
1277 lNpcCommandList = []; // force reload of notecard
1278 NPCEnabled = FALSE;
1279 if(NPCKey() != NULL_KEY){
1280 Kill(sNPCName);
1281 sNPCName = "";
1282 } else {
1283 bNPC_STOP = TRUE;
1284 makeText("Enter name of an NPC to stop");
1285 }
1286 }
1287 else if(message == "Menu" ) {
1288 makeMainMenu();
1289 }
1290 else if(message == "Erase RAM"){
1291 Clr();
1292 }
1293 else if(message == "Relative"){
1294 relAbs = "Absolute";
1295 KeyValueSet("co","A"); // remember coordinates = A
1296 Clr();
1297 }
1298 else if(message == "Absolute"){
1299 relAbs = "Relative";
1300 KeyValueSet("co","R"); // remember coordinates = R
1301 Clr();
1302 }
1303 else if(message == "Recording"){
1304 DoMenuForCommands(); // show them the recording menu
1305 }
1306 else if(message == "Owner Only") {
1307 priPub = "Group";
1308 KeyValueSet("pr","1");
1309
1310 llOwnerSay("Group members have control");
1311 makeMainMenu();
1312 }
1313 else if(message == "Group") {
1314 priPub = "Owner Only";
1315 KeyValueSet("pr","0");
1316 llOwnerSay("Only you have control");
1317 makeMainMenu();
1318 }
1319 else if(message == "Sense is On") {
1320 mSensor ="Sense is Off";
1321 KeyValueSet("se", "off");
1322 llOwnerSay(mSensor);
1323 makeMainMenu();
1324 }
1325 else if(message == "Sense is Off") {
1326 mSensor ="Sense is On";
1327 llOwnerSay(mSensor);
1328 KeyValueSet("se", "on");
1329
1330 NPCEnabled = TRUE;
1331
1332 integer count = checkNoteCards();
1333 if(count >= 2) {
1334 DEBUG("Notecards ok, DoProcessNPCLine");
1335 DoProcessNPCLine();
1336 return;
1337 }
1338 if(LSLEditor) {
1339 DoProcessNPCLine();
1340 return;
1341 }
1342
1343 llOwnerSay("You have not saved a recording and/or appearance, so you cannot start a NPC");
1344 makeMainMenu();
1345 }
1346 else if(message == "Appearance") {
1347 llRemoveInventory(Appearance); // delete the notecard
1348 osAgentSaveAppearance(kUserKey,Appearance); // make the ntecard
1349 llOwnerSay("Your outfit has been saved");
1350 makeMainMenu();
1351 }
1352 else if(message == "Save") {
1353 if(llGetListLength(lCommands) == 0) {
1354 llOwnerSay("Nothing recorded, you need to make a recording first");
1355 makeMainMenu();
1356 return;
1357 }
1358 DoSave();
1359 }
1360 else if(message == "Help"){
1361 llLoadURL(kUserKey,"Click to view help","http://www.outworldz.com/opensim/posts/NPC/");
1362 makeMainMenu();
1363 }
1364 else if(message == "Start NPC") {
1365 integer count = checkNoteCards();
1366
1367 NPCEnabled = TRUE;
1368
1369 if(LSLEditor) {
1370 DoProcessNPCLine();
1371 return;
1372 }
1373
1374 if(count >= 2) {
1375 DEBUG("Notecards approved , calling DoProcessNPCLine");
1376 SetStop(FALSE); // Let's run the notecard
1377 DoProcessNPCLine();
1378 return;
1379 }
1380
1381 llOwnerSay("You have not saved a recording or maybe an appearance, so we cannot start a NPC");
1382
1383 }
1384 else if(bNPC_STOP){
1385 bNPC_STOP = FALSE;
1386 Kill(message);
1387 }
1388 else if(message == ">>"){
1389 makeMenu(lMenu2);
1390 }
1391 else if(message == ">>>"){
1392 makeMenu(lMenu3);
1393 }
1394 else if(message == "<<") {
1395 makeMenu(lAtButtons);
1396 }
1397 else if(message == "<<<") {
1398 makeMenu(lMenu2);
1399 }
1400 else if(message == "@comment"){
1401 Text("# ","Enter a comment","");
1402 }
1403 else if(message == "@stop"){
1404 lCommands += "@stop"+ "\n";
1405 makeMenu(lAtButtons);
1406 }
1407 else if(message == "@run"){
1408 lCommands += "@run=" + Pos() + "\n";
1409 llOwnerSay("Recorded position: " + Pos());
1410 makeMenu(lAtButtons);
1411 }
1412 else if(message == "@fly"){
1413 lCommands += "@fly=" + Pos() + "\n";
1414 llOwnerSay("Recorded position: " + Pos());
1415 makeMenu(lAtButtons);
1416 }
1417 else if(message == "@land"){
1418 lCommands += "@land=" + Pos() + "\n";
1419 llOwnerSay("Recorded position: " + Pos());
1420 makeMenu(lAtButtons);
1421 }
1422 else if(message == "@walk") {
1423 lCommands += "@walk=" + Pos() + "\n";
1424 llOwnerSay("Recorded position: " + Pos());
1425 makeMenu(lAtButtons);
1426 }
1427 else if(message == "@stop"){
1428 lCommands += "@stop"+ "\n";
1429 makeMenu(lAtButtons);
1430 }
1431 else if(message == "@sound"){
1432 Text("@sound=","Enter a sound name or UUID to trigger","");
1433 }
1434 else if(message == "@randsound"){
1435 lCommands += "@randsound"+ "\n";
1436 makeMenu(lAtButtons);
1437 }
1438 else if(message == "@say") {
1439 Text("@say=","Enter what the NPC will say","");
1440 }
1441 else if(message == "@whisper"){
1442 Text("@whisper=","Enter what the NPC will whisper","");
1443 }
1444 else if(message == "@shout"){
1445 Text("@shout=","Enter what the NPC will shout","");
1446 }
1447 else if(message == "@wander") {
1448 Text("@wander=","Enter radius to wander","Enter number of wanders");
1449 }
1450 else if(message == "@pause") {
1451 Text("@pause=","Enter time to pause","");
1452 }
1453 else if(message == "@rotate") {
1454 Text("@rotate=","Enter degrees to rotate","");
1455 }
1456 else if(message == "@sit"){
1457 Text("@sit=","Enter name of object to sit on","");
1458 }
1459 else if(message == "@teleport"){
1460 lCommands += "@teleport=" + Pos() + "\n";
1461 llOwnerSay("teleport to position: " + Pos());
1462 makeMenu(lMenu3);
1463 }
1464 else if(message == "@touch"){
1465 Text("@touch=","Enter name of object to touch","");
1466 }
1467 else if(message == "@cmd"){
1468 Text("@cmd=","Enter cjhannel to speak on","Enter text to speak");
1469 }
1470 else if(message == "@stand"){
1471 lCommands += "@stand\n";
1472 llOwnerSay("Stand Recorded");
1473 makeMenu(lAtButtons);
1474 }
1475 else if(message == "@animate"){
1476 Text("@animate=","Enter animation name to play","Enter time to play the animation");
1477 }
1478 else if(message == "@attach"){
1479 Text("@animate=","Enter inventory name to attach","Enter number of the attachment point (1-40)");
1480 }
1481 else if(message == "@speed"){
1482 Text("@speed=","Enter a speed for the NPC, 1=100% normal speed, 0.5=50% speed","");
1483 }
1484
1485
1486 // Save NPC name
1487 else if(MakeNotecard == STATE) {
1488 sNPCName = message; // in case we need to kill it.
1489
1490 vector vDest = (vector) Pos();
1491
1492 if(relAbs == "Relative")
1493 {
1494 vDest -= llGetPos(); // just an offset for relative
1495 }
1496 sNotecard = "@spawn=" + message + "|" + (string) vDest + "\n";
1497 integer i;
1498 integer j = llGetListLength(lCommands);
1499 for (; i < j; i++){
1500 // get the command to save to the notecard
1501 string line = llList2String(lCommands,i);
1502 if(relAbs == "Absolute") {
1503 sNotecard += line; // add the un-modified string to the notecard
1504 } else {
1505 // since we have to record absolute coords since we do not know where the box goes until they press Save,
1506 // we process the absolute to relative conversion for walks here
1507 list parts = llParseString2List(line,["="],[]); //get the @command
1508
1509 if(llList2String(parts,0) == "@walk") {
1510 vector vec = (vector) llList2String(parts,1) - llGetPos();
1511 sNotecard += "@walk=" + (string) vec + "\n";
1512 }
1513 else if(llList2String(parts,0) == "@fly") {
1514 vector vec = (vector) llList2String(parts,1) - llGetPos();
1515 sNotecard += "@fly=" + (string) vec + "\n";
1516 }
1517 else if(llList2String(parts,0) == "@run") {
1518 vector vec = (vector) llList2String(parts,1) - llGetPos();
1519 sNotecard += "@run=" + (string) vec + "\n";
1520 }
1521 else if(llList2String(parts,0) == "@land") {
1522 vector vec = (vector) llList2String(parts,1) - llGetPos();
1523 sNotecard += "@land=" + (string) vec + "\n";
1524 }
1525 else {
1526 sNotecard += line; // add the un-modified string to the notecard
1527 }
1528 }
1529 }
1530 llRemoveInventory(Notecard); // delete the old notecard
1531 osMakeNotecard(Notecard,sNotecard); // Makes the notecard.
1532 llSay(0,sNotecard);
1533 llOwnerSay("Commands notecard has been written");
1534 STATE = NULL;
1535 } // MakeNotecard
1536
1537 else if(! llStringLength(sParam2)) {
1538 lCommands += sCommand + message + "\n";
1539 llOwnerSay("Recorded");
1540 makeMenu(lAtButtons);
1541 }
1542 else if(llStringLength(sParam2)){
1543 sCommand = sCommand + message + "|";
1544 llOwnerSay("Recorded");
1545 makeText(sParam2);
1546 sParam2 = "";
1547 }
1548
1549 }
1550
1551
1552
1553 timer(){
1554 // DEBUG("tick");
1555
1556 // if llDialog is up, kill the listener for the dialog box.
1557 if(iHandle) {
1558 llOwnerSay("Menu timed out");
1559 llListenRemove(iHandle);
1560 iHandle = 0;
1561 return; // ^^^^^^^^^^^^^^^^^^^^^^^
1562 }
1563 // if NoBodyHome, we are sensing for an avatar
1564 else if(NobodyHome == STATE) {
1565 ProcessSensor();
1566 return;
1567 }
1568 // if we are spawning, we need time to rez the NPC, then start processing NPC Commands.
1569 else if(Spawning == STATE) {
1570 STATE = NULL;
1571 TimerEvent(TIMER);
1572 }
1573 // We end aniamtions with a timer
1574 else if(Animate == STATE){
1575 NPCAnimate(STAND);
1576 TimerEvent(TIMER);
1577 }
1578
1579 else if(Walking == STATE) {
1580 if(--iWaitCounter) {
1581 DEBUG("still walking...");
1582 if(llVecDist(osNpcGetPos(NPCKey()), newDest) > MAXDIST) {
1583 return;
1584 }
1585 }
1586
1587 DEBUG("At Destination: " + (string) newDest);
1588
1589 // walk, fly, run, land
1590 if(walkstate == 1) {
1591 NPCAnimate(STAND);
1592 NPCWalkOption = OS_NPC_NO_FLY;
1593 } else if(walkstate == 2) {
1594 // nothing
1595 } else if(walkstate == 3) {
1596 NPCAnimate(STAND);
1597 NPCWalkOption = OS_NPC_NO_FLY;
1598 } else if(walkstate == 4) {
1599 llShout(FLIGHT,"landing");
1600 NPCAnimate(STAND);
1601 NPCWalkOption = OS_NPC_NO_FLY;
1602 }
1603 }
1604 // Wandering timer
1605 else if(Wander == STATE) {
1606 if(--iWaitCounter) { // wait 60 seconds to get to a destination.
1607 if(llVecDist(osNpcGetPos(NPCKey()), vWanderPos) > MAXDIST)
1608 return;
1609 }
1610
1611 // see if wander counter == 0, if so, stop walking, go to stand and process next line
1612 if(RAMwc == 0) {
1613 NPCAnimate(STAND);
1614 DEBUG("Wander ended, calling DoProcessNPCLine");
1615 STATE = NULL;
1616 return;
1617 }
1618 // one less time to wander around
1619 RAMwc--;
1620 NPCAnimate(STAND);
1621 TimerEvent(TIMER);
1622 StateWanderhold();
1623 return;
1624 }
1625 // Wandering requires us to re-wander when we reach a destination
1626 else if(WanderHold == STATE) {
1627 StateWander();
1628 }
1629 else if(DoProcess == STATE) {
1630 TimerEvent(QUICK);
1631 }
1632
1633 STATE = NULL;
1634
1635 // We always process a NPC line at end of timer.
1636 DEBUG("Tick end, calling DoProcessNPCLine");
1637 DoProcessNPCLine();
1638 }
1639
1640 // sensors are used for sitting on prims
1641 // Neo Cortex: added different states to trigger sit or touch
1642 sensor(integer num) {
1643 if(Sit == STATE ) {
1644 osNpcSit(NPCKey(), llDetectedKey(0), OS_NPC_SIT_NOW);
1645 DEBUG("Seated, calling DoProcessNPCLine");
1646
1647 STATE = 0;
1648 } else if(Touch == STATE) {
1649 osNpcTouch(NPCKey(), llDetectedKey(0), LINK_THIS);
1650 DEBUG("Touched, calling DoProcessNPCLine");
1651 STATE = 0;
1652 }
1653 DoProcessNPCLine();
1654 }
1655 no_sensor(){
1656 DEBUG ("no target prim located, calling DoProcessNPCLine");
1657 DoProcessNPCLine();
1658 STATE = NULL;
1659 }
1660
1661
1662 link_message(integer sender, integer num, string str, key id){
1663 if(num == 0)
1664 ParseMsg(str);
1665 if(num == 99)
1667 }
1668
1669 }
1670
1671 // __ END__

HyperGrid Story Nine

NPC console controller
Accepts button input from 8 buttons in a compass rose configuration
Controls a pait of NPCS to xap 6 other NPCs.

Category: NPC
By : Shin Ingen
Created: 2015-11-24 Edited: 2015-11-23
Worlds: Second Life

1
2 //vector osNpcGetPos(key id) { return <128,128,20>; }
3
4 // TUNABLES
5
6
7 integer debug = 0;
8 integer CommandChannel = 23;
9 integer BUSY;
10
11 string errorbeep = "back-in-time";
12 string footsteps = "footstepmuffled";
13 string click = "coins_bag_2_3";
14 string sex="Sex";
15 string effect="magic-string-spell-2";
16
17 integer gFireworksChannel =20;
18 integer STATE = 0;
19
20 float LASTDIST = 1;
21 integer counter = 0;
22 list FarAway = ["She is too far away","Move her closer to the dancers", "Move her near the dancers and press the center button.","What button did you just push?","Is she stuck?", "I hope this is working"];
23
24 integer counter1 = 0;
25 list Instructions = ["She is moving","Move her next to the dancers", "Try the center button.","Try the other direction","I think you have to get near a dancer", "Move her close to the dancers and press the center button", "Keep going", " This looks good"];
26
27
28 float MAXDIST = 15;
29 float MaxBeam = 3.0; // how far the helmet beam happens
30
31 vector centerPoint = <213.89366, 129.07689, 39.56485>;
32
33 // first three are root, namaka, and dylan
35
36 list avatars;
37
38 // Link Numbers of the NPC controller
39 integer namaka =2;
40 integer dylan =3;
41 integer npc1 =4;
42 integer npc2 =5;
43 integer npc3 =6;
44 integer npc4 =7;
45 integer npc5 =8;
46 integer npc6 =9; // MaryAnne
47
48 // GLOBALS
49 integer busy ;
50
51 key NamakaKey;
52 key DylanKey ;
53
54 // npc counters
55 integer FIRST = 0;
56 integer LAST = 6;
57 integer currNpcNum;
58
59 // FUNCTIONS
60
61 DEBUG(string msg)
62 {
63 if(debug ==1 || debug ==3)
64 llOwnerSay(llGetScriptName() + ":" + msg);
65 if(debug ==2 || debug ==3) {
66 llSetText(msg, <1,0,0>,1.0);
67 llSleep(0.25);
68 }
69 }
70
71 string NpcSayInstructions()
72 {
73 if(counter1 ++ >= llGetListLength(Instructions))
74 counter1 = 0;
75
76 return llList2String(Instructions,counter1);
77 }
78
79
80 string NpcSayTooFar()
81 {
82 if(counter++ >= llGetListLength(FarAway))
83 counter = 0;
84
85 return llList2String(FarAway,counter);
86 }
87
88
89 Command(integer Npc,string msg)
90 {
91 //DEBUG("Command:" +(string) Npc +":" + msg);
92 llMessageLinked(Npc,0,msg,"");
93 }
94
95 ChangeNpcToDemon(integer npc)
96 {
97 DEBUG("Change to demon " + (string) npc);
98 llMessageLinked(npc,100,"BOOM",""); // tell the effect to play
99 llMessageLinked(LINK_SET,200,"BANG",""); // tell the spiral effect to play
100 Command(npc,"@notecard=!Changed");
101
102 }
103
104 Win()
105 {
106 DEBUG("Win");
107
108 llRegionSay(gFireworksChannel,"Go");
109 STATE=1;
110
111 DEBUG("FIREWORKS");
113 }
114
115
116 InitAllNpc()
117 {
118 DEBUG("Init all Npc");
119 currNpcNum = FIRST;
120 llMessageLinked(LINK_SET,99,"reset","");
121 busy = FALSE;
122 STATE = 0;
123 avatars = [0,0,0,0,1,1,1,1,1,1]; // 0 is skipped, 1 = root, 2 - namaka
124 llShout(CommandChannel,"0006");
125 BUSY = FALSE;
126 }
127
128 default
129 {
131 {
132 DEBUG("Reset");
133 busy = FALSE;
134 llSetText("",<1,1,1>,1.0);
135 InitAllNpc();
136 }
137
138 timer()
139 {
140 llShout(CommandChannel,(string) (180 - STATE));
142
143 if(STATE == 15 || STATE== 20 || STATE == 25) {
144 llRegionSay(gFireworksChannel,"Go");
145
146 }
147
148 if(STATE==30)
149 {
150 Command(namaka,"@notecard=dragon");
151 Command(dylan,"@notecard=dragon");
152 }
153 if(STATE == 180)
154 {
155 InitAllNpc();
157 }
158 STATE++;
159 }
160
161
162 link_message(integer sender_number, integer number, string message, key id)
163 {
164 if(BUSY)
165 return;
166 // DEBUG("Num:" + (string) number + " str:" + message);
167 // --10 is the link num and pos of the dancer
168 // -2 is dylans key
169 // -1 is Namakas key
170 // 4 is for NPC direction commands
171 // 1 is for doorway
172 // 2 is the helmet beam channel
173
174 if(number == -10) {
175 list x = llParseString2List(message,["|"],[]);
176 integer linknum = (integer) llList2String( x,0);
177 vector v = (vector) llList2String( x,1);
178
179 avatarDancePos = llListReplaceList(avatarDancePos,[v],linknum,linknum);
180 //DEBUG("Npc Count = " + (string) llGetListLength(avatarDancePos));
181 }
182 else if(number == -2){ // -2 is Dylans key fromt the modified NPC controller script
183 DylanKey = id;
184 //DEBUG("Dylan Key set to " + (string) id);
185
186 } else if(number == -1){ // -1 is Namakas key
187 NamakaKey = id;
188 //DEBUG("Namaka Key set to " + (string) id);
189
190 } else if(number == 4){ // 4 is for NPC direction commands
191
192 llMessageLinked(LINK_SET,0," ",""); // stimulate back a NPC key message
193
194 vector direction = (vector) message;
195 direction *= 2.5;
196 DEBUG((string) direction);
197
198 llTriggerSound(click,1.0);
199
200 //DEBUG("Key: " + (string) NamakaKey);
201 list stuff = llGetObjectDetails(NamakaKey, [OBJECT_POS]);
202
203 vector namakaPos = llList2Vector(stuff,0);
204 //vector namakaPos = osNpcGetPos(NamakaKey);
205 // DEBUG("NPC at " + (string) namakaPos);
206
207 vector newNpcPos = namakaPos;
208 newNpcPos.z = 0;
209 centerPoint.z = 0;
210
211 //DEBUG("Roam dist:" + (string) llVecDist(newNpcPos, centerPoint));
212 llTriggerSound(footsteps,1.0);
213
214 // Move the npc some direction
215
216 if(llVecDist(newNpcPos, centerPoint) > MAXDIST) {
217 DEBUG("Went too far");
218 llTriggerSound(errorbeep,1.0);
219
220 string toSay = NpcSayTooFar();
221 Command(dylan,"@animate=avatar_type|3");
222 Command(dylan,"@say=" + toSay);
223
224 // string newPos2 = (string) (namakaPos + (direction *-2));
225 // DEBUG("Reversing to @walk=" + newPos2);
226
227 Command(namaka,"@teleport=<207, 126, 41>" );
228 Command(dylan,"@teleport=<198.4, 130, 41.5>" );
229 return;
230 }
231
232
233 string newPos = (string) (namakaPos + direction + <0,0,0.2>);
234 //DEBUG("Moving to @walk=" + newPos);
235
236 Command(namaka,"@walk=" + newPos);
237
238 } else if(number == 1) { // 1 is for doorway
239
240 if(busy) {
241 DEBUG("Busy");
242 return;
243 }
244
245 InitAllNpc();
246 busy++;
247
248 } else if(number == 2){ // 2 is the helmet beam channel
249
250 DEBUG(llDumpList2String(avatars,":"));
251
252 llTriggerSound(effect,1.0);
253 llMessageLinked(LINK_SET,0," ",""); // stimulate back a NPC key message
254
255 LASTDIST = 99;
256 integer i;
257 integer j = llGetListLength(avatarDancePos);
258 for (i = 0; i < j; i++)
259 {
260 if(llList2Integer(avatars,i) != 0)
261 {
262
263 //DEBUG("I:" + (string) i);
264 // get Namakas position and compare it to all the npcs
265 vector npcPos = llList2Vector(avatarDancePos,i);
266 npcPos.z = 0;
267 list stuff = llGetObjectDetails(NamakaKey, [OBJECT_POS]);
268 vector namakaPos = llList2Vector(stuff,0);
269
270 if(namakaPos == ZERO_VECTOR)
271 {
272 DEBUG("Oops");
273 return;
274 }
275
276 namakaPos.z = 0;
277
278 //DEBUG("namakaPos:" + (string) namakaPos);
279 //DEBUG("npcPos:" + (string) npcPos);
280
281 float dist = llVecDist(npcPos,namakaPos);
282 //DEBUG("dist:" + (string) dist);
283
284 if(dist < LASTDIST)
285 {
286 LASTDIST = dist;
287 }
288
289 if(dist < MaxBeam)
290 {
291 llSetText("Distance\n" + (string) dist,<0,1,0>,1.0);
292 Command(dylan,"@say=Something is happening!" );
293
294 ChangeNpcToDemon(i); //1 = root, 2 = namake, 3 = dylan
295
296 avatars= llListReplaceList(avatars,[0],i,i);
297 integer k;
298 integer l = llGetListLength(avatars);
299 integer count;
300 for (k=0; k < l; k++)
301 {
302 count += llList2Integer(avatars,k);
303 }
304 DEBUG("count:" + (string) count);
305 llShout(CommandChannel,(string) count);
306
307 if(count <2) {
308
309 llTriggerSound(sex,1.0);
310 Win();
311 BUSY++;
312 return;
313 }
314 llTriggerSound(effect,1.0);
315 return;
316 }
317
318 }
319 }
320 llSetText("Distance\n" + (string) LASTDIST,<0,1,0>,1.0);
321
322 string toSay2 = NpcSayInstructions();
323 Command(dylan,"@animate=avatar_type|3");
324 Command(dylan,"@say=" + toSay2);
325
326 integer d = (integer) LASTDIST;
327 Command(dylan,"@say=The closest dancer is about " + d + " meters away from her");
328
329 }
330 }
331 }

HyperGrid Story Nine

NPC console controller button script
Goes into 8 buttons in a compass rose configuration
Controls a pair of NPCS to xap 6 othert NPCs.
each button gets a different config.

Category: NPC
By : Shin Ingen
Created: 2015-11-24 Edited: 2015-11-23
Worlds: Second Life

1
2 string E = "<1,0,0>";
3 string NE = "<1,1,>";
4 string N = "<0,1,0>";
5 string NW = "<-1,1,0>";
6 string W = "<-1,0,0>";
7 string SW = "<-1,-1,0>";
8 string S = "<0,-1,0>";
9 string SE = "<1,-1,0>";
10
11 // chose a direction for one of 8 buttons.
12
13 default
14 {
15 touch_start(integer total_number)
16 {
17 llSetLinkPrimitiveParamsFast(llGetLinkNumber(),[PRIM_FULLBRIGHT, ALL_SIDES,TRUE]);
19 llSetTimerEvent(0.5);
20 }
21 timer()
22 {
23 llSetLinkPrimitiveParamsFast(llGetLinkNumber(),[PRIM_FULLBRIGHT,ALL_SIDES, FALSE]);
25 }
26 }

HyperGrid Story Nine

Triggers the NPC controller to play a notecard when collided.

Category: NPC
By : Shin Ingen
Created: 2015-11-24 Edited: 2015-11-23
Worlds: Second Life

1
2 string message = "@notecard=!Path";
3
4 Reset() {
5 llSetStatus(STATUS_PHANTOM, FALSE); // rev 2.0
7 llSleep(0.1);
9 }
10
11 default{
13 Reset();
14 }
17 return;
18 }
19 llMessageLinked(LINK_SET,1,message,""); // 1 ios for doorway only
20 }
21 on_rez(integer p){
23 }
24 changed(integer what){
25 if(what & CHANGED_REGION_START){
27 }
28 }
29 }

HyperGrid Story Nine

Button Script for console

Category: NPC
By : Shin Ingen
Created: 2015-11-24 Edited: 2015-11-23
Worlds: Second Life

1
2 // The Go button at the center
3
4 default
5 {
6 touch_start(integer total_number)
7 {
8 llSetLinkPrimitiveParamsFast(llGetLinkNumber(),[PRIM_FULLBRIGHT, ALL_SIDES,TRUE]);
9 llMessageLinked(LINK_SET,2,"Red","");
10 llSetTimerEvent(0.5);
11 }
12 timer()
13 {
14 llSetLinkPrimitiveParamsFast(llGetLinkNumber(),[PRIM_FULLBRIGHT,ALL_SIDES, FALSE]);
16 }
17
18 }

HyperGrid Story Nine

NPC helmet controller
Accepts a single chatted command to msake the cyber being helmet flash for a second or two.
worn by the NPC

Category: NPC
By : Shin Ingen
Created: 2015-11-24 Edited: 2015-11-23
Worlds: Second Life

1
2 // TUNABLES
3 integer debug = 1;
4
5
6
7
8 // GLOBALS
9 integer counter = 0;
10
11
12 // FUNCTIONS
13 DEBUG(string msg)
14 {
15 if(debug & 1)
16 llSay(0,llGetScriptName() + ":" + msg);
17 if(debug & 2)
18 llSetText(msg, <1,0,0>,1.0);
19 }
20
21 Go()
22 {
23 llSetLinkPrimitiveParamsFast(llGetLinkNumber(),[PRIM_GLOW,ALL_SIDES,.2, PRIM_SIZE, <0.5,0.5,2>,PRIM_COLOR,ALL_SIDES,<1,1,1>,0.5 ]);
25
26 }
27
28
29
30 default
31 {
33 {
35 }
36
37 changed(integer what)
38 {
39 if(what & CHANGED_REGION_START)
40 {
42 }
43 }
44
45
47 {
48 llSetText("", <1,0,0>,1.0);
49 llListenRemove(listener);
50
51 llSetTextureAnim(ANIM_ON | SMOOTH | LOOP , ALL_SIDES, 1, 1, 1.0, 1.0, 1.0);
52 }
53
54 link_message(integer total_number, integer Num, string text, key id)
55 {
56 if( text =="BANG")
57 Go();
58 }
59
60
61 timer()
62 {
63 llSetLinkPrimitiveParamsFast(llGetLinkNumber(),[PRIM_GLOW,ALL_SIDES,0.2, PRIM_SIZE, <0.5,0.5,2> ] );
64 llSleep(0.25);
65 llSetLinkPrimitiveParamsFast(llGetLinkNumber(),[PRIM_GLOW,ALL_SIDES,0.2, PRIM_SIZE, <0.4,0.4,2> ] );
66
67 llSetLinkPrimitiveParamsFast(llGetLinkNumber(),[PRIM_GLOW,ALL_SIDES,0, PRIM_SIZE, <0.2,0.2,2> ] );
68 llSleep(0.25);
69 llSetLinkPrimitiveParamsFast(llGetLinkNumber(),[PRIM_GLOW,ALL_SIDES,0, PRIM_SIZE, <0.1,0.1,2>,PRIM_COLOR,ALL_SIDES,<1,1,1>,0 ] );
71
72 }
73
74 }

HyperGrid Story Nine

NPC helmet controller
Accepts a single chatted command to msake the cyber being helmet flash for a second or two.
rezzed in world and not worn

Category: NPC
By : Shin Ingen
Created: 2015-11-24 Edited: 2015-11-23
Worlds: Second Life

1
2 // TUNABLES
3 integer debug = 0;
4
5
6 // GLOBALS
7 integer counter = 0;
8 integer listener;
9
10 // FUNCTIONS
11 DEBUG(string msg)
12 {
13 if(debug & 1)
14 llSay(0,llGetScriptName() + ":" + msg);
15 if(debug & 2)
16 llSetText(msg, <1,0,0>,1.0);
17 }
18 NpcSaySomething(){
19
20 list sayings = ["The helmet begins to glow","A beam of light appears","The helmet begins to hum","The cyberbeing helmet is activated", "bzzz", "Zap","KaPow!"];
21 key NpcKey = llGetOwner();
22 DEBUG("Owner Key = " + (string) NpcKey);
23
24 llSay(0, llList2String(sayings,counter++));
25 if(counter > llGetListLength(sayings)) {
26 counter = 0;
27 }
28 }
29
30 Go()
31 {
32 NpcSaySomething();
33 llSetLinkPrimitiveParamsFast(0,[PRIM_GLOW,ALL_SIDES,.2, PRIM_SIZE, <2,2,2>,PRIM_COLOR,ALL_SIDES,<1,1,1>,0.5 ]);
35
36 }
37
38 integer Helmet_Channel = 576;
39
40 default
41 {
43 {
45 }
46
47 changed(integer what)
48 {
49 if(what & CHANGED_REGION_START)
50 {
52 }
53 }
54
55
57 {
58 llSetText("", <1,0,0>,1.0);
59 llListenRemove(listener);
60 listener= llListen(Helmet_Channel, "","","BOOM"); // listen for boom command
61
62 llSetTextureAnim(ANIM_ON | SMOOTH | LOOP , ALL_SIDES, 1, 1, 1.0, 1.0, 1.0);
63
64 llSensorRepeat("Namaka ?", NULL_KEY, AGENT, 25.0, PI,5);
65 }
66
67 listen(integer channel, string name, key id, string msg)
68 {
69 Go();
70 }
71
72 touch_start(integer total_number)
73 {
74 Go();
75 }
76
78 {
79 llSetRegionPos(llDetectedPos(0) + <0,0,1>);
80 }
81
82 no_sensor()
83 {
84 llSetRegionPos(<200, 128, 48>); // sop we can find this blasted thing
85 }
86
87 timer()
88 {
89 llSetLinkPrimitiveParamsFast(0,[PRIM_GLOW,ALL_SIDES,0.2, PRIM_SIZE, <1.5,1.5,1> ] );
90 llSleep(0.25);
91 llSetLinkPrimitiveParamsFast(0,[PRIM_GLOW,ALL_SIDES,0.2, PRIM_SIZE, <1.2,1.2,1> ] );
92
93 llSetLinkPrimitiveParamsFast(0,[PRIM_GLOW,ALL_SIDES,0, PRIM_SIZE, <1,1,1> ] );
94 llSleep(0.25);
95 llSetLinkPrimitiveParamsFast(0,[PRIM_GLOW,ALL_SIDES,0, PRIM_SIZE, <1,1,1>,PRIM_COLOR,ALL_SIDES,<1,1,1>,0 ] );
97
98 }
99
100 }

HyperGrid Story Nine

All in one NPC recorder player.
Supports both absolute and relative paths and many new commands
Add animations named "Fly, Walk, Stand and Run"
Click Prim to use.
Should be worn as a HUD to record.
Put it on the ground and click Sensor or Start NPC when done.

Category: NPC
By : Shin Ingen
Created: 2015-11-24 Edited: 2015-11-23
Worlds: Second Life

1
2 // This is Rev 9.3 based on 4.5 09/26/2015
3
4 // Revision History
5 // Rev 1.1 10-2-2014 @Sit did not work. Minor tweaks to casting for lslEditor
6 // Rev 1.2 10-14-2014 @ sit had wrong type.
7 // Rev 1.3 relative movement fixed for @fly
8 // Rev 1.4 4-3-2014 allow anyone to use this, non owners and non group members can only start and stop.
9 // Rev 1.5 5-17-2014 set sensor to auto start on reboot of sim
10 // Rev 1.6 5-24-2014 move menu so you can get it by touching, removed many of the KeyValues to RAM for efficiency
11 // Rev 1.7 CHANGED_REGION_START, not CHANGED_REGION_START (Opensim difference)
12 // Rev 1.8 tuned up Kill NPC, added more flexible upgrader
13 // Rev 1.9 Better script injection by link message// Rev 2.0 Added osSetSpeed so you can speed up or slow down an NPC.
14 // Rev 2.1 No laggy sensor used exept to sit on stuff
15 // Rev 2.2 Various sensor fixes
16 // Rev 2.3 Sets No Sensor in menu, must be started by hand
17 // Rev 2.4 - reserved for patches to 2.3 if needed
18 // Rev 3.0 Refactor out into subs, not states to make command injection easier
19 // New command: @appearance=Notecardname so you can switch to a new notecard on the fly
20 // New command: @speed=1.0 which slows up ( < 1 ) or speeds up ( > 1)
21 // Rev 3.1 Commands are not interruptible by Link Message
22 // Rev 3.2 Sensor patches for consistency in removing the NPC
23 // Rev 3.3 Added Touch command by Neo.Cortex@hbase42/hopto/org:8002
24 // Added Menu 3 for notecard and appearance commands
25 // Rev 3.4 animation timer cannot be zero or it shuts off timer tweaked
26 // solves the NPC starting up when no sensor is set.
27 // Rev 3.5 fixes saving to !Path notecard
28 // Rev 3.6 08-11-2015 @delete acts like @stop. TYjhe NPC now rezzes after an @go back in where it was deleted
29 // Rev 3.7 08-11-2015 @attach command added to load an attachment from the inventory to the NPC
30 // Rev 3.8 08-17-2015 process queued commands one at a time without calling ProcessNPCLine on link message
31 // Rev 3.9 08-23-2011 Queued command fixes including @delete which were not always working
32 // Rev 4.0 09-15-2015 Fixes for Sensor functions which continually rezzed a NPC when no one was around.
33 // Rev 4.1 09-20-2015 Added a Listener so link messages are not needed
34 // Rev 4.2 09-23-2015 Added @teleport=<vector>
35 // Rev 4.3 09-24-2015 Added @reset to restart the NPC at the very start of the !Path notecard
36 // @teleport works for relative and absolute modes
37 // Rev 4.4 09-26-2015 if it could not find the (deleted) NPC, it could not restart
38 // Rev 4.5 09-29-2015 remove wait for STATE == 0
39 //*******************************************************************//
40
41 // Instructions on how to use this are at http://www.outworldz.com/opensim/posts/NPC/
42 // This is an OpenSim-only script.
43 // Author: Ferd Frederix aka Fred Beckhusen - fred@mitsi.com
44
45 ////////////////////////////////////////////////////////////////////////////////////////////
46 // Original code was Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 //
47 ///////////////////////////////////////////////////////////////////////////////////////////
48 // Please see: http://www.gnu.org/licenses/gpl.html for legal details, //
49 // rights of fair usage, the disclaimer and warranty conditions. //
50 ///////////////////////////////////////////////////////////////////////////////////////////
51 // The original NPC controller was from http://was.fm/opensim:npc
52 // Extensive additions and bug fixes by Fred Beckhusen, aka Ferd Frederix
53 // llSensor had two params swapped
54 // @Wander would wander where it had rezzed, not where it was.
55 // There was no 'no_sensor' event in sit, so if a @sit failed, the NPC got stuck
56 // The animation and walks always stopped old, then started new. It should be start new, then stop old so the default stand would be suppressed.
57 // New code:
58 // Merged with new Route recorder and notecard writer
59 // If the NPC failed to reach a destination it never moved on.
60 // Added WAIT global to tune this
61 // Exposed many tunable variables and ported the code
62 // Added floating point to times in notecard.
63 // Added @sound, @randsound, @whisper, @shout, and @cmd controls.
64 // notecards integers are not floats for better control
65 //
66 // Link Messages may be used to perform external control by injecting @commands into the stream of actions
67 // Example:
68 // To chat something, such as with a chat robot
69 // llMessageLinked(LINK_SET,0,"@npc_say=Hello","");
70
71 // This script assumes that NPCs and OSSl scripting is enabled in the OpenSim configuration.
72 // In order to enable them, the following changes must be made in the OpenSim.ini configuration file:
73 //
74 // ; Turn on OSSL
75 // AllowOSFunctions = true
76 // OSFunctionThreatLevel = Severe
77
78 //[NPC]
79 // ;# {Enabled} {} {Enable Non Player Character (NPC) facilities} {true false}
80 // Enabled = true
81 //
82 // and then the server has to be restarted.
83 // please note that there are better ways to enable NPC in the latest Opensim.
84
85 // Commands: All commands begin with an @ sign. All other lines are ignored
86 // @commands may have optional parameters. The syntax is always:
87 // @cmd=parm1|parm2
88 // NaN in the table below meand Not a Number. This means there is no parameter
89
90 //Command First Parameter Second Parameter Description
91 //@spawn name location (vector) Rezzes an NPC with name at a location.
92 //@appearance NoteCardName NaN switch the NPC appearance to a new notecard
93 //@walk destination (vector) NaN Makes the NPC walk to destination.
94 //@fly destination (vector) NaN Makes the NPC fly to destination.
95 //@land destination (vector) NaN Makes the NPC land at destination.
96 //@say string NaN Makes the NPC speak a phrase.
97 //@whisper string NaN Makes the NPC whisper a phrase.
98 //@shout string NaN Makes the NPC shout a phrase.
99 //@pause seconds (float) NaN Makes the NPC wait for a multiple of seconds.
100 //@wander radius (float) cycles (integer) Makes the NPC wander in radius, for cycles seconds.
101 //@delete NaN NaN Removes the NPC. Requires a link message to continue
102 //@goto label (string) NaN Jump to the label label in the script.
103 //@animate animation (string) time (float) Makes the NPC trigger the animation animation for time seconds.
104 //@sound sound_name NaN plays a sound from inventory
105 //@randsound NaN NaN Plays a random sound from inventory
106 //@rotate degrees (float) NaN Rotate the NPC degrees around the Z axis.
107 //@sit primitive name NaN Sit on a primitive with a given name.
108 //@touch primitive name NaN Touch on a primitive with a given name.
109 //@stand NaN NaN If sitting on a primitive, stand up.
110 //@cmd channel (integer) string Says string on channel, for controlling external gadgets
111 //@stop NaN NaN Halts the NPC script indefinitely. Can be started with a link message
112 //@go NaN NaN Continues on next notecard line, for use in link messages
113 //@speed speed (float) NaN from 0 to N, where 1.0 ius a normal speed of an avatar. 0.2 is a turtle.
114 //@notecard notename (string) NaN load a new Path notecard
115 //@attach InventoryName attachmentPoint load an attachment from the inventory to the NPC onto point
116 //@teleport destination (vector) NaN Makes the NPC teleport to destination in the same sim. They cannot tp to another sim or across the HG
117 //@reset NaN NaN Deletes the NPC, starts the !Path notecard over.
118
119 // Constant attachmentPoint Comment
120 // ATTACH_CHEST 1 chest/sternum
121 // ATTACH_HEAD 2 head
122 // ATTACH_LSHOULDER 3 left shoulder
123 // ATTACH_RSHOULDER 4 right shoulder
124 // ATTACH_LHAND 5 left hand
125 // ATTACH_RHAND 6 right hand
126 // ATTACH_LFOOT 7 left foot
127 // ATTACH_RFOOT 8 right foot
128 // ATTACH_BACK 9 back
129 // ATTACH_PELVIS 10 pelvis
130 // ATTACH_MOUTH 11 mouth
131 // ATTACH_CHIN 12 chin
132 // ATTACH_LEAR 13 left ear
133 // ATTACH_REAR 14 right ear
134 // ATTACH_LEYE 15 left eye
135 // ATTACH_REYE 16 right eye
136 // ATTACH_NOSE 17 nose
137 // ATTACH_RUARM 18 right upper arm
138 // ATTACH_RLARM 19 right lower arm
139 // ATTACH_LUARM 20 left upper arm
140 // ATTACH_LLARM 21 left lower arm
141 // ATTACH_RHIP 22 right hip
142 // ATTACH_RULEG 23 right upper leg
143 // ATTACH_RLLEG 24 right lower leg
144 // ATTACH_LHIP 25 left hip
145 // ATTACH_LULEG 26 left upper leg
146 // ATTACH_LLLEG 27 left lower leg
147 // ATTACH_BELLY 28 belly/stomach/tummy
148 // ATTACH_LEFT_PEC 29 left pectoral
149 // ATTACH_RIGHT_PEC 30 right pectoral
150 // ATTACH_HUD_CENTER_2 31 HUD Center 2
151 // ATTACH_HUD_TOP_RIGHT 32 HUD Top Right
152 // ATTACH_HUD_TOP_CENTER 33 HUD Top
153 // ATTACH_HUD_TOP_LEFT 34 HUD Top Left
154 // ATTACH_HUD_CENTER_1 35 HUD Center
155 // ATTACH_HUD_BOTTOM_LEFT 36 HUD Bottom Left
156 // ATTACH_HUD_BOTTOM 37 HUD Bottom
157 // ATTACH_HUD_BOTTOM_RIGHT 38 HUD Bottom Right
158 // ATTACH_NECK 39 neck
159 // ATTACH_AVATAR_CENTER 40 avatar center/root
160
161
162
163 //////////////////////////////////////////////////////////
164 // DEBUG //
165 //////////////////////////////////////////////////////////
166 integer debug = FALSE; // set to TRUE or FALSE for debug chat on various actions
167 integer LSLEditor = FALSE; // set to to TRUE to working in LSLEditor, FALSE for in-world.
168 // you must also include the NPC commands found in the other script since LSLEditor does not support OpenSim
169 integer iTitleText = FALSE; // set to TRUE to see debug info in text above the controller
170
171 //////////////////////////////////////////////////////////
172 // TUNABLE CONFIGURATION //
173 //////////////////////////////////////////////////////////
174 integer keyNum = -1; // (dylan) special number for link message to broadcast the NPC key
175 integer allowListener = TRUE; // set to TRUE to anable a command listener. Usually, this is setto FALSE
176 integer link_Channel = 4223; // some random number you want to talk to this gadget on. Best if large and negative
177 float TIMER = 2; // faster = less jerky stopping. How often the system checks the distance traveled. Fastest you can go is 0.5 seconds
178 float QUICK = 1; // when we need to move to the next state, we use a QUICK timer
179 string Appearance = "!Appearance"; // The name of the recorded Appearance notecard
180 string Notecard = "!Path"; // The name of the recorded routes
181 integer allowUsers = FALSE; // If true, any user can get a Start NPC and Stop NPC menu. Only groups and owners can get all commands if TRUE, or FALSE
182 float MAXDIST = 2.0; // how close a NPC has to get to a dest pos to continue to next state. Do not lower this too much, as it may miss the target
183 integer WANDERRAND = TRUE; // set to TRUE and they will pause during wanders a random number of seconds
184 float WANDERTIME = 3.0; // how long they stand after each @wander,if WANDERRAND is FALSE. If WANDERRAND is TRUE, this is the max time
185 integer WAIT = 30; // wait for this number of seconds for the NPC to reach a destination (for safety). If it fails to reach a target, it will move on after this time.
186 float RANGE = 150; // 1 to N meters - anyone this close to the controller will start NPCS if Sensor button is clicked
187 float REZTIME = 2.0; // wait this long for NPC to rez in, then start the process
188 string STAND = "Stand"; // the name of the default Stand animation
189 string WALK = "Walk"; // the name of the default Walk animation
190 string FLY = "Fly"; // the name of the default Fly animation
191 string RUN = "Run"; // the name of the default Run animation
192 string LAND = "Land"; // the name of the default land animation ( for birds only)
193 float OffsetZ = 0.5; // appear 0.5 meter above ground, this is added to all destinations to keep them from sinking in.
194 float SPEEDMULT =0.8; // 1.0 = regular avatar speed. Smaller numbers slow down walks. Large numbers speed them up.
195 integer FLIGHT = 299; // For controlling wings. A channel that is shouted at when flight starts and ends. "flying" or "landing"
196
197 // DESCRIPTIONS FIELDS HAVE TO SURVIVE A RESET
198 // These vars are stored by saving them with KeyValueSet
199 // "pr" is a 0 if it is set for Owner Only, 1 for Group control
200 // "se" is "on" if Started
201 // "co" = "R" or "A" for relative or absolute addressing mode
202 // "key" = NPC key
203
204 // These Globals used to be stored in description. Moved to RAM in V1.6
205 float RAMPause; // @pause param
206 float RAMwd ; // @wander distance
207 integer RAMwc; // @wander count
208 float RAMrot; // @rotate
209 string RAMsit; // @sit primname
210 string RAMtouch; // @touch primname
211 string RAManimationName; // @animate animation (string) time (float)
212 float RAManimationTime;
213
214 // other globals section
215 integer iChannel; // a listen channel, randomly assigned
216 integer iHandle; // the handle to it
217
218 // NPC controls
219 vector newDest ; // tmp storage for the walks
220 integer iWaitCounter ; // wait for this number of seconds for the NPC to reach a desrtination
221 string sNPCName; // the name of the NPC that may be in world. So we can remove it.
222 integer bNPC_STOP = FALSE; // boolean to reuse a listener
223 integer Stopped = FALSE; // set to TRUE by link messages so we do not remember them
224 float fTimerVal ; // how long we wait when wandering (calculated)
225 float NPCEnabled; // true if the NPC is suppodes to be running
226
227 // OS_NPC_CREATOR_OWNED will create an 'owned' NPC that will only respond to osNpc* commands issued from scripts that have the same owner as the one that created the NPC.
228 // OS_NPC_NOT_OWNED will create an 'unowned' NPC that will respond to any script that has OSSL permissions to call osNpc* commands.
229 integer NPCOptions = OS_NPC_CREATOR_OWNED; // only yhe owner of this box can control this NPC.
230
231 integer walkstate = 0; // helps us reshare the walk state for run, fly and land - a bit of a hack, but it saves RAM. Has to be done this way because some bits of NPCWalkOption are asserted as 0
232
233 integer NPCWalkOption; // Some notes for what happens to NPCWalkOption:
234 // OS_NPC_FLY - Fly the avatar to the given position. The avatar will not land unless the OS_NPC_LAND_AT_TARGET option is also given.
235 // OS_NPC_NO_FLY - Do not fly to the target. The NPC will attempt to walk to the location. If it's up in the air then the avatar will keep bouncing hopeless until another move target is given or the move is stopped
236 //OS_NPC_LAND_AT_TARGET - If given and the avatar is flying, then it will land when it reaches the target. If OS_NPC_NO_FLY is given then this option has no effect.
237 // OS_NPC_RUNNING - if given, NPC avatar moves at running/fast flying speed, otherwise moves at walking/slow flying speed.
238
239 // menus
240 string mSensor="Sense is Off"; // Sensor or "No Sensor"
241
242 list lAtButtons = ["Menu","-", ">>", "@run", "@walk", "@fly", "@land", "@wander", "@sit", "@stand","@animate","@rotate"];
243 list lMenu2 = ["<<", "@comment", ">>>", "@stop", "@say", "@whisper","@shout","@sound","@randsound","@cmd", "@pause", "@delete"];
244 list lMenu3 = ["<<<","@notecard","@appearance", "@touch", "@speed", "@attach", "@teleport","-", "-", "-", "-", "-" ];
245
246 string sCommand; // place to store a command for two-prompted ones
247 string sParam2; // place to store a prompt for two-prompted ones
248 string priPub = "Owner Only"; // Private or Group
249 key kUserKey; // the person who is controlling the avatar, not the Owner
250 // the command lists
251 list lCommands; // commands are stored here
252 list lNpcCommandList; // Storage for the NPC script.
253 string npcAction; // Storage for the next action. @cmd=0|hello, this becomes @cmd
254 string npcParams; // Storage for the param, @cmd=0|hello, this becomes 0|hello
255
256 // misc vars
257 string sNotecard; // commands are stored here temporarily for dumping
258 vector vWanderPos; // a place to wander to
259 string lastANIM ; // last animation run
260 // Sensor
261 integer avatarPresent; // Sensor sets this flag when people are within Range.
262
263 // Coordinate control
264 vector vInitialPos ; // Vector that will be filled by the script with the initial starting position in region coordinates.
265 vector vDestPos = ZERO_VECTOR; // Storage for destination position.
266 string relAbs = "Relative"; // absolute vs relative positioning
267
268
269 // STATES
270 integer MENU ; // processing a dialog box state, may be concurrent with STATE
271 integer STATE; // state storage
272 integer NULL = 0; // the null state
273 integer MakeNotecard = 1; // displaying a text box for NPC name
274 integer RecordPath = 2; // displaying a path notecard menu
275 integer NobodyHome = 3; // looking for an avatar
276 integer Spawning = 4; // spawning an avatar
277 integer Animate = 5; // animation timer needed
278 integer Walking = 6; // Hey! I am walking here!
279 integer Wander = 7; // Wandering around neeeds a timer, too
280 integer WanderHold = 8; // We reached a wander point
281 integer DoProcess = 9; // Set this to make it process a new command
282 integer Touch = 10; // Timer is busy sensing something to touch
283 integer Sit = 11; // Timer is busy sensing something to sit on
284 integer Paused = 12; // Timer is busy pausing
285
286 key gNpcKey = NULL_KEY; // global key storage for the one NPC, to save CPU cycles
287 list Stack ; // a command stack from link message input
288
289 ///////////////////////////////////////////////////////////////////////////
290 // FUNCTIONS //
291 ///////////////////////////////////////////////////////////////////////////
292
293
294 TimerEvent(float timesent)
295 {
296 if(LSLEditor)
297 timesent *= 5; // slow thinggs doen when the LSLEDITOR is in use
298
299 DEBUG("Setting timer: " + (string) timesent);
300 llSetTimerEvent(timesent);
301 }
302
303 // for 4.1 parse a message from a Listen or a Link message
304 ParseMsg(string str) {
305 DEBUG("Command In:" + str);
306 if(str=="@go") {
307 SetStop(FALSE); // Let's run the notecard
308 DEBUG("@go running");
309
310 DoProcessNPCLine();
311 } else {
312 Stack += [str]; // take anything, the controller will filter away non @ stuff
313 DEBUG(llDumpList2String(Stack,","));
314 if(STATE == NULL) {
315 DoProcessNPCLine(); // v 4.5 remove wait for STATE == 0
316 }
317 }
318 }
319
320 SetStop(integer what)
321 {
322 DEBUG("Stopped set to " + (string ) what);
323 Stopped = what;
324 }
325 // Do* functions are much like states from the old V2 scripts.
326
327 // Save a Path notecard
328 DoSave()
329 {
330 STATE = MakeNotecard;
331 makeText("Stand where you want the NPC to appear, and enter the NPC Name");
332 }
333
334 // This function is used to record the path for the NPC
335 // Each command can take 0, 1, or 2 params
336 DoMenuForCommands() {
337 makeMenu(lAtButtons);
338 }
339
340
341 // No one is here when sensors were on, so we kill off the NPC
342 DoNobodyHome()
343 {
344 DEBUG("Nobody Home");
345 STATE = NobodyHome;
346 if(NPCKey() != NULL_KEY) {
347 osNpcRemove(NPCKey());
348 SaveKey(NULL_KEY);
349 }
350 TimerEvent(5); // keep ticking to sense avatars
351 }
352
353
354 /////////////////////// STATELIKE BEHAVIOUR /////////////
355 // these StateXX functions need to wait on a timer to fire.
356
357 // Create a NPC
358 StateSpawn() {
359 DEBUG("state spawn " + sNPCName);
360
361 NPCEnabled = TRUE; // in world
362 // see if there is already one out there.
363 if(NPCKey() != NULL_KEY) {
364 DEBUG("Already living");
365 return;
366 }
367
368 STATE = Spawning;
369 list name = llParseString2List(sNPCName, [" "], []);
370
371 vector vRezPos = vInitialPos;
372 if(relAbs == "Relative"){
373 vRezPos += llGetPos();
374 }
375
376 // llSay(0,llDumpList2String(name,","));
377
378 DEBUG("Rezzing NPC name " +llList2String(name, 0)+ llList2String(name, 1) + " at "+ (string) vRezPos);
379 key aKey = osNpcCreate(llList2String(name, 0), llList2String(name, 1), vRezPos, Appearance, NPCOptions);
380
381 llMessageLinked(LINK_SET,keyNum,"",aKey); // bboradcast the key on num = -1
382 SaveKey(aKey); // save in description and global, too
383
384 osSetSpeed(aKey,SPEEDMULT); // 1.9 speed multiplier
385 TimerEvent(REZTIME);
386 NPCAnimate(STAND);
387 }
388
389 StateSit() {
390 DEBUG ("state sit - looking for " + RAMsit);
391 STATE=Sit;
392 llSensor(RAMsit, "", PASSIVE|ACTIVE|SCRIPTED, 96, PI);
393 }
394
395 StateTouch() {
396 DEBUG ("state touch - looking for " + RAMtouch);
397 STATE = Touch;
398 llSensor(RAMtouch, "", PASSIVE|ACTIVE|SCRIPTED, 96, PI);
399 }
400
401 DoStand() {
402 DEBUG("state stand");
403 osNpcStand(NPCKey());
404 }
405
406
407 StateAnimate() {
408
409 DEBUG("state animate");
410 STATE = Animate;
411 NPCAnimate(RAManimationName);
412 if(RAManimationTime <=0 ) // V 3.4 tweak
413 RAManimationTime = 1;
414 TimerEvent(RAManimationTime);
415 }
416
417 StateWalk() {
418
419 DEBUG("Start Walk");
420 //DEBUG("NPCWalkOption = " + (string) NPCWalkOption);
421 STATE = Walking;
422
423 // walk, fly, run, land
424 if(walkstate == 1) {
425 NPCAnimate(WALK);
426 } else if(walkstate == 2) {
427 llShout(FLIGHT,"flying");
428 NPCAnimate(FLY);
429 } else if(walkstate == 3) {
430 NPCAnimate(RUN);
431 } else if(walkstate == 4) {
432 NPCAnimate(LAND);
433 }
434 newDest = vDestPos ;
435 newDest.z += OffsetZ;
436
437 // notecard is stored as offsets from this box with relative addressing. Convert to absolute
438 if(relAbs == "Relative"){
439 newDest += llGetPos();
440 }
441
442 DEBUG("Moveto:" + (string) newDest);
443 osNpcMoveToTarget(NPCKey(), newDest, NPCWalkOption);
444 iWaitCounter = WAIT; // wait 60 seconds to get to a destination.
445 TimerEvent(TIMER);
446 }
447
448
449 StateWander(){
450 DEBUG("state wander");
451 STATE = Wander;
452
453 vector point = CirclePoint(RAMwd);
454 DEBUG("CirclePoint:" + (string) point);
455 vWanderPos = vDestPos + point;
456 DEBUG("vWanderPos:" + (string) vWanderPos);
457
458 fTimerVal = WANDERTIME; // default time to pause after each wander
459 if(WANDERRAND)
460 fTimerVal = llFrand(WANDERTIME) + 1; // override, they want random times
461
462 NPCAnimate(WALK);
463
464 DEBUG("Wander to:" + (string) vWanderPos);
465
466 osNpcMoveToTarget(NPCKey(), vWanderPos, NPCWalkOption);
467 iWaitCounter = WAIT; // wait 60 seconds to get to a destination.
468 TimerEvent(TIMER);
469 }
470
471 StateWanderhold() {
472
473 DEBUG("Wander Hold");
474 STATE = WanderHold;
475
476 // now that we have reached a wander spot, slow the timer down to the desired value
477 TimerEvent(fTimerVal);
478 }
479
480
481
482 DoRotate() {
483 DEBUG("@rotate=" + (string) RAMrot);
484 osNpcSetRot(NPCKey(), llEuler2Rot(<0,0,RAMrot> * DEG_TO_RAD));
485 }
486
487
488
489 // @pause=10 will do nothing for 10 seconds
490 DoPause() {
491 STATE = Paused;
492 if(RAMPause < 0.1)
493 RAMPause = 0.1;
494 DEBUG("@pause=" + (string)RAMPause);
495 TimerEvent(RAMPause);
496 }
497
498
499 // @stop makes the NPC stop moving in whatever state it is in. You have to linkmessage to get moving again
500 DoStop() {
501 DEBUG("NPC is Stopped");
502 STATE = 0; // accept commands
503 SetStop(TRUE); // Link controlled - we mnust have a @go to continue with notecards
504 TimerEvent(0);
505 Stack = []; // v3.8
506 }
507
508 // @delete removes the NPC forever. Next command starts it up again at the beginning
509 DoDelete() {
510 DEBUG("state delete");
511 osNpcRemove(NPCKey());
512 SaveKey(NULL_KEY);
513 TimerEvent(0);
514 Stack = []; // v3.8
515 STATE = NULL; // accept commands
516 }
517
518 // change the appearance of the NPC
519 DoAppearance(string notecard) {
520 DEBUG("state appearance");
522 DEBUG("Load appearance " + notecard);
523 osNpcLoadAppearance(NPCKey(),notecard);
524 }
525 STATE = NULL; // accept commands
526 }
527
528 // Change the avatar speed
529 DoSpeed(string speed) {
530 float newspeed = (float) speed;
531 if(newspeed > 0.1 && newspeed < 5.0) {// sanity check
532 osSetSpeed(NPCKey(),newspeed);
533 }
534 STATE = NULL; // accept commands
535 }
536
537 DoTeleport(string params) {
538 vector Dest = (vector) params;
539
540 DEBUG("teleport to " + (string) Dest);
541
542 if(Dest != ZERO_VECTOR) {
543 if(relAbs == "Relative"){
544 Dest += llGetPos();
545 }
546 DEBUG("Off to " + (string) Dest);
547 osTeleportAgent( NPCKey(), llGetRegionName(), Dest, ZERO_VECTOR );
548
549 } else {
550 llSay(DEBUG_CHANNEL,"Attempt to teleport to <0,0,0> probably not what you intended: @teleport=<vector>");
551 }
552 STATE = NULL; // accept commands
553 }
554
555
556
557 DoNewNote (string card) {
558 DEBUG("Load Notecard " + card);
559 NPCReadNoteCard(card);
560 SetStop(FALSE);
561 STATE = NULL; // accept commands
562 }
563 DoAttach(string params) {
564
565 list Data = llParseString2List(params, ["|"], []);
566 string itemName = llList2String(Data, 0);
567 integer attachmentPoint = (integer) llList2String(Data, 1);
568 if(attachmentPoint > 0
569 && attachmentPoint < 40
571 )
572 {
573 osForceAttachToOtherAvatarFromInventory(NPCKey(),itemName,attachmentPoint);
574 }
575 STATE = NULL; // accept commands
576 }
577
578 // This loops over the notecard, processing each command
579 DoProcessNPCLine() {
580 DEBUG("ProcessNPCLine, stopped = " + (string) Stopped);
581
582 STATE = DoProcess;
583
584 // auto load a notecard
585 if(! llGetListLength(lNpcCommandList)) {
586 DEBUG("Read Notecard");
587 NPCReadNoteCard(Notecard);
588 }
589
590 // look for link messages on the stack
591
592 DEBUG(llDumpList2String(Stack,","));
593 string next = llList2String(Stack,0); // lets see if there is anything from a link message
594
595 DEBUG("Next:" + next);
596
597 if(llStringLength(next))
598 {
599 Stack = llDeleteSubList(Stack,0,0);
600 ProcessCmd(next); //lets do this command instead.
601 return;
602 }
603
604 // @stop issued?
605 if(Stopped) {
606 TimerEvent(0);
607 DEBUG("Stopped, waiting for input");
608 STATE = NULL;
609 return;
610 }
611
612 // No, we have an @go for liftoff
613 next = llList2String(lNpcCommandList, 0); // get the next command
614 DEBUG("Execute:" + next);
615 lNpcCommandList = llDeleteSubList(lNpcCommandList, 0, 0); // delete it
616
617 if(llGetListLength(lNpcCommandList) == 0) {
618 DEBUG("EOF");
619 }
620 ProcessCmd(next);
621 }
622
623
624
625 ProcessCmd(string cmd) {
626
627 DEBUG("ProcessCmd:" + cmd);
628 llMessageLinked(LINK_SET,keyNum,"",NPCKey()); // bboradcast the key on num = -1
629 if(llGetSubString(cmd, 0, 0) != "@") {
630 DEBUG("ignoring");
631 TimerEvent(QUICK); // this is so we do not recurse the stack
632 STATE = NULL;
633 return;
634 }
635
636 list data = llParseString2List(cmd, ["="], []);
637 npcAction = llToLower(llStringTrim(llList2String(data, 0), STRING_TRIM));
638
639 DEBUG("Action:" + npcAction);
640 npcParams = llStringTrim(llList2String(data, 1), STRING_TRIM);
641 DEBUG("Params:" + npcParams);
642
643 @commands;
644
645 ProcessSensor();
646
647 if(npcAction == "@spawn") {
648 DEBUG("@spawn npcParams ");
649 list spawnData = llParseString2List(npcParams, ["|"], []);
650 sNPCName =llList2String(spawnData, 0); // V 1.6 name in RAM
651
652 vInitialPos = (vector) llList2String(spawnData, 1);
653 DEBUG("Coords for NPC at " + (string) vInitialPos);
654 StateSpawn();
655 return;
656 }
657
658 if(! avatarPresent){
659 DoNobodyHome();
660 DEBUG("No avatar nearby");
661 STATE = NULL;
662 return;
663 } else {
664 if( NPCKey() == NULL_KEY) {
665 StateSpawn();
666 }
667 }
668
669
670
671
672 if(npcAction == "@stop") {
673 DoStop();
674 STATE = NULL;
675 return;
676 }
677 else if(npcAction == "@goto") {
678 DEBUG("goto");
679 integer lastIdx = llGetListLength(lNpcCommandList)-1;
680 lNpcCommandList = llDeleteSubList(lNpcCommandList, lastIdx, lastIdx);
681 // Wind commands till goto label.
682 @wind;
683 string next1 = llList2String(lNpcCommandList, 0);
684 lNpcCommandList = llDeleteSubList(lNpcCommandList, 0, 0);
685 lNpcCommandList += next1;
686 if(next1 != npcParams) jump wind;
687 // Wind the label too.
688 next1 = llList2String(lNpcCommandList, 0);
689 lNpcCommandList = llDeleteSubList(lNpcCommandList, 0, 0);
690 lNpcCommandList += next1;
691 // Get next command.
692 list data1 = llParseString2List(next1, ["="], []);
693 npcAction = llToLower(llStringTrim(llList2String(data1, 0), STRING_TRIM));
694 npcParams = llStringTrim(llList2String(data1, 1), STRING_TRIM);
695 // Reschedule.
696 jump commands;
697 }
698 else if(npcAction == "@sound") {
699 DEBUG("sound");
700 llTriggerSound(npcParams,1.0);
701 }
702 else if(npcAction == "@randsound") {
703 DEBUG("@randsound");
705 integer rand = llCeil(llFrand(N)) -1; // pick a random sound
707 llTriggerSound(toPlay,1.0);
708 }
709 else if(npcAction == "@walk") {
710 DEBUG("@walk");
711 GetDest(npcParams);
712 walkstate = 1;// walking
713 NPCWalkOption = OS_NPC_NO_FLY ;
714 StateWalk();
715 return;
716 }
717 else if(npcAction == "@fly") {
718 GetDest(npcParams);
719 walkstate = 2;// flying
720 NPCWalkOption = OS_NPC_FLY ;
721 StateWalk();
722 return;
723 }
724 else if(npcAction == "@run") {
725 DEBUG("@run");
726 GetDest(npcParams);
727 walkstate = 3;// running
728 NPCWalkOption = OS_NPC_NO_FLY | OS_NPC_RUNNING;
729 StateWalk();
730 return;
731 }
732 else if(npcAction == "@land") {
733 DEBUG("@land");
734 GetDest(npcParams);
735 walkstate = 4;// landing
736 NPCWalkOption= OS_NPC_FLY | OS_NPC_LAND_AT_TARGET ;
737 StateWalk();
738 return;
739 }
740 else if(npcAction == "@say") {
741 DEBUG("@say " + npcParams);
742 osNpcSay(NPCKey(), 0, npcParams);
743 }
744 else if(npcAction == "@shout") {
745 DEBUG("@shout");
746 osNpcShout(NPCKey(),0, npcParams);
747 }
748 else if(npcAction == "@whisper") {
749 DEBUG("@whisper " + npcParams);
750 osNpcWhisper(NPCKey(),0, npcParams);
751 }
752 // speak a command on a channel, so you can open doors and control stuff.
753 else if(npcAction == "@cmd") {
754 DEBUG("@cmd");
755 list dataToSpeak = llParseString2List(npcParams, ["|"], []);
756 string channel = llList2String(dataToSpeak,0);
757 DEBUG("Channel:"+(string) channel);
758 integer iChannel = (integer) channel;
759 string stringToSpeak = llList2String(dataToSpeak,1);
760 llSay(iChannel, stringToSpeak);
761 }
762 // stop everything
763 else if(npcAction == "@pause") {
764 RAMPause = (float) npcParams;
765 DoPause();
766 return;
767 }
768 else if(npcAction == "@wander") {
769 list wanderData = llParseString2List(npcParams, ["|"], []);
770 RAMwd = (float) llList2String(wanderData, 0);
771 RAMwc = (integer) llList2String(wanderData, 1);
772 vDestPos = osNpcGetPos(NPCKey()); // set the wander start
773 DEBUG("Starting at " + (string) vDestPos);
774 StateWander();
775 return;
776 }
777 else if(npcAction == "@rotate") {
778 RAMrot = (float) npcParams;
779 DoRotate();
780 }
781 else if(npcAction == "@sit") {
782 RAMsit= npcParams;
783 StateSit();
784 return;
785 }
786 else if(npcAction == "@touch") {
787 RAMtouch= npcParams;
788 StateTouch();
789 return;
790 }
791 else if(npcAction == "@stand") {
792 DoStand();
793 }
794 else if(npcAction == "@delete") {
795 DoDelete();
796 SetStop(TRUE); // Link controlled - we mnust have a @go to continue with notecards
797 return;
798 }
799 else if(npcAction == "@animate") {
800 list animateData = llParseString2List(npcParams, ["|"], []);
801 RAManimationName = llList2String(animateData, 0);
802 RAManimationTime = (float) llList2String(animateData, 1);
803 StateAnimate();
804 return;
805 }
806 else if(npcAction == "@appearance" ){
807 DoAppearance(npcParams);
808 }
809 else if(npcAction =="@speed") {
810 DoSpeed(npcParams);
811 }
812 else if(npcAction =="@notecard") {
813 DoNewNote(npcParams);
814 Notecard = npcParams;
815 }
816 else if(npcAction == "@attach")
817 {
818 DoAttach(npcParams);
819 }
820 else if(npcAction == "@teleport")
821 {
822 DoTeleport(npcParams);
823 }
824 else if(npcAction == "@reset")
825 {
826 DoDelete();
827 SetStop(FALSE); // a @resst will restart the original !Path after deleting the notecard.
828 }
829
830 STATE = NULL;
831 TimerEvent(QUICK); // yeah I know, not possible this fast, we just go as fast as we can go - this is so we do not recurse the stack
832 }
833
834
835
836 /////////////////// UTILITY Functions, not state-like //////////////////
837
838 // DEBUG(string) will chat a string or display it as hovertext if debug == TRUE
839 DEBUG(string str) {
840 if(debug && ! LSLEditor)
841 llOwnerSay( str); // Send the owner debug info
842 if(debug && LSLEditor)
843 llSay(0, str); // Send to the Console in LSLEDitor
844 if(iTitleText) {
845 llSetText(str,<1.0,1.0,1.0>,1.0); // show hovertext
846
847 }
848 }
849
850 GetDest(string npcParams) {
851 list dest = llParseString2List(npcParams, ["<", ",", ">"], []);
852 vDestPos.x = llList2Float(dest, 0);
853 vDestPos.y = llList2Float(dest, 1);
854 vDestPos.z = llList2Float(dest, 2);
855 }
856
857 NPCReadNoteCard(string Note) {
858 DEBUG("NPCReadNoteCard");
859 lNpcCommandList = llParseString2List(osGetNotecard(Note), ["\n"], []);
860 }
861
862 integer SenseAvatar()
863 {
864 //Returns a strided list of the UUID, position, and name of each avatar in the region
865 list avatars = llGetAgentList(AGENT_LIST_REGION ,[]);
866 integer numOfAvatars = llGetListLength(avatars);
867 if(numOfAvatars == 0)
868 {
869 DEBUG("No people");
870 return 0;
871 }
872 //DEBUG("Located " + (string)numOfAvatars + " avatars and NPC's");
873
874 integer nAvatars;
875 integer i;
876 for( i = 0;i < numOfAvatars; i++) {
877 key aviKey = llList2Key(avatars,i);
878 if(!osIsNpc(aviKey)) {
879 list detail = llGetObjectDetails(aviKey,[OBJECT_POS]);
880 vector pos = llList2Vector(detail,0);
881 float dist = llVecDist(pos, llGetPos());
882 if(dist < RANGE)
883 {
884 nAvatars++;
885 DEBUG("In range:" + llKey2Name(aviKey));
886 }
887 }
888 }
889 //DEBUG("Located " + (string) nAvatars + " avatars");
890 return nAvatars;
891 }
892
893 // return TRUE if the avatar is owner when private is set, or TRUE if the avatar is in the same group and GROUP is set.
894 integer checkPerms() {
895
896 integer group = (integer) KeyValueGet("pr");
897 if(! group)
898 priPub = "Owner Only";
899 else
900 priPub = "Group";
901
902
904 kUserKey = llDetectedKey(0);
905 return TRUE;
906 }
907
908 if( group && llDetectedGroup(0)) {
909 kUserKey = llDetectedKey(0);
910 return TRUE;
911 }
912 kUserKey = llDetectedKey(0);
913 return FALSE;
914 }
915
916
917
918 NPCAnimate(string anim)
919 {
920 DEBUG("Start Anim: " + anim);
922
923 if(lastANIM != anim) {
924 if(llStringLength(lastANIM)) {
925 osNpcStopAnimation(NPCKey(), lastANIM);
926 }
927 osNpcPlayAnimation(NPCKey(), anim);
928 lastANIM = anim;
929 }
930 } else {
931 llSay(DEBUG_CHANNEL, "No animation named " + anim);
932 }
933 }
934
935 // Kill a NPC by Name
936 Kill(string param)
937 {
938 integer count;
939 list avatars = osGetAvatarList(); // Returns a strided list of the UUID, position, and name of each avatar in the region except the owner.\
940 integer i;
941 integer j = llGetListLength(avatars);
942 for (i=0 ; i <= j; i+=3){
943
944 string desired = llList2String(avatars,i+2);
945 desired = llStringTrim(desired,STRING_TRIM); // should not be needed but is needed
946
947 if(desired == param){
948 vector v = llList2Vector(avatars,i+1);
949 key target = llList2Key(avatars,i); // get the UUID of the avatar
950 osNpcRemove(target);
951
952 llOwnerSay("Removed " + param+ " at location " + (string) v);
953 count++;
954 }
955 }
956
957 NPCEnabled = FALSE; // not in world
958 SaveKey(NULL_KEY ); // Rev 4.4
959
960 if(count)
961 llOwnerSay("Removed " + (string) count + " NPC's");
962 else
963 llOwnerSay("Could not locate " + param);
964 }
965
966
967 // return a String for the position we are at. Strings used as the caller wants strings
968 string Pos()
969 {
970 vector where = llGetPos(); // find the box position
971
972 where.z += OffsetZ; // use the ground position + an offset
973
974 if(LSLEditor)
975 where = <128,128,31 + llFrand(1)>; // force center of sim when editing
976
977 // if attached the height will be too high by 1/2 the agent size
978 if(llGetAttached()) {
980 float Z = size.z;
981 where.z -= Z/2;
982 }
983
984 // DEBUG("Pos= " + (string) where);
985 return (string) where;
986 }
987
988 // setup a menu with a timer for timeouts, called by all make*()
989 menu()
990 {
991 llListenRemove(iHandle);
992 iChannel = llCeil(llFrand(100000) + 20000);
993 iHandle = llListen(iChannel,"","","");
994 TimerEvent(30.0);
995 MENU = TRUE;
996 }
997
998 // make a text box
999 makeText(string Param)
1000 {
1001 menu();
1002 llTextBox(kUserKey, Param, iChannel);
1003 }
1004
1005 // top level menu
1006 makeMainMenu()
1007 {
1008 menu();
1009 list buttons = ["Appearance","Recording","Save","Help","-","Erase RAM", priPub,relAbs,"-","Stop NPC",mSensor,"Start NPC"];
1010 llDialog(kUserKey,(string) llGetListLength(lCommands) + " Records",buttons,iChannel);
1011 }
1012
1013
1014 // Rev 1.4
1015 // top level menu for non group/ non owners
1016 makeUserMenu()
1017 {
1018 if(!allowUsers) return;
1019
1020 menu();
1021 list buttons = ["Start NPC","Stop NPC"];
1022 llDialog(kUserKey,"Choose",buttons,iChannel);
1023 }
1024
1025
1026
1027 // programmable menu for @commands
1028 makeMenu(list buttons)
1029 {
1030 menu();
1031 llDialog(kUserKey,(string) llGetListLength(lCommands) + " Record",buttons,iChannel);
1032 }
1033
1034
1035 // make one or two text boxes with prompts
1036 Text(string cmd, string p1, string p2)
1037 {
1038 sCommand = cmd;
1039 sParam2 = "";
1040 if(llStringLength(p2))
1041 sParam2 = p2;
1042
1043 makeText(p1);
1044 }
1045
1046 // Set the Avatar Present flag - if sensors are off and we are force run, there will be one present.
1047 ProcessSensor()
1048 {
1049 integer SensorOn;
1050 if("on" == KeyValueGet("se"))
1051 {
1052 SensorOn = TRUE; // we need to scan for avatars
1053 } else {
1054 SensorOn = FALSE; // we need to scan for avatars
1055 }
1056 DEBUG("Sensor:" + (string) SensorOn);
1057
1058 integer n = SenseAvatar();
1059
1060 DEBUG("Avatars:" + (string) n);
1061 if(SensorOn && n)
1062 avatarPresent = TRUE; // someone is here and we need to tell the system to run
1063 else if(SensorOn && !n)
1064 avatarPresent = FALSE; // someone is not here and we need to tell the system to stop
1065 else { // sensor is off, lete see if there is a NPC. If so, we are ON
1066 // DEBUG("NPCEnabled:" + (string) NPCEnabled);
1067
1068 if(NPCEnabled)
1069 avatarPresent = TRUE;
1070 else
1071 avatarPresent = FALSE;
1072 }
1073
1074 // start up from when when no one is near
1075 if(avatarPresent && STATE == NobodyHome)
1076 STATE = NULL;
1077
1078 DEBUG("Avatar Present: " + (string) avatarPresent);
1079 }
1080
1081 vector CirclePoint(float radius) {
1082 float x = llFrand(radius *2) - radius; // +/- radius, randomized
1083 float y = llFrand(radius *2) - radius; // +/- radius, randomized
1084 return <x, y, 0>; // so this should always happen
1085 }
1086
1087 string KeyValueGet(string var) {
1088 list dVars = llParseString2List(llGetObjectDesc(), ["&"], []);
1089 do {
1090 list data = llParseString2List(llList2String(dVars, 0), ["="], []);
1091 string k = llList2String(data, 0);
1092 if(k != var) jump continue;
1093 //DEBUG("got " + var + " = " + llList2String(data, 1));
1094 return llList2String(data, 1);
1095 @continue;
1096 dVars = llDeleteSubList(dVars, 0, 0);
1097 } while(llGetListLength(dVars));
1098 return "";
1099 }
1100
1101 KeyValueSet(string var, string val) {
1102
1103 //DEBUG("set " + var + " = " + val);
1104 list dVars = llParseString2List(llGetObjectDesc(), ["&"], []);
1105 if(llGetListLength(dVars) == 0)
1106 {
1107 llSetObjectDesc(var + "=" + val);
1108 return;
1109 }
1110 list result = [];
1111 do {
1112 list data = llParseString2List(llList2String(dVars, 0), ["="], []);
1113 string k = llList2String(data, 0);
1114 if(k == "") jump continue;
1115 if(k == var && val == "") jump continue;
1116 if(k == var) {
1117 result += k + "=" + val;
1118 val = "";
1119 jump continue;
1120 }
1121 string v = llList2String(data, 1);
1122 if(v == "") jump continue;
1123 result += k + "=" + v;
1124 @continue;
1125 dVars = llDeleteSubList(dVars, 0, 0);
1126 } while(llGetListLength(dVars));
1127 if(val != "") result += var + "=" + val;
1129 }
1130
1131
1132 // clear RAM
1133 Clr() {
1134
1135 lCommands = [];
1136 llOwnerSay("RAM Memory cleared. Notecards, if any, are not modified.");
1137 makeMainMenu();
1138 }
1139
1140 integer checkNoteCards()
1141 {
1142 // Check that they have saved an Appeaance and Path notecard
1143 integer num = llGetInventoryNumber(INVENTORY_NOTECARD); // how many notecards overall
1144
1145 integer i;
1146 integer count;
1147 for (; i < num; i++){
1149 count++;
1151 count++;
1152 }
1153 DEBUG("Checked " + (string) count + " Notecards");
1154 // if we have both, run the NPC
1155 return count;
1156 }
1157
1158 Update(string SName) {
1159
1160 // delete all NPC* scripts except myself
1161 integer i;
1163 for (i = 0; i < j; i++) {
1165 string match = llGetSubString(targetName,0,2);
1166
1167 if(match == SName && llGetScriptName() != targetName){
1168 llOwnerSay("Upgrading " + targetName);
1169 if(! LSLEditor){ // lets not kill the editor
1170 llRemoveInventory(targetName);
1171 }
1172 }
1173 }
1174 }
1175
1176 // Get all default saved params from the Description
1177 GetSwitches()
1178 {
1179 string rA = KeyValueGet("co"); // Get the remembered menu setting for Abs Vs Relative
1180 if(rA == "A")
1181 relAbs = "Absolute";
1182 else if(rA == "R")
1183 relAbs = "Relative";
1184 else
1185 relAbs = "Absolute";
1186
1187
1188 // reenable NPC if sensor is on.
1189 if("on" == KeyValueGet("se"))
1190 {
1191 NPCEnabled = TRUE;
1192 mSensor = "Sense is On";
1193 ProcessSensor(); // fake 1 avatar to get it rezzed
1194 } else {
1195 mSensor = "Sense is Off";
1196 }
1197 }
1198
1199
1200 SaveKey(key akey)
1201 {
1202 DEBUG("Saving Key of " + (string) akey);
1203 KeyValueSet("key", akey);
1204 if(akey != (key) KeyValueGet("key") )
1205 {
1206 DEBUG("Fatal error, cannot save key");
1207 }
1208 gNpcKey = akey;
1209 }
1210
1211
1212 key NPCKey()
1213 {
1214 key akey = gNpcKey; // from cached copy
1215 // gNpcKey saves a lot of CPU processing by caching the key, if blank we get it from the description
1216 if(gNpcKey == NULL_KEY)
1217 {
1218 //DEBUG("Get DKey");
1219 akey = KeyValueGet("key"); // from Description of the prim
1220 }
1221 // DEBUG("NPC KEY:" + (string) akey);
1222 return akey;
1223 }
1224
1225
1226 /////////////////// CODE BEGINS //////////////////
1227
1228
1229 default
1230 {
1231 changed(integer change) {
1232 if(change & CHANGED_REGION_START) {
1234 }
1235 }
1236
1237 on_rez(integer start_param)
1238 {
1240 }
1241
1242 state_entry() {
1243
1244 llSetText("",<1,1,1>,1.0); // clr all hovertext- we may not be using it.
1245 DoDelete(); // kill any NPC that is out running
1246 Update("NPC"); // If dragged and ropped into a prim with any script named "NPC...", this will replace it.
1247 GetSwitches(); // Get all default saved params from the Description
1248
1249 // 4.1 allow listeners to send us commands
1250 if(allowListener)
1251 llListen(link_Channel,"","","");
1252 TimerEvent(TIMER);
1253 }
1254
1255
1257 { // if touched, make a menu
1258
1259 if(checkPerms()) {
1260 if(RecordPath == STATE) {
1261 makeMenu(lAtButtons);
1262 } else {
1263 makeMainMenu();
1264 }
1265 } else {
1266 makeUserMenu();
1267 }
1268 }
1269
1270 // menu listener
1271 listen(integer iChannel, string name, key id, string message) {
1272
1273 // process @commands that come in via the listener
1274 if(iChannel == link_Channel)
1275 {
1276 ParseMsg(message);
1277 return;
1278 }
1279
1280 if(MENU) {
1281 llListenRemove(iHandle);
1282 MENU = 0; // menu is off
1283 iHandle = 0;
1284 }
1285
1286 if(message == "Stop NPC")
1287 {
1288 lNpcCommandList = []; // force reload of notecard
1289 NPCEnabled = FALSE;
1290 if(NPCKey() != NULL_KEY){
1291 Kill(sNPCName);
1292 sNPCName = "";
1293 } else {
1294 bNPC_STOP = TRUE;
1295 makeText("Enter name of an NPC to stop");
1296 }
1297 }
1298 else if(message == "Menu" ) {
1299 makeMainMenu();
1300 }
1301 else if(message == "Erase RAM"){
1302 Clr();
1303 }
1304 else if(message == "Relative"){
1305 relAbs = "Absolute";
1306 KeyValueSet("co","A"); // remember coordinates = A
1307 Clr();
1308 }
1309 else if(message == "Absolute"){
1310 relAbs = "Relative";
1311 KeyValueSet("co","R"); // remember coordinates = R
1312 Clr();
1313 }
1314 else if(message == "Recording"){
1315 DoMenuForCommands(); // show them the recording menu
1316 }
1317 else if(message == "Owner Only") {
1318 priPub = "Group";
1319 KeyValueSet("pr","1");
1320
1321 llOwnerSay("Group members have control");
1322 makeMainMenu();
1323 }
1324 else if(message == "Group") {
1325 priPub = "Owner Only";
1326 KeyValueSet("pr","0");
1327 llOwnerSay("Only you have control");
1328 makeMainMenu();
1329 }
1330 else if(message == "Sense is On") {
1331 mSensor ="Sense is Off";
1332 KeyValueSet("se", "off");
1333 llOwnerSay(mSensor);
1334 makeMainMenu();
1335 }
1336 else if(message == "Sense is Off") {
1337 mSensor ="Sense is On";
1338 llOwnerSay(mSensor);
1339 KeyValueSet("se", "on");
1340
1341 NPCEnabled = TRUE;
1342
1343 integer count = checkNoteCards();
1344 if(count >= 2) {
1345 DEBUG("Notecards ok, DoProcessNPCLine");
1346 DoProcessNPCLine();
1347 return;
1348 }
1349 if(LSLEditor) {
1350 DoProcessNPCLine();
1351 return;
1352 }
1353
1354 llOwnerSay("You have not saved a recording and/or appearance, so you cannot start a NPC");
1355 makeMainMenu();
1356 }
1357 else if(message == "Appearance") {
1358 llRemoveInventory(Appearance); // delete the notecard
1359 osAgentSaveAppearance(kUserKey,Appearance); // make the ntecard
1360 llOwnerSay("Your outfit has been saved");
1361 makeMainMenu();
1362 }
1363 else if(message == "Save") {
1364 if(llGetListLength(lCommands) == 0) {
1365 llOwnerSay("Nothing recorded, you need to make a recording first");
1366 makeMainMenu();
1367 return;
1368 }
1369 DoSave();
1370 }
1371 else if(message == "Help"){
1372 llLoadURL(kUserKey,"Click to view help","http://www.outworldz.com/opensim/posts/NPC/");
1373 makeMainMenu();
1374 }
1375 else if(message == "Start NPC") {
1376 integer count = checkNoteCards();
1377
1378 NPCEnabled = TRUE;
1379
1380 if(LSLEditor) {
1381 DoProcessNPCLine();
1382 return;
1383 }
1384
1385 if(count >= 2) {
1386 DEBUG("Notecards approved , calling DoProcessNPCLine");
1387 SetStop(FALSE); // Let's run the notecard
1388 DoProcessNPCLine();
1389 return;
1390 }
1391
1392 llOwnerSay("You have not saved a recording or maybe an appearance, so we cannot start a NPC");
1393
1394 }
1395 else if(bNPC_STOP){
1396 bNPC_STOP = FALSE;
1397 Kill(message);
1398 }
1399 else if(message == ">>"){
1400 makeMenu(lMenu2);
1401 }
1402 else if(message == ">>>"){
1403 makeMenu(lMenu3);
1404 }
1405 else if(message == "<<") {
1406 makeMenu(lAtButtons);
1407 }
1408 else if(message == "<<<") {
1409 makeMenu(lMenu2);
1410 }
1411 else if(message == "@comment"){
1412 Text("# ","Enter a comment","");
1413 }
1414 else if(message == "@stop"){
1415 lCommands += "@stop"+ "\n";
1416 makeMenu(lAtButtons);
1417 }
1418 else if(message == "@run"){
1419 lCommands += "@run=" + Pos() + "\n";
1420 llOwnerSay("Recorded position: " + Pos());
1421 makeMenu(lAtButtons);
1422 }
1423 else if(message == "@fly"){
1424 lCommands += "@fly=" + Pos() + "\n";
1425 llOwnerSay("Recorded position: " + Pos());
1426 makeMenu(lAtButtons);
1427 }
1428 else if(message == "@land"){
1429 lCommands += "@land=" + Pos() + "\n";
1430 llOwnerSay("Recorded position: " + Pos());
1431 makeMenu(lAtButtons);
1432 }
1433 else if(message == "@walk") {
1434 lCommands += "@walk=" + Pos() + "\n";
1435 llOwnerSay("Recorded position: " + Pos());
1436 makeMenu(lAtButtons);
1437 }
1438 else if(message == "@stop"){
1439 lCommands += "@stop"+ "\n";
1440 makeMenu(lAtButtons);
1441 }
1442 else if(message == "@sound"){
1443 Text("@sound=","Enter a sound name or UUID to trigger","");
1444 }
1445 else if(message == "@randsound"){
1446 lCommands += "@randsound"+ "\n";
1447 makeMenu(lAtButtons);
1448 }
1449 else if(message == "@say") {
1450 Text("@say=","Enter what the NPC will say","");
1451 }
1452 else if(message == "@whisper"){
1453 Text("@whisper=","Enter what the NPC will whisper","");
1454 }
1455 else if(message == "@shout"){
1456 Text("@shout=","Enter what the NPC will shout","");
1457 }
1458 else if(message == "@wander") {
1459 Text("@wander=","Enter radius to wander","Enter number of wanders");
1460 }
1461 else if(message == "@pause") {
1462 Text("@pause=","Enter time to pause","");
1463 }
1464 else if(message == "@rotate") {
1465 Text("@rotate=","Enter degrees to rotate","");
1466 }
1467 else if(message == "@sit"){
1468 Text("@sit=","Enter name of object to sit on","");
1469 }
1470 else if(message == "@teleport"){
1471 lCommands += "@teleport=" + Pos() + "\n";
1472 llOwnerSay("teleport to position: " + Pos());
1473 makeMenu(lMenu3);
1474 }
1475 else if(message == "@touch"){
1476 Text("@touch=","Enter name of object to touch","");
1477 }
1478 else if(message == "@cmd"){
1479 Text("@cmd=","Enter cjhannel to speak on","Enter text to speak");
1480 }
1481 else if(message == "@stand"){
1482 lCommands += "@stand\n";
1483 llOwnerSay("Stand Recorded");
1484 makeMenu(lAtButtons);
1485 }
1486 else if(message == "@animate"){
1487 Text("@animate=","Enter animation name to play","Enter time to play the animation");
1488 }
1489 else if(message == "@attach"){
1490 Text("@animate=","Enter inventory name to attach","Enter number of the attachment point (1-40)");
1491 }
1492 else if(message == "@speed"){
1493 Text("@speed=","Enter a speed for the NPC, 1=100% normal speed, 0.5=50% speed","");
1494 }
1495
1496
1497 // Save NPC name
1498 else if(MakeNotecard == STATE) {
1499 sNPCName = message; // in case we need to kill it.
1500
1501 vector vDest = (vector) Pos();
1502
1503 if(relAbs == "Relative")
1504 {
1505 vDest -= llGetPos(); // just an offset for relative
1506 }
1507 sNotecard = "@spawn=" + message + "|" + (string) vDest + "\n";
1508 integer i;
1509 integer j = llGetListLength(lCommands);
1510 for (; i < j; i++){
1511 // get the command to save to the notecard
1512 string line = llList2String(lCommands,i);
1513 if(relAbs == "Absolute") {
1514 sNotecard += line; // add the un-modified string to the notecard
1515 } else {
1516 // since we have to record absolute coords since we do not know where the box goes until they press Save,
1517 // we process the absolute to relative conversion for walks here
1518 list parts = llParseString2List(line,["="],[]); //get the @command
1519
1520 if(llList2String(parts,0) == "@walk") {
1521 vector vec = (vector) llList2String(parts,1) - llGetPos();
1522 sNotecard += "@walk=" + (string) vec + "\n";
1523 }
1524 else if(llList2String(parts,0) == "@fly") {
1525 vector vec = (vector) llList2String(parts,1) - llGetPos();
1526 sNotecard += "@fly=" + (string) vec + "\n";
1527 }
1528 else if(llList2String(parts,0) == "@run") {
1529 vector vec = (vector) llList2String(parts,1) - llGetPos();
1530 sNotecard += "@run=" + (string) vec + "\n";
1531 }
1532 else if(llList2String(parts,0) == "@land") {
1533 vector vec = (vector) llList2String(parts,1) - llGetPos();
1534 sNotecard += "@land=" + (string) vec + "\n";
1535 }
1536 else {
1537 sNotecard += line; // add the un-modified string to the notecard
1538 }
1539 }
1540 }
1541 llRemoveInventory(Notecard); // delete the old notecard
1542 osMakeNotecard(Notecard,sNotecard); // Makes the notecard.
1543 llSay(0,sNotecard);
1544 llOwnerSay("Commands notecard has been written");
1545 STATE = NULL;
1546 } // MakeNotecard
1547
1548 else if(! llStringLength(sParam2)) {
1549 lCommands += sCommand + message + "\n";
1550 llOwnerSay("Recorded");
1551 makeMenu(lAtButtons);
1552 }
1553 else if(llStringLength(sParam2)){
1554 sCommand = sCommand + message + "|";
1555 llOwnerSay("Recorded");
1556 makeText(sParam2);
1557 sParam2 = "";
1558 }
1559
1560 }
1561
1562
1563
1564 timer(){
1565 // DEBUG("tick");
1566
1567 // if llDialog is up, kill the listener for the dialog box.
1568 if(iHandle) {
1569 llOwnerSay("Menu timed out");
1570 llListenRemove(iHandle);
1571 iHandle = 0;
1572 return; // ^^^^^^^^^^^^^^^^^^^^^^^
1573 }
1574 // if NoBodyHome, we are sensing for an avatar
1575 else if(NobodyHome == STATE) {
1576 ProcessSensor();
1577 return;
1578 }
1579 // if we are spawning, we need time to rez the NPC, then start processing NPC Commands.
1580 else if(Spawning == STATE) {
1581 STATE = NULL;
1582 TimerEvent(TIMER);
1583 }
1584 // We end aniamtions with a timer
1585 else if(Animate == STATE){
1586 NPCAnimate(STAND);
1587 TimerEvent(TIMER);
1588 }
1589
1590 else if(Walking == STATE) {
1591 if(--iWaitCounter) {
1592 DEBUG("still walking...");
1593 if(llVecDist(osNpcGetPos(NPCKey()), newDest) > MAXDIST) {
1594 return;
1595 }
1596 }
1597
1598 DEBUG("At Destination: " + (string) newDest);
1599
1600 // walk, fly, run, land
1601 if(walkstate == 1) {
1602 NPCAnimate(STAND);
1603 NPCWalkOption = OS_NPC_NO_FLY;
1604 } else if(walkstate == 2) {
1605 // nothing
1606 } else if(walkstate == 3) {
1607 NPCAnimate(STAND);
1608 NPCWalkOption = OS_NPC_NO_FLY;
1609 } else if(walkstate == 4) {
1610 llShout(FLIGHT,"landing");
1611 NPCAnimate(STAND);
1612 NPCWalkOption = OS_NPC_NO_FLY;
1613 }
1614 }
1615 // Wandering timer
1616 else if(Wander == STATE) {
1617 if(--iWaitCounter) { // wait 60 seconds to get to a destination.
1618 if(llVecDist(osNpcGetPos(NPCKey()), vWanderPos) > MAXDIST)
1619 return;
1620 }
1621
1622 // see if wander counter == 0, if so, stop walking, go to stand and process next line
1623 if(RAMwc == 0) {
1624 NPCAnimate(STAND);
1625 DEBUG("Wander ended, calling DoProcessNPCLine");
1626 STATE = NULL;
1627 return;
1628 }
1629 // one less time to wander around
1630 RAMwc--;
1631 NPCAnimate(STAND);
1632 TimerEvent(TIMER);
1633 StateWanderhold();
1634 return;
1635 }
1636 // Wandering requires us to re-wander when we reach a destination
1637 else if(WanderHold == STATE) {
1638 StateWander();
1639 }
1640 else if(DoProcess == STATE) {
1641 TimerEvent(QUICK);
1642 }
1643
1644 STATE = NULL;
1645
1646 // We always process a NPC line at end of timer.
1647 DEBUG("Tick end, calling DoProcessNPCLine");
1648 DoProcessNPCLine();
1649 }
1650
1651 // sensors are used for sitting on prims
1652 // Neo Cortex: added different states to trigger sit or touch
1653 sensor(integer num) {
1654 if(Sit == STATE ) {
1655 osNpcSit(NPCKey(), llDetectedKey(0), OS_NPC_SIT_NOW);
1656 DEBUG("Seated, calling DoProcessNPCLine");
1657
1658 STATE = 0;
1659 } else if(Touch == STATE) {
1660 osNpcTouch(NPCKey(), llDetectedKey(0), LINK_THIS);
1661 DEBUG("Touched, calling DoProcessNPCLine");
1662 STATE = 0;
1663 }
1664 DoProcessNPCLine();
1665 }
1666 no_sensor(){
1667 DEBUG ("no target prim located, calling DoProcessNPCLine");
1668 DoProcessNPCLine();
1669 STATE = NULL;
1670 }
1671
1672
1673 link_message(integer sender, integer num, string str, key id){
1674 if(num == 0)
1675 ParseMsg(str);
1676 if(num == 99)
1678 }
1679
1680 }
1681
1682 // __ END__

HyperGrid Story Nine

Timer script - ended up not being used.

Category: NPC
By : Shin Ingen
Created: 2015-11-24 Edited: 2015-11-23
Worlds: Second Life

1
2 //HYPERGRID STORY NINE
3 // List NPC Listener channels
4 //sara=11 sammy=12 claire=13 jolinda=14 tiny=15 tiny2=16 fireworks=20
5
6 integer gListener; // Listener for handling different channels
7 integer gSimpleMenuChannel; // The channel used for the menu
8 float gTimerInterval = 1; // Keep this at 1 for your sanity.
9 list gTimeoutList;
10 float gTimeElapsed=0;
11 integer gAutofire=FALSE;
12 //============================================================================
13 vector gFacecolor=<1.0,0.0,0.0>; //RED
14 integer gNewTime;
15 integer gOldTime;
16 //==Function that returns a random number for our menu handler channel
17 integer randomchannel() {
18 return((integer)(llFrand(99999.0)*-1));
19 }
20
21 menu(key id, integer channel, string title, list buttons) {
22 llListenRemove(gListener);
23 gListener = llListen(channel,"",id,"");
24 llDialog(id,title,buttons,channel);
25 // TimerEvent for killing the menu listener
26 settimeout("untouched", 30); //call untouched timeout
27 }
28
29 simplemenu(key id) {
30 gSimpleMenuChannel = randomchannel();
31 menu(id,gSimpleMenuChannel,"Select an option",["FIREWORKS","SARA","SAMMY","CLAIRE","JOLINDA","TINY","TINY2","LeadNPC","Finale!","CeaseFire"]);
32 }
33
34 settimeout(string timereventid, float time) {
35 if(gTimeoutList == [])
36 llSetTimerEvent(gTimerInterval);
37 integer identifyerIndex = llListFindList(gTimeoutList, [timereventid]);
38 if(identifyerIndex != -1)
39 gTimeoutList = llDeleteSubList(gTimeoutList, identifyerIndex - 1, identifyerIndex);
40 if(time != 0) {
41 gTimeoutList += time + gTimeElapsed;
42 gTimeoutList += timereventid;
43 }
44 gTimeoutList = llListSort(gTimeoutList, 2, TRUE);
45 }
46
47 timeout(string timereventid)
48 {
49 if(timereventid == "untouched")
50 {
51 llSay(0, "I have been untouched for 30 seconds, killing menu listener!");
52 llListenRemove(gListener); // kill the listener after 30 seconds inactivity
53 }
54 else if(timereventid == "countdown2execute")
55 {
56 float seconds2count = 1800;
57 llSay(0, "I have counted down " + (string)seconds2count + " seconds, repeat countdown and start the show");
58 settimeout("countdown2execute", seconds2count); //call it again to loop.
59 }
60 else if(timereventid == "autofire")
61 {
62 if(gAutofire)
63 {
64 llSay(0,"should fire every 5 seconds.");
65 llRegionSay(20,"Go");
66 settimeout("autofire", 5);
67 }
68 }
69 if(timereventid == "stopautofire")
70 {
71 gAutofire=FALSE;
72 }
73 }
74 timertick()
75 {
76 gTimeElapsed += gTimerInterval;
77 integer i;
78 integer numTimers = llGetListLength(gTimeoutList);
79 for (i = 0; i < numTimers; i += 2)
80 {
81 float triggerTime = llList2Float(gTimeoutList, i);
82 if(triggerTime <= gTimeElapsed)
83 {
84 string timereventid = llList2String(gTimeoutList, i + 1);
85 gTimeoutList = llDeleteSubList(gTimeoutList, i, i + 1);
86 timeout(timereventid);
87 if(timereventid=="countdown2execute")
88 {
89 displaycountdowntext((integer)gTimeElapsed);
90 getdigit((integer)gTimeElapsed);
91 }
92 if(gTimeoutList == [])
94 }
95 else
96 {
97 return;
98 }
99 }
100 }
101
102 displaydigit(integer n, string d){
103 list Facelist; //list depends on your mesh digital readout face number.
104 integer i;
105 if(n==0) Facelist=[ 1,1,1,1,0,1,1 ]; //index @0 | 7 faces per digit
106 if(n==1) Facelist=[ 0,0,1,0,0,1,0 ];
107 if(n==2) Facelist=[ 1,1,0,1,1,1,0 ];
108 if(n==3) Facelist=[ 1,1,1,0,1,1,0 ];
109 if(n==4) Facelist=[ 0,0,1,0,1,1,1 ];
110 if(n==5) Facelist=[ 1,1,1,0,1,0,1 ];
111 if(n==6) Facelist=[ 0,1,1,1,1,0,1 ];
112 if(n==7) Facelist=[ 1,0,1,0,0,1,0 ];
113 if(n==8) Facelist=[ 1,1,1,1,1,1,1 ];
114 if(n==9) Facelist=[ 1,0,1,0,1,1,1 ];
115
116 integer l = llGetLinkNumber() != 0;
117 integer x = llGetNumberOfPrims() + l;
118
119 for (; l < x; ++l){
120 if(llGetLinkName(l) == d){
121 for (i=0;i<7;i++){
122 llSetLinkPrimitiveParamsFast(l,[PRIM_COLOR,i,gFacecolor,llList2Float(Facelist,i)]);
123 }
124 }
125 }
126 }
127
128 getdigit(integer time){
129 string dOne;
130 string dTen;
131 string dHundred;
132 string dThousand;
133
134 integer i;
135
136 if(time<10){
137 dOne=(string)time;
138 dTen="0";
139 dHundred="0";
140 dThousand="0"; // add your digits here
141 }else if(time<100){
142 dOne=llGetSubString((string)time,-1,-1);
143 dTen=llGetSubString((string)time,0,0);
144 dHundred="0";
145 dThousand="0";
146 }else if(time<1000){
147 dOne=llGetSubString((string)time,-1,-1);
148 dTen=llGetSubString((string)time,1,1);
149 dHundred=llGetSubString((string)time,0,0);
150 dThousand="0";
151 }else if(time<10000){
152 dOne=llGetSubString((string)time,-1,-1);
153 dTen=llGetSubString((string)time,2,2);
154 dHundred=llGetSubString((string)time,1,1);
155 dThousand=llGetSubString((string)time,0,0);
156 }
157 displaydigit((integer)dOne, "one");
158 displaydigit((integer)dTen, "ten");
159 displaydigit((integer)dHundred, "hundred");
160 displaydigit((integer)dThousand, "thousand");
161 }
162
163 displaycountdowntext(integer time){
164 gNewTime = time;
165 if(gOldTime < gNewTime){
166 llSetText("Elapsed Ticker: " + (string)gNewTime + " ticks",<1,1,.6>,1.0);
167 gOldTime = gNewTime;
168 }
169 }
170
171 default
172 {
174 {
175 float seconds2count = 3600;
176 settimeout("countdown2execute", seconds2count);
177
178 }
180 {
181 simplemenu(llDetectedKey(0));
182 }
183
184 listen (integer channel, string name, key id, string message)
185 {
186 if(message == "FIREWORKS") {
187 llSay(0,"You selected option FIREWORKS");
188 llRegionSay(20,"Go");
189 //do something else here
190 }
191 else if(message == "SARA")
192 {
193 llSay(0,"You selected option SARA");
194 llRegionSay(11,"@go");
195 //do something else here
196 }
197 else if(message == "SAMMY")
198 {
199 llSay(0,"You selected option SAMMY");
200 llRegionSay(12,"@go");
201 //do something else here
202 }
203 else if(message == "CLAIRE")
204 {
205 llSay(0,"You selected option CLAIRE");
206 llRegionSay(13,"@go");
207 //do something else here
208 }
209 else if(message == "JOLINDA")
210 {
211 llSay(0,"You selected option JOLINDA");
212 llRegionSay(14,"@go");
213 //do something else here
214 }
215 else if(message == "TINY")
216 {
217 llSay(0,"You selected option TINY");
218 llRegionSay(15,"@go");
219 //do something else here
220 }
221 else if(message == "TINY2")
222 {
223 llSay(0,"You selected option TINY2");
224 llRegionSay(16,"@go");
225 //do something else here
226 }
227 else if(message == "LeadNPC")
228 {
229 llSay(0,"You selected option LeadNPC");
230 llRegionSay(10,"@go");
231 //do something else here
232 }
233 else if(message == "Finale!")
234 {
235 llSay(0,"You selected option Finale!");
236 gAutofire=TRUE;
237 settimeout("autofire", 1);
238 settimeout("stopautofire", 90);
239 //do something else here
240 }
241 else if(message == "CeaseFire")
242 {
243 llSay(0,"You selected option CeaseFire");
244 gAutofire=FALSE;
245 //do something else here
246 }
247 simplemenu(id); // refresh menu
248 }
249
250 timer()
251 {
252 timertick();
253 integer MaxTicks = 1800; //Ticks to countdown from
254 //limit the ticker to maximum ticks
255 if(gTimeElapsed >= MaxTicks){
256 gTimeElapsed=0;
257 }
258 integer CountDown = MaxTicks - (integer)gTimeElapsed;
259 integer mm = (CountDown / 60) * 100;
260 integer ss = (CountDown % 60);
261 integer cc = mm + ss;
262 displaycountdowntext((integer)gTimeElapsed);
263 getdigit((integer)cc);
264
265 }
266 }

HyperGrid Story Nine

Timer script - ended up not being used.

Category: NPC
By : Shin Ingen
Created: 2015-11-24 Edited: 2015-11-23
Worlds: Second Life

1
2
3
4 integer debug = 2;
5
6 // Link Numbers of the NPC controller
7 integer namaka =2;
8 integer dylan =3;
9 integer npc1 =4;
10 integer npc2 =5;
11 integer npc3 =6;
12 integer npc4 =7;
13 integer npc5 =8;
14 integer npc6 =9;
15
16 // List of NPC Listener channels
17
18 integer gFireworksChannel =20;
19 integer gTeleportChannel = 21;
20 integer CommandChannel = 23;
21 integer gSimpleMenuChannel = 999; // The channel used for the menu
22
23 integer MaxTicks = 60; // Show length
24 // The repeat the show timer
25
26 list kDance1 = ["Dance1",1]; // Dance
27 list kDance2 = ["Dance2",10]; // Dance
28 list kDance3 = ["Dance3",20]; // Dance
29 list kDance4 = ["Dance4",30]; // Dance
30 list kDance5 = ["Dance5",40]; // Dance
31 list kDance6 = ["Dance6",50]; // Dance
32
33 list kFireworksRepRate = ["Shoot", 2]; // rep rate for finale
34
35 // the following are calculated based on the current timer tick
36 list kFireworksStartsAt = ["StartFireworks", 99999]; // rep rate for finale
37 list kRaiseTeleport = ["BeamMeUp",99999]; // raise the teleports
38 list kStopAutoFire = ["EndFireworks",99999]; // and stops here
39
40 SendMessageLinked(integer npc, string cmd)
41 {
42 llRegionSay(CommandChannel,(string) npc + "|" + cmd);
43 }
44 DoCmd(string message, integer channel)
45 {
46 if(channel == CommandChannel) {
47 Process(message);
48 }
49
50 if(message == "ShowTime") {
51
52 DEBUG("Running");
53 ShowTime();
54 }
55 else if(message == "Finale!")
56 {
57 kFireworksStartsAt = ["StartFireworks", gTimeElapsed+1]; // rep rate for finale
58 kRaiseTeleport = ["BeamMeUp",gTimeElapsed+25]; // raise the teleports
59 kStopAutoFire = ["EndFireworks",gTimeElapsed+30]; // and stops here
60
61 settimeout(kFireworksStartsAt);
62 settimeout(kStopAutoFire);
63 settimeout(kRaiseTeleport);
64 }
65 else if(message == "Stop")
66 {
67 llSay(0,"You selected option Stop");
68 gTimeoutList = [];
69 }
70
71 }
72 // this is a list of all the cycling show bits
73 ShowTime()
74 {
75 DEBUG("Starting Countdown");
76 gTimeElapsed=0;
77
78 settimeout(kDance1);
79 settimeout(kDance2);
80 settimeout(kDance3);
81 settimeout(kDance4);
82 settimeout(kDance5);
83 settimeout(kDance6);
84
85 DEBUG(llDumpList2String(gTimeoutList,":"));
86 }
87
88 Process(string timereventid) {
89
90 if(llList2String(kDance1,0) == timereventid)
91 {
92 DEBUG("Dance1");
93 ;
94 SendMessageLinked(dylan,"@appearance=Dylan");
95 SendMessageLinked(namaka,"@appearance=Namaka");
96
97 SendMessageLinked(npc1,"@appearance=Crispen Enpeacee");
98 SendMessageLinked(npc2,"@appearance=Alessandra Enpeacee");
99 SendMessageLinked(npc3,"@appearance=Sammy Enpeacee");
100 SendMessageLinked(npc4,"@appearance=Jolinda Enpeacee");
101 SendMessageLinked(npc5,"@appearance=Claire Enpeacee");
102 SendMessageLinked(npc6,"@appearance=Marianne Enpeacee");
103
104 SendMessageLinked(LINK_SET,"@notecard=Dance1");
105 }
106 else if(llList2String(kDance2,0) == timereventid)
107 {
108 DEBUG("Dance2");
109 SendMessageLinked(LINK_SET,"@appearance=Alessandra Enpeacee");
110 SendMessageLinked(LINK_SET,"@notecard=Dance2");
111 }
112 else if(llList2String(kDance3,0) == timereventid)
113 {
114 DEBUG("Dance3");
115 SendMessageLinked(LINK_SET,"@appearance=Sammy Enpeacee");
116 SendMessageLinked(LINK_SET,"@notecard=Dance3");
117 }
118 else if(llList2String(kDance4,0)== timereventid)
119 {
120 DEBUG("Dance4");
121 SendMessageLinked(LINK_SET,"@appearance=Jolinda Enpeacee");
122 SendMessageLinked(LINK_SET,"@notecard=Dance4");
123 }
124 else if(llList2String(kDance5,0)== timereventid)
125 {
126 DEBUG("Dance5");
127 SendMessageLinked(LINK_SET,"@appearance=Claire Enpeacee");
128 SendMessageLinked(LINK_SET,"@notecard=Dance5");
129 }
130 else if(llList2String(kDance6,0)== timereventid)
131 {
132 DEBUG("Dance6");
133 SendMessageLinked(LINK_SET,"@appearance=MarianneEnpeacee");
134 SendMessageLinked(LINK_SET,"@notecard=Dance6");
135 }
136 else if(llList2String(kFireworksStartsAt,0) == timereventid)
137 {
138 DEBUG("Shoot!");
139 SendMessageLinked(LINK_SET,"@appearance=Dragon");
140 SendMessageLinked(LINK_SET,"@notecard=Fireworks");
141 llRegionSay(gFireworksChannel,"Go");
142 settimeout(kFireworksRepRate);
143 }
144 else if(llList2String(kFireworksRepRate,0) == timereventid)
145 {
146 DEBUG("Fireworks Start");
147 llRegionSay(gFireworksChannel,"Go");
148
149 SendMessageLinked(npc1,"@appearance=Crispen Enpeacee");
150 SendMessageLinked(npc2,"@appearance=Alessandra Enpeacee");
151 SendMessageLinked(npc3,"@appearance=Sammy Enpeacee");
152 SendMessageLinked(npc4,"@appearance=Jolinda Enpeacee");
153 SendMessageLinked(npc5,"@appearance=Claire Enpeacee");
154 SendMessageLinked(npc6,"@appearance=Marianne Enpeacee");
155
156 SendMessageLinked(dylan,"@appearance=Dylan");
157 SendMessageLinked(namaka,"@appearance=Namaka");
158
159 SendMessageLinked(LINK_SET,"@notecard=Final");
160
161 settimeout(kFireworksRepRate);
162 }
163 else if(llList2String(kStopAutoFire,0) == timereventid)
164 {
165
166 DEBUG("Fireworks End");
167 deletetimeout(kFireworksRepRate);
168 settimeout(kRaiseTeleport);
169 }
170 else if(llList2String(kRaiseTeleport,0) == timereventid)
171 {
172 SendMessageLinked(LINK_SET,"@notecard=Teleport");
173 DEBUG("Teleports Up");
174 llRegionSay(gTeleportChannel,"Up");
175 }
176 }
177
178 integer busy;
179 integer gListener; // Listener for handling different channels
180 list gTimeoutList;
181 float gTimeElapsed=0;
182
183 //============================================================================
184 vector gFacecolor=<1.0,0.0,0.0>; //RED
185
186
187 DEBUG(string msg)
188 {
189 if(debug ==1)
190 llSay(0,llGetScriptName() + ":" + msg);
191 if(debug ==2)
192 llSetText(msg, <1,0,0>,1.0);
193 }
194
195
196
197
198
199 menu(key id, integer channel, string title, list buttons) {
200 llListenRemove(gListener);
201 gListener = llListen(channel,"",id,"");
202 llDialog(id,title,buttons,channel);
203 }
204
205 simplemenu(key id) {
206 menu(id,gSimpleMenuChannel,"Select an option",["ShowTime","Finale","Stop"]);
207 }
208
209 deletetimeout(list events) {
210
211 string timereventid = llList2String(events,0);
212
213 DEBUG("Deleteing timer event " + timereventid);
214 integer identifyerIndex = llListFindList(gTimeoutList, [timereventid]);
215 if(identifyerIndex != -1)
216 gTimeoutList = llDeleteSubList(gTimeoutList, identifyerIndex - 1, identifyerIndex);
217 }
218
219 settimeout(list events)
220 {
221 // unpack the list
222 string timereventid = llList2String(events,0);
223 integer time = llList2Integer(events,1);
224
225 DEBUG("Adding " + timereventid);
226 integer identifyerIndex = llListFindList(gTimeoutList, [timereventid]);
227 if(identifyerIndex != -1)
228 gTimeoutList = llDeleteSubList(gTimeoutList, identifyerIndex - 1, identifyerIndex);
229 if(time != 0) {
230 gTimeoutList += time;
231 gTimeoutList += timereventid;
232 }
233
234 llSetTimerEvent(1.0);
235
236 }
237
238 timertick() {
239 gTimeElapsed ++;
240 //DEBUG((string) gTimeElapsed );
241
242 integer i;
243 integer numTimers = llGetListLength(gTimeoutList);
244
245 //DEBUG((string) (numTimers/2) + " timers");
246 // scan over all queued timers
247 for (i = 0; i < numTimers; i += 2)
248 {
249 integer triggerTime = llList2Integer(gTimeoutList, i);
250
251 //DEBUG("TriggerTime = " + (string)triggerTime);
252 if(triggerTime == gTimeElapsed) {
253
254 string timereventid = llList2String(gTimeoutList, i + 1);
255 //DEBUG("matched " + timereventid);
256
257 gTimeoutList = llDeleteSubList(gTimeoutList, i, i + 1); // they are one-shots, so delete the event that just happened.
258
259 Process(timereventid); // process the callback
260
261
262 if(gTimeoutList == []) {
263 DEBUG("Show over! Starting back up");
264 ShowTime();
265 }
266 }
267 }
268 }
269
270 displaydigit(integer n, string d){
271 list Facelist; //list depends on your mesh digital readout face number.
272 integer i;
273 if(n==0) Facelist=[ 1,1,1,1,0,1,1 ]; //index @0 | 7 faces per digit
274 if(n==1) Facelist=[ 0,0,1,0,0,1,0 ];
275 if(n==2) Facelist=[ 1,1,0,1,1,1,0 ];
276 if(n==3) Facelist=[ 1,1,1,0,1,1,0 ];
277 if(n==4) Facelist=[ 0,0,1,0,1,1,1 ];
278 if(n==5) Facelist=[ 1,1,1,0,1,0,1 ];
279 if(n==6) Facelist=[ 0,1,1,1,1,0,1 ];
280 if(n==7) Facelist=[ 1,0,1,0,0,1,0 ];
281 if(n==8) Facelist=[ 1,1,1,1,1,1,1 ];
282 if(n==9) Facelist=[ 1,0,1,0,1,1,1 ];
283
284 integer l = llGetLinkNumber() != 0;
285 integer x = llGetNumberOfPrims() + l;
286
287 for (; l < x; ++l){
288 if(llGetLinkName(l) == d){
289 for (i=0;i<7;i++){
290 llSetLinkPrimitiveParamsFast(l,[PRIM_COLOR,i,gFacecolor,llList2Float(Facelist,i)]);
291 }
292 }
293 }
294 }
295
296 getdigit(integer time){
297 string dOne;
298 string dTen;
299 string dHundred;
300 string dThousand;
301
302 if(time<10){
303 dOne=(string)time;
304 dTen="0";
305 dHundred="0";
306 dThousand="0"; // add your digits here
307 }else if(time<100){
308 dOne=llGetSubString((string)time,-1,-1);
309 dTen=llGetSubString((string)time,0,0);
310 dHundred="0";
311 dThousand="0";
312 }else if(time<1000){
313 dOne=llGetSubString((string)time,-1,-1);
314 dTen=llGetSubString((string)time,1,1);
315 dHundred=llGetSubString((string)time,0,0);
316 dThousand="0";
317 }else if(time<10000){
318 dOne=llGetSubString((string)time,-1,-1);
319 dTen=llGetSubString((string)time,2,2);
320 dHundred=llGetSubString((string)time,1,1);
321 dThousand=llGetSubString((string)time,0,0);
322 }
323 displaydigit((integer)dOne, "one");
324 displaydigit((integer)dTen, "ten");
325 displaydigit((integer)dHundred, "hundred");
326 displaydigit((integer)dThousand, "thousand");
327
328 //DEBUG(dThousand + dHundred + ":" + dTen + dOne);
329 }
330
331 StopAll()
332 {
333 DEBUG("Stopped");
335
336 }
337
338 default
339 {
341 {
342 llListen(CommandChannel,"","","");
343 getdigit(0);
344 }
345
347 {
348 if( llGetOwner() == llDetectedKey(0)) {
349 if(! busy) {
350 simplemenu(llDetectedKey(0));
351 busy++;
352 } else {
353 busy = FALSE;
354 StopAll();
355 }
356 }
357 }
358
359 link_message(integer sender_number, integer number, string message, key id)
360 {
361 DEBUG("LINK MESSAGE\n" + message);
362 DoCmd(message, number);
363 }
364
365 listen (integer channel, string name, key id, string message)
366 {
367 // !!! add parser for killing the NPC
368 DEBUG("LISTEN\n" + message);
369 DoCmd(message, channel);
370 }
371
372 timer()
373 {
374 timertick();
375
376 //limit the ticker to maximum ticks
377 if(gTimeElapsed >= MaxTicks){
378 gTimeElapsed=0;
379 }
380 integer CountDown = MaxTicks - (integer)gTimeElapsed;
381 integer mm = (CountDown / 60) * 100;
382 integer ss = (CountDown % 60);
383 integer cc = mm + ss;
384
385 getdigit((integer)cc);
386 }
387
388 }

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