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

Search dozens of selected web sites for OpenSim and LSL script

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

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

Home   Show All
Category: Contributor: Creator
NPC All In One NPC Recorder and Player  

All In One NPC Recorder and Player

All in one NPC recorder player set of debug functions. Add these ONLY if you are compileing in LSLEditor.

Category: NPC
By : Ferd Frederix
Created: 2015-10-12 Edited: 2016-11-09
Worlds: OpenSim

the Zip file

Download all files for All In One NPC Recorder and Player
Contents are in zip format, with .LSL (text) source code and LSLEdit (text + Solution) formats.
Get file # 1. !Appearance.txt
Get file # 2. !Appearance2.txt
Get file # 3. !Path.txt
Get file # 4. LSLEditor fake NPC commands.lsl
Get file # 5. Notecard.txt
Get file # 6. NPC Recorder Script v 2.3.lsl
Get file # 7. NPC Recorder Script v 3.2.lsl
Get file # 8. NPC Recorder Script v 3.3.lsl
Get file # 9. NPC Recorder Script v 3.4.lsl
Get file # 10. NPC Recorder Script v 3.5.lsl
Get file # 11. NPC Recorder Script v 3.6.lsl
Get file # 12. NPC Recorder Script v 3.8.lsl
Get file # 13. NPC Recorder Script v 3.9.lsl
Get file # 14. NPC Recorder Script v 4.0.lsl
Get file # 15. NPC Recorder Script v 4.1.lsl
Get file # 16. NPC Recorder Script v 4.2.lsl
Get file # 17. NPC Recorder Script v 4.3.lsl
Get file # 18. NPC Recorder Script v 4.4.lsl
Get file # 19. NPC Recorder Script v 4.5.lsl
Get file # 20. NPC Recorder Script v 4.6.lsl
Get file # 21. NPC Recorder Script V 4.7.lsl
Get file # 22. NPC Recorder Script V4.8.lsl
Get file # 23. NPC Recorder Script V4.9.lsl
Get file # 24. NPC Recorder Script V5.0.lsl
Get file # 25. NPC Recorder Script V5.1.lsl
Get file # 26. Chat commands.lsl
Get file # 27. Collision test.lsl
Get file # 28. Listener for things that appear and disappear.lsl
Get file # 29. Stop Processing.lsl
Get file # 30. Touch to Go.lsl
Get file # 31. Touch Trigger NPC.lsl

This script by Ferd Frederix may be used in any manner, modified, and republished.  Unless specified otherwise, my scripts are always free and open source.  Objects made with these scripts may be sold with no restrictions.  All I ask is that you point others to this location should they ask you about it and to not sell this script, unless it is for $0 L. Please help improve my work by reporting bugs and improvements.

1 // IMPORTANT NOTE
2 // DELETE ALL these lines out for Opensim, leave uncommented for testing in LSLEditor
3
4 integer OS_NPC_CREATOR_OWNED = 1;
5 integer OS_NPC_SIT_NOW = 1;
6 integer OS_NPC_SENSE_AS_AGENT = 2;
7 integer OS_NPC_NO_FLY = 3;
8 integer OS_NPC_NOT_OWNED = 4;
9 integer OS_NPC_RUNNING = 5;
10 integer OS_NPC_LAND_AT_TARGET = 6;
11 integer OS_NPC_FLY = 7;
12
13
15 return TRUE;
16 }
17
18 osNpcStand(key npc) {
19 llOwnerSay("Standing");
20 }
22 vector vDestPos;
23 vDestPos.x += llFrand(1.0); // some randomness for debugging
24 llOwnerSay("Reached " + (string) vDestPos);
25 return vDestPos;
26 }
27 osNpcMoveToTarget(key npc, vector target, integer options){
28 llSay(0,"Moving to " + (string) target);
29 }
30 key osNpcCreate(string firstname, string lastname, vector position, string cloneFrom, integer options) {
31 llSay(0,"Creating NPC " + firstname + " " + lastname + " at " + (string) position);
32 return (key) "12345000-0000-0000-0000-0000000000002";
33 }
34 osNpcLoadAppearance(key npc, string notecard) {
35 llSay(0,"Load notecard " + notecard);
36 }
37 osNpcPlayAnimation(key npc, string animation) {
38 llSay(0,"Playing animation " + animation);
39 }
40 osNpcStopAnimation(key npc, string animation) {
41 llSay(0,"Stopped animation " + animation);
42 }
43 osNpcSay(key npc, integer iChannel, string message) {
44 llSay(0,"Saying " + message);
45 }
46 osNpcWhisper(key npc, integer iChannel, string message) {
47 llSay(0,"Whispering " + message);
48 }
49 osNpcShout(key npc, integer iChannel, string message) {
50 llSay(0,"Shouting " + message);
51 }
52 osNpcSit(key npc, key target, integer options) {
53 llSay(0,"Sat on " +target);
54 }
55 osNpcSetRot(key npc, rotation rot) {
56 llSay(0,"Set rotation of NPC to " + (string) rot);
57 }
59 llSay(0,"Created Notecard " + notecard);
60 }
61 osAgentSaveAppearance(key avatar, string notecard) {
62 llSay(0,"Created Notecard " + notecard);
63 }
64 osNpcRemove (key target) {
65 llSay(0,"NPC removed");
66 }
67 list osGetAvatarList () {
68 list lStuff = [(key) "12345000-0000-0000-0000-0000000000002", <1,2,3>, "Digit Gorilla"];
69 return lStuff;
70 }
71 osMakeNotecard(string notecardName, string contents) {
72 llOwnerSay("Make Notecard " + notecardName + "Contents:" + (string) contents);
73 }
75 // sample notecard for testing
76 string str = "@spawn=Digit Gorilla|<645, 128, 25>\n"
77 + "@walk=<645, 120, 25>\n"
78 + "REPEAT\n"
79 + "@cmd=0|Hello on channel 0\n"
80 + "@wander=3|5\n"
81 + "@say=say , walking is so tiresome...\n"
82 + "@whisper=whisper, walking is so tiresome...\n"
83 + "@shout=shout, walking is so tiresome...\n"
84 + "@goto=REPEAT\n"
85 + "@goto=NEXT\n"
86 + "@say=i will never say this...\n"
87 + "NEXT\n"
88 + "@sound=somesound\n"
89 + "@randsound\n"
90 + "@pause=5\n"
91 + "@rotate=90\n"
92 + "@wander=3|1\n"
93 + "@say=Uff, I'm done...\n"
94 + "@delete\n";
95 return str;
96 }
97
98 osAvatarPlayAnimation(key npc, string animation){
99 llSay(0,"playing " + animation);
100 }
101
102 osAvatarStopAnimation(key npc, string animation){
103 llSay(0,"Stopping " + animation);
104 }
105
106 osForceOtherSit(key AvatarKey, key UUID) {
107 llSay(0,"Sitting");
108 }
109
110
111 osSetSpeed(key NPC, float speed) {
112 llSay(0,"Speed set to " + (string) speed);
113 }
114
115 osNpcTouch(key NPC, key thing, integer where) {;}
116
117 osForceAttachToOtherAvatarFromInventory(key npc, string inventory, integer point) {
118 llSay(0,"attach " + inventory + " to " + (string) point);
119 }
120
121 osTeleportAgent( key avi, string name, vector dest, vector lookat) {;}
122
123 // END commented code for OpenSim vs Editor environments
124 //*******************************************************************//
125 // comment this out, is only here for testing in LSLEditor.
126 default {}

All In One NPC Recorder and Player

Sample path notecard

Category: NPC
By : Ferd Frederix
Created: 2015-10-12 Edited: 2016-11-09
Worlds: OpenSim


This script by Ferd Frederix may be used in any manner, modified, and republished.  Unless specified otherwise, my scripts are always free and open source.  Objects made with these scripts may be sold with no restrictions.  All I ask is that you point others to this location should they ask you about it and to not sell this script, unless it is for $0 L. Please help improve my work by reporting bugs and improvements.

1
2
3 Blank lines are allowed in this notecard
4
5 @spawn=A TestNPC|<5, 0, 1>
6
7 You must send a link command @go to get past this next line:
8 @say=Stopped, type @go or click a box to continue
9 @stop
10
11 Swap appearances to a new NPC - if card is missing, nothing happens
12 @say=Change appearance test
13 @appearance=!Appearance2
14 @say=Walk test
15 @walk=<5, 10, 0>
16 @walk=<0,5,0>
17 @say=say, walking is so tiresome...
18 @fly=<0,35,10>
19 @whisper=whisper, flying is also tiresome...
20 @land=<5,0,1>
21 @shout=shout, Back on land
22
23 @say=pause 5 seconds
24 @pause=5
25
26 @say=Walk test - should walk, not fly
27 @walk=<10,10,0>
28 @walk=<5,0,0>
29
30 @say=wander 2 times inside a 3 meter area
31 @wander=2|3
32
33 @goto=NEXT
34 @say=i will never say this...
35 NEXT
36
37 Swap appearances to a new NPC - if card is missing, nothing happens
38 @say=Change appearance test
39 @appearance=!Appearance
40
41 @say=play a sound
42 @sound=somesound
43
44 @say=play a dance for 5 seconds
45 @animate=someanimation|5
46
47 @say=rotate left, behind, right, left, front
48 @rotate=90
49 @pause=5
50 @rotate=180
51 @pause=5
52 @rotate=270
53 @pause=5
54 @rotate=90
55 @pause=1
56 @rotate=0
57 @pause=5
58
59 @say=sit on prim named 'chair'
60 @sit=chair
61 @say=pause 5 seconds
62 @pause=5
63 @stand
64
65 @say=rotate the door primt
66 @cmd=100|OpenDoor
67
68 @walk=<5,0,0>
69 @animate=Stand|1
70 @say=Uff, I'm done...
71 @say=Repeat everything

All In One NPC Recorder and Player

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 : Ferd Frederix
Created: 2015-10-12 Edited: 2016-11-09
Worlds: OpenSim


This script by Ferd Frederix may be used in any manner, modified, and republished.  Unless specified otherwise, my scripts are always free and open source.  Objects made with these scripts may be sold with no restrictions.  All I ask is that you point others to this location should they ask you about it and to not sell this script, unless it is for $0 L. Please help improve my work by reporting bugs and improvements.

1 // License: GNU GPLv3
2 // Rev 1.6 5-24-2014
3
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_RESTART, not CHANGED_REGION_START (Opensim difference)
11 // Rev 1.8 tuned up Kill NPC, added more flexible upgrader
12 // Rev 1.9 Start NPC turns off Sensor
13 //*******************************************************************//
14
15 // Instructions on how to use this is at http://www.free-lsl-scripts.com/opensim/posts/NPC/
16 // This is an OpenSim-only script.
17 // Author: Ferd Frederix
18
19
20 ////////////////////////////////////////////////////////////////////////////////////////////
21 // Original code was Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 //
22 ///////////////////////////////////////////////////////////////////////////////////////////
23 // Please see: http://www.gnu.org/licenses/gpl.html for legal details, //
24 // rights of fair usage, the disclaimer and warranty conditions. //
25 ///////////////////////////////////////////////////////////////////////////////////////////
26 // The original NPC controller was from http://was.fm/opensim:npc
27 // Extensive additions and bug fixes by Fred Beckhusem, aka Ferd Frederix, fred@mitsi.com
28 // llSensor had two params swapped
29 // @Wander would wander where it had rezzed, not where it was.
30 // There was no 'no_sensor' event in sit, so if a @sit failed, the NPC got stuck
31 // 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.
32 // New code:
33 // Merged with new Route recorder and notecard writer
34 // If the NPC failed to reach a destination it never moved on. Added WAIT global to tune this
35 // Exposed many tunable variables and ported the code to LSLEditor.
36 // Added floating point to times in notecard.
37
38 // Added @sound, @randsound, @whisper, @shout, and @cmd controls.
39 //
40 // notecards integers are not floats for better control
41 //
42 // Link Messages may be used to perform external control by injecting @commands into the stream of actions
43 // Example:
44 // To chat something, such as with a chat robot
45 // llMessageLinked(LINK_SET,0,"@npc_say=Hello","");
46
47 // This script assumes that NPCs and OSSl scripting is enabled in the OpenSim configuration.
48 // In order to enable them, the following changes must be made in the OpenSim.ini configuration file:
49 //
50 // ; Turn on OSSL
51 // AllowOSFunctions = true
52 // OSFunctionThreatLevel = Severe
53
54 //[NPC]
55 // ;# {Enabled} {} {Enable Non Player Character (NPC) facilities} {true false}
56 // Enabled = true
57 //
58 // and then the server has to be restarted.
59
60
61 // Commands: All commands begin with an @ sign. All other lines are ignored
62 // @commands may have optional parameters. The syntax is always:
63 // @cmd=parm1|parm2
64 // NaN in the table below meand Not a Number. This means there is no parameter
65
66 //Command First Parameter Second Parameter Description
67 //@spawn name location (vector) Rezzes an NPC with name at a location.
68 //@walk destination (vector) NaN Makes the NPC walk to destination.
69 //@fly destination (vector) NaN Makes the NPC fly to destination.
70 //@land destination (vector) NaN Makes the NPC land at destination.
71 //@say string NaN Makes the NPC speak a phrase.
72 //@whisper string NaN Makes the NPC whisper a phrase.
73 //@shout string NaN Makes the NPC shout a phrase.
74 //@pause seconds (float) NaN Makes the NPC wait for a multiple of seconds.
75 //@wander radius (float) cycles (integer) Makes the NPC wander in radius, for cycles seconds.
76 //@delete NaN NaN Removes the NPC. Rerquires a link message of @npc_start to continue
77 //@npc_start NaN NaN Starts the NPC at the beginning
78 //@animate animation (string) time (float) Makes the NPC trigger the animation animation for time seconds.
79 //@goto label (string) NaN Jump to the label label in the script.
80 //@rotate degrees (float) NaN Rotate the NPC degrees around the Z axis.
81 //@sit primitive name NaN Sit on a primitive with a given name.
82 //@stand NaN NaN If sitting on a primitive, stand up.
83 //@sound sound_name NaN plays a sound from inventory
84 //@randsound NaN NaN Plays a random sound from inventory
85 //@cmd channel (integer) string Says string on channel, for controlling external gadgets
86
87 //@stop NaN NaN Halts the NPC script indefinitely. Can be started with a link message
88 //@go NaN NaN Continues on next notecard line, for use in link messages
89
90
91 //////////////////////////////////////////////////////////
92 // DEBUG //
93 //////////////////////////////////////////////////////////
94 integer debug = FALSE; // set to TRUE or FALSE for debug chat on various actions
95 integer Editor = FALSE; // set to to TRUE to working in LSLEditor, FALSE for in-world.
96 // you must also include the NPC commands found in the other script since LSLEditor does not support OpenSim
97 integer iTitleText = FALSE; // set to TRUE to see debug info in text above the controller
98
99 //////////////////////////////////////////////////////////
100 // TUNABLE CONFIGURATION //
101 //////////////////////////////////////////////////////////
102 integer allowUsers = TRUE; // If true, any user can get a Start NPC and Stop NPC menu. Only groups and owners canb get all commands if TRUE, or FALSE
103 float MAXDIST = 1.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
104 float TIMER = 0.5; // how often the system checks the distance traveled. Fastest you can go is 0.5 seconds
105 integer WANDERRAND = TRUE; // set to TRUE and they will pause during wanders a random number of seconds
106 float WANDERTIME = 60.0; // how long they stand after each @wander,if WANDERRAND is FALSE. If WANDERRAND is TRUE, this is the max time
107 integer WAIT = 10; // 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.
108 float RANGE = 96.0; // 1 to 96.0 meters - anyone this close to the controller will start NPCS if Sensor button is clicked
109 float REZTIME = 10.0; // wait this lng for NPC to rez in, then start the process
110 string STAND = "Stand"; // the name of the default Stand animation
111 string WALK = "Walk"; // the name of the default Walk animation
112 string FLY = "Fly"; // the name of the default Fly animation
113 string RUN = "Run"; // the name of the default Run animation
114 string LAND = "Land"; // the name of the default land animation ( for birds only)
115 float OffsetZ = 0.5; // appear 0.5 meter above ground, this is added to all destinations to keep them from sinking in. For fun, make this large and watch them fall out of the sky
116
117
118 // DESCRIPTIONS FIELDS HAVE TO SURVIVE A RESET
119 // These vars are stored by saving them with KeyValueSet
120 // "pr" is a 0 if it is set for Owner Only, 1 for Group control
121 // "se" is "on" if Started
122 // "co" = "R" or "A" for relative or absolute addressing mode
123 // "key" = NPC key
124
125 // These Globals used to be stored in description. Moved to RAM in V1.6
126 float RAMPause; // @pause param
127 float RAMwd ; // @wander distance
128 integer RAMwc; // @wander count
129 float RAMrot; // @rotate
130 string RAMsit; // @sit primname
131 string RAManimationName; // @animate animation (string) time (float)
132 float RAManimationTime;
133
134 // other globals section
135 integer iChannel; // a listen channel, randomly assigned
136 integer iHandle; // the handle to it
137
138
139
140 // NPC controls
141 vector newDest ; // tmp storage for the walks
142 integer iWaitCounter ; // wait for this number of seconds for the NPC to reach a desrtination
143 string sNPCName; // the name of the NPC that may be in world. So we can remove it.
144 integer bNPC_STOP = FALSE; // boolean to reuse a listener
145 integer bForget = FALSE; // set to TRUE by link messages so we do not remember them
146 float fTimerVal ; // how long we wait when wandering (calculated)
147
148 // 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.
149 // OS_NPC_NOT_OWNED will create an 'unowned' NPC that will respond to any script that has OSSL permissions to call osNpc* commands.
150 integer NPCOptions = OS_NPC_CREATOR_OWNED; // only yhe owner of this box can control this NPC.
151
152 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
153
154 integer NPCWalkOption; // Some notes for what happens to NPCWalkOption:
155 // 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.
156 // 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
157 //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.
158 // OS_NPC_RUNNING - if given, NPC avatar moves at running/fast flying speed, otherwise moves at walking/slow flying speed.
159
160 // menus
161 integer showMenu = FALSE; // when we switch states, we need to bring up a menu
162 list lAtButtons = ["Menu","-", "More", "@run","@walk","@fly", "@land","@wander","@sit", "@stand","@animate","@rotate"];
163 list lMenu2 = ["<<", "@comment", "@stop", "@say","@whisper","@shout", "@sound","@randsound", "-", "@cmd", "@pause", "@delete" ];
164 string sCommand; // place to store a command for two-prompted ones
165 string sParam2; // place to store a prompt for two-prompted ones
166 string priPub = "Owner Only"; // Private or Group
167 key kUserKey; // the person who is controlling the avatar, not the Owner
168
169 // the command lists
170 list lCommands; // commands are stored here
171 list lNPCScript; // Storage for the NPC script.
172 string npcAction; // Storage for the next action. @cmd=0|hello, this becomes @cmd
173 string npcParams; // Storage for the param, @cmd=0|hello, this becomes 0|hello
174
175 // misc vars
176 string sNotecard; // commands are stored here temporarily for dumping
177 vector vWanderPos; // a place to wander to
178 string lastANIM ; // last animation run
179 // Sensor
180 integer avatarPresent = TRUE; // Sensor sets this flag when people are within Range.
181 integer Sensor; // set to true if we are running a Sensor for avatars
182
183 // Coordinate control
184 vector vInitialPos ; // Vector that will be filled by the script with the initial starting position in region coordinates.
185 vector vDestPos = ZERO_VECTOR; // Storage for destination position.
186 string relAbs = "Absolute"; // absolute vs relative positioning
187
188 ///////////////////////////////////////////////////////////////////////////
189 // FUNCTIONS //
190 ///////////////////////////////////////////////////////////////////////////
191
192
193 // DEBUG(string) will chat a string or display it as hovertext if debug == TRUE
194 DEBUG(string str)
195 {
196 if(debug)
197 llOwnerSay( str); // Send the owner debug info so you can chase NPCS
198 if(iTitleText)
199 {
200 llSleep(0.1);
201 llSetText(str,<1.0,1.0,1.0>,1.0); // show hovertext
202 }
203 }
204
205
206 // common subroutines
207
208
209
210 // return TRUE if the avatar is owner when private is set, or TRUE if the avatar is in th same group and GROUP is set.
211 integer checkPerms() {
212
213 integer group = (integer) KeyValueGet("pr");
214 if(! group)
215 priPub = "Owner Only";
216 else
217 priPub = "Group";
218
219
221 kUserKey = llDetectedKey(0);
222 return TRUE;
223 }
224
225 if( group && llDetectedGroup(0)) {
226 kUserKey = llDetectedKey(0);
227 return TRUE;
228 }
229 kUserKey = llDetectedKey(0);
230 return FALSE;
231 }
232
233
234
235 NPCStart(string anim)
236 {
237 DEBUG(" Start Anim: " + anim);
239
240 if(lastANIM != anim) {
241 osNpcPlayAnimation(NPCKey(), anim);
242
243 if(llStringLength(lastANIM) && llGetInventoryType(lastANIM) == INVENTORY_ANIMATION) {
244 osNpcStopAnimation(NPCKey(), lastANIM) ;
245 }
246
247 lastANIM = anim;
248 }
249 } else {
250 llSay(DEBUG_CHANNEL, "No animation named " + anim);
251 }
252 }
253
254
255 TimerEvent(float timesent)
256 {
257 //DEBUG("Setting timer: " + (string) timesent);
258 llSetTimerEvent(timesent);
259 }
260
261
262
263 ProcessLink(string str)
264 {
265 // DEBUG("Processing exern cmd : " + str);
266 bForget = TRUE; // tell the NPCProcess state to forget this command after processing it.
267 lNPCScript = llListInsertList(lNPCScript,[str],0); // add this command to the beginning of the list of commands
268 NPCStart(STAND);
269 }
270
271 // Kill a NPC by Name
272 Kill(string param)
273 {
274
275 integer count;
276
277 list avatars = osGetAvatarList(); // Returns a strided list of the UUID, position, and name of each avatar in the region except the owner.
278
279 integer i;
280 integer j = llGetListLength(avatars);
281 for (i=0 ; i <= j; i+=3){
282
283 string desired = llList2String(avatars,i+2);
284 desired = llStringTrim(desired,STRING_TRIM); // should not be needed but is needed
285
286 if(desired == param){
287 vector v = llList2Vector(avatars,i+1);
288 key target = llList2Key(avatars,i); // get the UUID of the avatar
289 osNpcRemove(target);
290 llOwnerSay("Removed " + param+ " at location " + (string) v);
291 count++;
292 }
293 }
294 if(count)
295 llOwnerSay("Removed " + (string) count + " NPC's");
296 else
297 llOwnerSay("Could not locate " + param);
298 }
299
300
301 // return a String for the position we are at. Strings used as the caller wants strings
302 string Pos()
303 {
304 vector where = llGetPos(); // find the box position
305
306 where.z += OffsetZ; // use the ground position + an offset
307
308 if(Editor)
309 where = <128,128,31 + llFrand(1)>; // center of sim for editing
310
311 // if attached the height will be too high by 1/2 the agent size
312 if(llGetAttached()) {
314 float Z = size.z;
315 where.z -= Z/2;
316 }
317
318 // DEBUG("Pos= " + (string) where);
319 return (string) where;
320 }
321
322 Expire()
323 {
324 llOwnerSay("Menu expired");
325 iHandle = 0;
326 TimerEvent(0.0);
327 }
328
329 // setup a menu with a timer for timeouts, called by all make*()
330 menu()
331 {
332 llListenRemove(iHandle);
333 iChannel = llCeil(llFrand(100000) + 20000);
334 iHandle = llListen(iChannel,"","","");
335 TimerEvent(120.0);
336 }
337
338
339 // make a text box
340 makeText(string Param)
341 {
342 menu();
343 llTextBox(kUserKey, Param, iChannel);
344 }
345
346 // top level menu
347 makeMainMenu()
348 {
349 menu();
350 list buttons = ["Appearance","Recording","Save","Help","-","Erase RAM", priPub,relAbs,"-","Stop NPC","Sensor","Start NPC"];
351 llDialog(kUserKey,(string) llGetListLength(lCommands) + "Recordings",buttons,iChannel);
352 }
353
354
355 // Rev 1.4
356 // top level menu for non group/ non owners
357 makeUserMenu()
358 {
359 if(!allowUsers)
360 return;
361 menu();
362 list buttons = ["Start NPC","Stop NPC"];
363 llDialog(kUserKey,"Choose",buttons,iChannel);
364 }
365
366
367
368 // programmable menu for @commands
369 makeMenu(list buttons)
370 {
371 menu();
372 llDialog(kUserKey,(string) llGetListLength(lCommands) + "Recordings",buttons,iChannel);
373 }
374
375
376
377
378 // make one or two text boxes with prompts
379 Text(string cmd, string p1, string p2)
380 {
381 sCommand = cmd;
382 sParam2 = "";
384 sParam2 = p2;
385
386 makeText(p1);
387 }
388
389 ProcessSensor(integer n)
390 {
391 DEBUG("Sensor:" + (string) n);
392 if(Sensor && n)
393 avatarPresent = TRUE; // someone is here and we need to tell the system to run
394 else if(Sensor && !n)
395 avatarPresent = FALSE; // someone is not here and we need to tell the system to stop
396 else
397 avatarPresent = TRUE; // someone is effectivly always here
398 }
399
400 vector CirclePoint(float radius) {
401 float x = llFrand(radius *2) - radius; // +/- radius, randomized
402 float y = llFrand(radius *2) - radius; // +/- radius, randomized
403 return <x, y, 0>; // so this should always happen
404 }
405
406 string KeyValueGet(string var) {
407 list dVars = llParseString2List(llGetObjectDesc(), ["&"], []);
408 do {
409 list data = llParseString2List(llList2String(dVars, 0), ["="], []);
410 string k = llList2String(data, 0);
411 if(k != var) jump continue;
412 //DEBUG("got " + var + " = " + llList2String(data, 1));
413 return llList2String(data, 1);
414 @continue;
415 dVars = llDeleteSubList(dVars, 0, 0);
416 } while(llGetListLength(dVars));
417 return "";
418 }
419
420 KeyValueSet(string var, string val) {
421
422 //DEBUG("set " + var + " = " + val);
423 list dVars = llParseString2List(llGetObjectDesc(), ["&"], []);
424 if(llGetListLength(dVars) == 0)
425 {
426 llSetObjectDesc(var + "=" + val);
427 return;
428 }
429 list result = [];
430 do {
431 list data = llParseString2List(llList2String(dVars, 0), ["="], []);
432 string k = llList2String(data, 0);
433 if(k == "") jump continue;
434 if(k == var && val == "") jump continue;
435 if(k == var) {
436 result += k + "=" + val;
437 val = "";
438 jump continue;
439 }
440 string v = llList2String(data, 1);
441 if(v == "") jump continue;
442 result += k + "=" + v;
443 @continue;
444 dVars = llDeleteSubList(dVars, 0, 0);
445 } while(llGetListLength(dVars));
446 if(val != "") result += var + "=" + val;
448 }
449
450
451 // clear RAM
452 Clr() {
453
454 lCommands = [];
455 llOwnerSay("RAM Memory cleared. Notecards, if any, are not modified.");
456 makeMainMenu();
457 }
458
459 integer checkNoteCards()
460 {
461 // Check that they have saved an Appeaance and Path notecard
462 integer num = llGetInventoryNumber(INVENTORY_NOTECARD); // how many notecards overall
463
464 integer i;
465 integer count;
466 for (; i < num; i++){
468 count++;
469 if(llGetInventoryName(INVENTORY_NOTECARD,i) == "Appearance")
470 count++;
471 }
472 // if we have both, run the NPC
473 return count;
474 }
475 // saves a few bytes per call
476 key NPCKey()
477 {
478 return KeyValueGet("key");
479 }
480
481 // Notes:
482 // No llResetScript() used so we can retain memory between rezzes and sim restarts. NPC info and stateful info is held in the Description
483 //
484
485
486 // This state is the first main menu
487 default
488 {
490 {
491 llSetText("",<1,1,1>,1.0); // clr all hovertext- we may not be using it.
492
493 // delete all NPC*scripts except myself
494 integer i;
496 for (i = 0; i < j; i++) {
498 string match = llGetSubString(name,0,2);
499 if(match == "NPC" && llGetScriptName() != name)
500 {
502 llOwnerSay("Upgraded");
503 }
504 }
505
506
507 string rA = KeyValueGet("co"); // Get the remembered menu setting for Abs Vs Relative
508 if(rA == "A")
509 relAbs = "Absolute";
510 else if(rA == "R")
511 relAbs = "Relative";
512 else
513 relAbs = "Absolute";
514
515
516 if(showMenu) {
517 makeMainMenu();
518 return;
519 }
520
521 // reenable NPC if sensor is on.
522 if("on" == KeyValueGet("se"))
523 {
524 ProcessSensor(1); // fake 1 avatar to get it rezzed
525 Sensor = TRUE; // we need to scan for avatars
526 state NPCGo;
527 }
528 // reenable NPC if On is on.
529 if("1" == KeyValueGet("On"))
530 {
531 ProcessSensor(1); // fake 1 avatar to get it rezzed
532 Sensor = FALSE; // no need to scan for avatars
533 state NPCGo;
534 }
535
536 }
537
538
539 touch_start(integer n) { // if touched, make a menu
540 if(checkPerms())
541 makeMainMenu();
542 else
543 makeUserMenu();
544 }
545
546 // no changed event needed
547
548 // menu listener
549 listen(integer iChannel, string name, key id, string message) {
550 TimerEvent(0.0); /// kill the menu expiration timer
551
552 if(message == "Stop NPC")
553 {
554 KeyValueSet("On","0"); // On is Off
555 if(llStringLength(sNPCName)){
556 Kill(sNPCName);
557 sNPCName = "";
558 } else {
559 bNPC_STOP = TRUE;
560 makeText("Enter name of an NPC to stop");
561 }
562 }
563 else if(message == "Erase RAM"){
564 Clr();
565 }
566 else if(message == "Relative"){
567 relAbs = "Absolute";
568 KeyValueSet("co","A"); // remember coordinates = A
569 Clr();
570 }
571 else if(message == "Absolute"){
572 relAbs = "Relative";
573 KeyValueSet("co","R"); // remember coordinates = R
574 Clr();
575 }
576 else if(message == "Recording"){
577 state Commands; // show them the recording menu
578 }
579 else if(message == "Owner Only") {
580 priPub = "Group";
581 KeyValueSet("pr","1");
582
583 llOwnerSay("Group members have control");
584 makeMainMenu();
585 }
586 else if(message == "Group") {
587 priPub = "Owner Only";
588 KeyValueSet("pr","0");
589 llOwnerSay("Only you have control");
590 makeMainMenu();
591 }
592 else if(message == "Sensor") {
593 KeyValueSet("On","1"); // On is Off
594 integer count = checkNoteCards();
595
596 if(count >= 2) {
597 KeyValueSet("se", "on");
598 ProcessSensor(1); // fake 1 avatar to get it rezzed
599 Sensor = TRUE; // we need to scan for avatars
600 state NPCGo;
601 }
602
603 // lslEditor does not handle the above, so I hack it in
604 if(Editor) {
605 Sensor = TRUE; // we need to scan for avatars
606 state NPCGo;
607 }
608
609 llOwnerSay("You have not saved a recording and/or appearance, so you cannot start a NPC");
610 makeMainMenu();
611 }
612 else if(message == "Appearance") {
613 llRemoveInventory("Appearance"); // delete the notecard
614 osAgentSaveAppearance(kUserKey, "Appearance"); // make the ntecard
615 llOwnerSay("Your Appearance has been recorded in notecard 'Appearance'");
616 makeMainMenu();
617 }
618 else if(message == "Save") {
619 if(llGetListLength(lCommands) == 0) {
620 llOwnerSay("Nothing recorded, you need to make some Recodings first");
621 makeMainMenu();
622 return;
623 }
624 state Save;
625 }
626 else if(message == "Help"){
627 llLoadURL(kUserKey,"Click to view help","http://www.free-lsl-scripts.com/opensim/posts/NPC/");
628 makeMainMenu();
629 }
630 else if(message == "Start NPC") {
631 KeyValueSet("On","1"); // On is On
632 KeyValueSet("se", "off");
633 integer count = checkNoteCards();
634 if(Editor) state NPCGo;
635 if(count == 2)
636 state NPCGo;
637
638 llOwnerSay("You have not saved a either recording or and appearance, so we cannot start a NPC");
639
640 }
641 else if(bNPC_STOP){
642 bNPC_STOP = FALSE;
643 Kill(message);
644 }
645 }
646 timer(){
647 Expire();
648 }
649 }
650
651
652 // This state is used to save a Path notecard
653 state Save
654 {
656 makeText("Stand where you want the NPC to appear, and enter the NPC Name");
657 }
658 listen(integer iChannel, string name, key id, string message) {
659 TimerEvent(0.0); /// kill the menu expiration timer
660
661 sNPCName = message; // in case we need to kill it.
662 vector vDest = (vector) Pos();
663
664 if(relAbs == "Relative")
665 {
666 vDest -= llGetPos(); // just an offset for relative
667 }
668 sNotecard = "@spawn=" + message + "|" + (string) vDest + "\n";
669 integer i;
670 integer j = llGetListLength(lCommands);
671 for (; i < j; i++){
672 // get the command to save to the notecard
673 string line = llList2String(lCommands,i);
674 if(relAbs == "Absolute") {
675 sNotecard += line; // add the un-modified string to the notecard
676 } else {
677 // since we have to record absolute coords since we do not know where the box goes until they press Save,
678 // we process the absolute to relative conversion for walks here
679 list parts = llParseString2List(line,["="],[]); //get the @command
680
681 if(llList2String(parts,0) == "@walk") {
682 vector vec = (vector) llList2String(parts,1) - llGetPos();
683 sNotecard += "@walk=" + (string) vec + "\n";
684 }
685 else if(llList2String(parts,0) == "@fly") {
686 vector vec = (vector) llList2String(parts,1) - llGetPos();
687 sNotecard += "@fly=" + (string) vec + "\n";
688 }
689 else if(llList2String(parts,0) == "@run") {
690 vector vec = (vector) llList2String(parts,1) - llGetPos();
691 sNotecard += "@run=" + (string) vec + "\n";
692 }
693 else if(llList2String(parts,0) == "@land") {
694 vector vec = (vector) llList2String(parts,1) - llGetPos();
695 sNotecard += "@land=" + (string) vec + "\n";
696 }
697 else {
698 sNotecard += line; // add the un-modified string to the notecard
699 }
700 }
701 }
702 llRemoveInventory("Path"); // delete the old notecard
703 osMakeNotecard("Path",sNotecard); // Makes the notecard.
704 llOwnerSay("'Path' notecard has been written");
705 state default;
706 }
707 timer(){
708 Expire();
709 }
710
711 }
712
713 // This state is used to record the path for the NPC
714 // Each command can take 0, 1, or 2 params
715 state Commands
716 {
717 state_entry() {
718 makeMenu(lAtButtons);
719 }
720
721 on_rez(integer p) {
722 showMenu= TRUE;
723 state default;
724 }
726 if(checkPerms())
727 makeMenu(lAtButtons);
728 else
729 makeUserMenu();
730
731 }
732
733 listen(integer iChannel, string name, key id, string message)
734 {
735 TimerEvent(0.0); /// kill the menu expiration timer
736
737 if(message == "Menu"){
738 showMenu= TRUE;
739 state default;
740 }
741 else if(message == "More"){
742 makeMenu(lMenu2);
743 }
744 else if(message == "<<") {
745 makeMenu(lAtButtons);
746 }
747 else if(message == "@comment"){
748 Text("# ","Enter a comment","");
749 }
750 else if(message == "@stop"){
751 lCommands += "@stop"+ "\n";
752 makeMenu(lAtButtons);
753 }
754 else if(message == "@run"){
755 lCommands += "@run=" + Pos() + "\n";
756 llOwnerSay("Recorded position: " + Pos());
757 makeMenu(lAtButtons);
758 }
759 else if(message == "@fly"){
760 lCommands += "@fly=" + Pos() + "\n";
761 llOwnerSay("Recorded position: " + Pos());
762 makeMenu(lAtButtons);
763 }
764 else if(message == "@land"){
765 lCommands += "@land=" + Pos() + "\n";
766 llOwnerSay("Recorded position: " + Pos());
767 makeMenu(lAtButtons);
768 }
769 else if(message == "@walk") {
770 lCommands += "@walk=" + Pos() + "\n";
771 llOwnerSay("Recorded position: " + Pos());
772 makeMenu(lAtButtons);
773 }
774 else if(message == "@stop"){
775 lCommands += "@stop"+ "\n";
776 makeMenu(lAtButtons);
777 }
778 else if(message == "@sound"){
779 Text("@sound=","Enter a sound name or UUID to trigger","");
780 }
781 else if(message == "@randsound"){
782 lCommands += "@randsound"+ "\n";
783 makeMenu(lAtButtons);
784 }
785 else if(message == "@say") {
786 Text("@say=","Enter what the NPC will say","");
787 }
788 else if(message == "@whisper"){
789 Text("@whisper=","Enter what the NPC will whisper","");
790 }
791 else if(message == "@shout"){
792 Text("@shout=","Enter what the NPC will shout","");
793 }
794 else if(message == "@wander") {
795 Text("@wander=","Enter radius to wander","Enter number of wanders");
796 }
797 else if(message == "@pause") {
798 Text("@pause=","Enter time to pause","");
799 }
800 else if(message == "@rotate") {
801 Text("@rotate=","Enter degrees to rotate","");
802 }
803 else if(message == "@sit"){
804 Text("@sit=","Enter name of object to sit on","");
805 }
806 else if(message == "@cmd"){
807 Text("@cmd=","Enter channel to speak on","Enter text to speak");
808 }
809 else if(message == "@stand"){
810 lCommands += "@stand\n";
811 llOwnerSay("Stand Recorded");
812 makeMenu(lAtButtons);
813 }
814 else if(message == "@animate"){
815 Text("@animate=","Enter animation name to play","Enter time to play the animation");
816 }
817 else if(! llStringLength(sParam2)) {
818 lCommands += sCommand + message + "\n";
819 llOwnerSay("Recorded");
820 makeMenu(lAtButtons);
821 }
822 else if(llStringLength(sParam2)){
823 sCommand = sCommand + message + "|";
824 llOwnerSay("Recorded");
825 makeText(sParam2);
826 sParam2 = "";
827 }
828 }
829 timer() {
830 Expire();
831 }
832
833
834
835 }
836
837
838 // This state will create an NPC in world
839 state NPCGo {
840 state_entry() {
841 // DEBUG("NPCGo");
842 ProcessSensor(1); // assert that someone is home so we can get rezzed.
843 osNpcRemove(NPCKey());
844 TimerEvent(5);
845 }
846 timer() {
847 lNPCScript = llParseString2List(osGetNotecard("Path"), ["\n"], []);
848 if(llGetListLength(lNPCScript) == 0) {
849 llSay(DEBUG_CHANNEL, "No Path notecard found.");
850 TimerEvent(0.0);
851 return;
852 }
853 state ProcessNPCLine;
854 }
855 changed(integer change) {
856 if(change & CHANGED_REGION_RESTART)
857 state ProcessNPCLine;
858 }
859 on_rez(integer num) {
861 }
862 state_exit() {
863 TimerEvent(0.0);
864 }
865 }
866
867
868 // This state loops over the notecard, processing each command
869 state ProcessNPCLine
870 {
872 {
873 // DEBUG("ProcessNPCLine");
874 @ignore;
875
876 string next = llList2String(lNPCScript, 0); // get the next command
877 // DEBUG("Cmd:" + next);
878 lNPCScript = llDeleteSubList(lNPCScript, 0, 0); // delete it
879 if(! bForget) {
880 lNPCScript += next; // put it on the end unless we are told to forget it from a Link Message
881 bForget = FALSE;
882 }
883 if(llGetSubString(next, 0, 0) != "@") jump ignore; // ignore non-@ commands
884 list data = llParseString2List(next, ["="], []);
885 npcAction = llToLower(llStringTrim(llList2String(data, 0), STRING_TRIM));
886 npcParams = llStringTrim(llList2String(data, 1), STRING_TRIM);
887
888 @commands;
889
890 if(! avatarPresent){
891 state nobodyHome;
892 }
893 if(npcAction == "@spawn") {
894 // DEBUG("Spawning");
895 integer lastIdx = llGetListLength(lNPCScript)-1;
896 lNPCScript = llDeleteSubList(lNPCScript, lastIdx, lastIdx); // remove spawn commands, we do them only once
897 list spawnData = llParseString2List(npcParams, ["|"], []);
898 sNPCName =llList2String(spawnData, 0); // V 1.6 name in RAM
899
900 list spawnDest = llParseString2List(llList2String(spawnData, 1), ["<", ",", ">"], []);
901 vInitialPos.x = llList2Float(spawnDest, 0);
902 vInitialPos.y = llList2Float(spawnDest, 1);
903 vInitialPos.z = llList2Float(spawnDest, 2);
904 state spawn;
905 }
906 if(npcAction == "@stop") {
907 state stop;
908 }
909 if(npcAction == "@goto") {
910 // DEBUG("goto");
911 integer lastIdx = llGetListLength(lNPCScript)-1;
912 lNPCScript = llDeleteSubList(lNPCScript, lastIdx, lastIdx);
913 // Wind commands till goto label.
914 @wind;
915 string next1 = llList2String(lNPCScript, 0);
916 lNPCScript = llDeleteSubList(lNPCScript, 0, 0);
917 lNPCScript += next1;
918 if(next1 != npcParams) jump wind;
919 // Wind the label too.
920 next1 = llList2String(lNPCScript, 0);
921 lNPCScript = llDeleteSubList(lNPCScript, 0, 0);
922 lNPCScript += next1;
923 // Get next command.
924 list data1 = llParseString2List(next1, ["="], []);
925 npcAction = llToLower(llStringTrim(llList2String(data1, 0), STRING_TRIM));
926 npcParams = llStringTrim(llList2String(data1, 1), STRING_TRIM);
927 // Reschedule.
928 jump commands;
929 }
930
931 if(npcAction == "@sound") {
932 // DEBUG("sound");
933 llTriggerSound(npcParams,1.0);
934 jump ignore; // process next line
935 }
936 if(npcAction == "@randsound") {
937 // DEBUG("random sound");
939 integer rand = llCeil(llFrand(N)) -1; // pick a random sound
941 llTriggerSound(toPlay,1.0);
942 jump ignore; // process next line
943 }
944
945 if(npcAction == "@walk") {
946 list dest = llParseString2List(npcParams, ["<", ",", ">"], []);
947 vDestPos.x = llList2Float(dest, 0);
948 vDestPos.y = llList2Float(dest, 1);
949 vDestPos.z = llList2Float(dest, 2);
950
951 if(vDestPos == ZERO_VECTOR) {
952 llSay(DEBUG_CHANNEL,"Bad (zeros) position for @walk");
953 state ProcessNPCLine;
954 }
955 walkstate = 1;// walking
956 NPCWalkOption = OS_NPC_NO_FLY ;
957 state walk;
958 }
959
960 if(npcAction == "@fly") {
961 list dest = llParseString2List(npcParams, ["<", ",", ">"], []);
962 vDestPos.x = llList2Float(dest, 0);
963 vDestPos.y = llList2Float(dest, 1);
964 vDestPos.z = llList2Float(dest, 2);
965
966 if(vDestPos == ZERO_VECTOR) {
967 llSay(DEBUG_CHANNEL,"Bad (zeros) position for @fly");
968 state ProcessNPCLine;
969 }
970
971 walkstate = 2;// flying
972 NPCWalkOption = OS_NPC_FLY ;
973 state walk;
974 }
975
976 if(npcAction == "@run") {
977 list dest = llParseString2List(npcParams, ["<", ",", ">"], []);
978 vDestPos.x = llList2Float(dest, 0);
979 vDestPos.y = llList2Float(dest, 1);
980 vDestPos.z = llList2Float(dest, 2);
981
982 if(vDestPos == ZERO_VECTOR) {
983 llSay(DEBUG_CHANNEL,"Bad (zeros) position for @walk");
984 state ProcessNPCLine;
985 }
986 walkstate = 3;// running
987 NPCWalkOption = OS_NPC_NO_FLY | OS_NPC_RUNNING;
988 state walk;
989 }
990
991 if(npcAction == "@land") {
992 list dest = llParseString2List(npcParams, ["<", ",", ">"], []);
993 vDestPos.x = llList2Float(dest, 0);
994 vDestPos.y = llList2Float(dest, 1);
995 vDestPos.z = llList2Float(dest, 2);
996
997 if(vDestPos == ZERO_VECTOR) {
998 llSay(DEBUG_CHANNEL,"Bad (zeros) position for @walk");
999 state ProcessNPCLine;
1000 }
1001
1002 walkstate = 4;// landing
1003
1004
1005 NPCWalkOption= OS_NPC_FLY | OS_NPC_LAND_AT_TARGET ;
1006 state walk;
1007 }
1008
1009
1010
1011 // chat commands
1012 // speak in white text
1013
1014 if(npcAction == "@say") {
1015 // DEBUG("say");
1016 osNpcSay(NPCKey(),0, npcParams);
1017 jump ignore; // process next line
1018 }
1019 if(npcAction == "@shout") {
1020 // DEBUG("shout");
1021 osNpcShout(NPCKey(),0, npcParams);
1022 jump ignore; // process next line
1023 }
1024 if(npcAction == "@whisper") {
1025 // DEBUG("whisper");
1026 osNpcWhisper(NPCKey(),0, npcParams);
1027 jump ignore; // process next line
1028 }
1029 // speak a command on a channel, so you can open doors and control stuff.
1030 if(npcAction == "@cmd") {
1031 // DEBUG("cmd");
1032 list dataToSpeak = llParseString2List(npcParams, ["|"], []);
1033 integer iChannel = (integer) llList2String(dataToSpeak,0);
1034 string stringToSpeak = llList2String(dataToSpeak,1);
1035 llRegionSay(iChannel, stringToSpeak); // V 1.2
1036 llMessageLinked(LINK_SET,iChannel,stringToSpeak,"");// V 2.0
1037 jump ignore; // process next line
1038 }
1039 // stop everything
1040 if(npcAction == "@pause") {
1041 // DEBUG("pause");
1042 RAMPause = (float) npcParams;
1043 state pause;
1044 }
1045 if(npcAction == "@wander") {
1046 // DEBUG("wander");
1047 list wanderData = llParseString2List(npcParams, ["|"], []);
1048 RAMwd = (float) llList2String(wanderData, 0);
1049 RAMwc = (integer) llList2String(wanderData, 1);
1050
1051 vDestPos = osNpcGetPos(NPCKey()); // set the wander start
1052 DEBUG("Starting at " + (string) vDestPos);
1053 state wander;
1054 }
1055 if(npcAction == "@rotate") {
1056 // DEBUG("rotate");
1057 RAMrot = (float) npcParams;
1058 state rotate;
1059 }
1060 if(npcAction == "@sit") {
1061 // DEBUG("sit");
1062 RAMsit= npcParams;
1063 state sit;
1064 }
1065 if(npcAction == "@stand") {
1066 // DEBUG("stand");
1067 state stand;
1068 }
1069 if(npcAction == "@delete") {
1070 state delete;
1071 }
1072 if(npcAction == "@animate") {
1073 // DEBUG("animate");
1074 list animateData = llParseString2List(npcParams, ["|"], []);
1075 RAManimationName = llList2String(animateData, 0);
1076 RAManimationTime = (float) llList2String(animateData, 1);
1077 state animate;
1078 }
1079 llSay(DEBUG_CHANNEL, "ERROR: Unrecognized script line: " + npcAction + "=" + npcParams);
1080 jump ignore;
1081
1082 }
1083 changed(integer change) {
1084 if(change & CHANGED_REGION_RESTART)
1085 state NPCGo;
1086 }
1087 on_rez(integer num) {
1089 }
1090 link_message(integer sender, integer num, string str, key id) {
1091 ProcessLink(str);
1092 state ProcessNPCLine;
1093 }
1094 // if touched by owner while running code, make a menu
1096
1097 if(checkPerms()) {
1098 TimerEvent(0); // stop the NPC from ticking
1099 showMenu = TRUE;
1100 state default;
1101 } else {
1102 makeUserMenu();
1103 }
1104
1105 }
1106 }
1107
1108
1109 state nobodyHome
1110 {
1111 state_entry() {
1112 // DEBUG("Removing NPC");
1113 osNpcRemove(NPCKey());
1114 llSensorRepeat("","",AGENT,RANGE,TWO_PI, 5);
1115 }
1116 sensor(integer n) {
1118 state NPCGo;
1119 }
1120 // if touched by owner while running code, make a menu
1122 if(checkPerms()) {
1123 TimerEvent(0); // stop the NPC from ticking
1124 showMenu = TRUE;
1125 state default;
1126 } else {
1127 makeUserMenu();
1128 }
1129
1130 }
1131 }
1132
1133
1134 state spawn
1135 {
1136 state_entry() {
1137 // DEBUG("state spawn");
1138 list name = llParseString2List(sNPCName, [" "], []);
1139 // notecard is stored as offsets from this box with relative addressing. Convert to absolute
1140 if(relAbs == "Relative"){
1141 vInitialPos += llGetPos();
1142 }
1143
1144 // DEBUG("rez:" + (string) vInitialPos);
1145 llSleep(llFrand(5));
1146 KeyValueSet("key", osNpcCreate(llList2String(name, 0), llList2String(name, 1), vInitialPos, "Appearance", NPCOptions)); // no OS_NPC_SENSE_AS_AGENT allowed due to llSensor Use
1147 osNpcLoadAppearance(NPCKey(), "Appearance");
1148 TimerEvent(REZTIME);
1149 NPCStart(STAND);
1150 }
1151 link_message(integer sender, integer num, string str, key id) {
1152 ProcessLink(str);
1153 state ProcessNPCLine;
1154 }
1155 timer() {
1156 state ProcessNPCLine;
1157 }
1158 changed(integer change) {
1159 if(change & CHANGED_REGION_RESTART)
1160 state NPCGo;
1161 }
1162 on_rez(integer num) {
1164 }
1165 state_exit(){
1166 TimerEvent(0.0);
1167 }
1168 // if touched by owner while running code, make a menu
1170 if(checkPerms()) {
1171 TimerEvent(0); // stop the NPC from ticking
1172 showMenu = TRUE;
1173 state default;
1174 }else {
1175 makeUserMenu();
1176 }
1177
1178 }
1179 }
1180
1181
1182 state rotate {
1183 state_entry() {
1184 // DEBUG("state rotate");
1185 osNpcSetRot(NPCKey(), llEuler2Rot(<0,0,RAMrot> * DEG_TO_RAD));
1186 TimerEvent(TIMER);
1187 }
1188 link_message(integer sender, integer num, string str, key id) {
1189 ProcessLink(str);
1190 state ProcessNPCLine;
1191 }
1192 timer() {
1193 state ProcessNPCLine;
1194 }
1195 changed(integer change) {
1196 if(change & CHANGED_REGION_RESTART)
1197 state NPCGo;
1198 }
1199 on_rez(integer num) {
1201 }
1202 state_exit() {
1203 TimerEvent(0.0);
1204 }
1205 // if touched by owner while running code, make a menu
1207 if(checkPerms()) {
1208 TimerEvent(0); // stop the NPC from ticking
1209 showMenu = TRUE;
1210 state default;
1211 }else {
1212 makeUserMenu();
1213 }
1214 }
1215 }
1216
1217 state sit {
1218 state_entry() {
1219 // DEBUG ("state sit");
1220 llSensorRepeat(RAMsit, "", PASSIVE|ACTIVE, 96, TWO_PI, 1);
1221 }
1222 sensor(integer num) {
1224 osNpcSit(NPCKey(), llDetectedKey(0), OS_NPC_SIT_NOW); //V1.2
1225 TimerEvent(TIMER);
1226 }
1227 no_sensor(){
1228 state ProcessNPCLine;
1229
1230 }
1231 timer() {
1232 state ProcessNPCLine;
1233 }
1234 link_message(integer sender, integer num, string str, key id) {
1235 ProcessLink(str);
1236 state ProcessNPCLine;
1237 }
1238 changed(integer change) {
1239 if(change & CHANGED_REGION_RESTART)
1240 state NPCGo;
1241 }
1242 on_rez(integer num) {
1244 }
1245 // if touched by owner while running code, make a menu
1247 if(checkPerms()) {
1248 TimerEvent(0); // stop the NPC from ticking
1249 showMenu = TRUE;
1250 state default;
1251 } else {
1252 makeUserMenu();
1253 }
1254
1255 }
1256
1257
1258 state_exit(){
1259 TimerEvent(0.0);
1260 }
1261
1262
1263 }
1264
1265 state stand {
1266 state_entry() {
1267 // DEBUG("state stand");
1268 osNpcStand(NPCKey());
1269 state ProcessNPCLine;
1270 }
1271 changed(integer change) {
1272 if(change & CHANGED_REGION_RESTART)
1273 state NPCGo;
1274 }
1275 // if touched by owner while running code, make a menu
1277 if(checkPerms()) {
1278 TimerEvent(0); // stop the NPC from ticking
1279 showMenu = TRUE;
1280 state default;
1281 } else {
1282 makeUserMenu();
1283 }
1284
1285 }
1286
1287
1288 on_rez(integer num) {
1290 }
1291
1292 }
1293
1294 state animate {
1295 state_entry() {
1296 // DEBUG("state animate");
1297 NPCStart(RAManimationName);
1298 TimerEvent(RAManimationTime);
1299 }
1300 link_message(integer sender, integer num, string str, key id) {
1301 ProcessLink(str);
1302 state ProcessNPCLine;
1303 }
1304 timer() {
1305 NPCStart(STAND);
1306 state ProcessNPCLine;
1307 }
1308 changed(integer change) {
1309 if(change & CHANGED_REGION_RESTART)
1310 state NPCGo;
1311 }
1312 // if touched by owner while running code, make a menu
1314 if(checkPerms()) {
1315 TimerEvent(0); // stop the NPC from ticking
1316 showMenu = TRUE;
1317 state default;
1318 } else {
1319 makeUserMenu();
1320 }
1321
1322 }
1323
1324
1325 on_rez(integer num) {
1327 }
1328 state_exit() {
1329 TimerEvent(0.0);
1330 }
1331
1332 }
1333 state walk {
1334 state_entry() {
1335
1336 DEBUG("NPCWalkOption = " + (string) NPCWalkOption);
1337
1338 // walk, fly, run, land
1339 if(walkstate == 1) {
1340 NPCStart(WALK);
1341 } else if(walkstate == 2) {
1342 llShout(299,"on");
1343 NPCStart(FLY);
1344 } else if(walkstate == 3) {
1345 NPCStart(RUN);
1346 } else if(walkstate == 4) {
1347 NPCStart(LAND);
1348 } else {
1349 state ProcessNPCLine;
1350 }
1351
1352 if(Sensor) {
1353 // DEBUG("Sensor on");
1354 llSensor("","",AGENT,RANGE,TWO_PI); // sensor survive state switches.
1355 }
1356
1357 newDest = vDestPos ;
1358 // notecard is stored as offsets from this box with relative addressing. Convert to absolute
1359 if(relAbs == "Relative"){
1360 newDest += llGetPos();
1361 }
1362
1363 DEBUG("Moveto:" + (string) newDest);
1364 iWaitCounter = WAIT; // wait 60 seconds to get to a destination.
1365 osNpcMoveToTarget(NPCKey(), newDest, NPCWalkOption);
1366 TimerEvent(TIMER);
1367 }
1368 link_message(integer sender, integer num, string str, key id) {
1369 ProcessLink(str);
1370 state ProcessNPCLine;
1371 }
1372 timer() {
1373 if(--iWaitCounter) {
1374
1375 vector tDest = newDest;
1376 tDest.z = 0;
1377 vector hisDest = osNpcGetPos(NPCKey());
1378 hisDest.z = 0;
1379
1380 if(llVecDist(hisDest, newDest) > MAXDIST) {
1381 return;
1382 }
1383 }
1384
1385 // walk, fly, run, land
1386 if(walkstate == 1) {
1387 NPCStart(STAND);
1388 } else if(walkstate == 2) {
1389 // nothing
1390 } else if(walkstate == 3) {
1391 NPCStart(STAND);
1392 } else if(walkstate == 4) {
1393 llShout(299,"off");
1394 NPCStart(STAND);
1395 } else {
1396 state ProcessNPCLine;
1397 }
1398
1399 state ProcessNPCLine;
1400 }
1401 sensor(integer n) {
1402 ProcessSensor(n);
1403 }
1404 no_sensor(){
1405 ProcessSensor(0);
1406 }
1407 // if touched by owner while running code, make a menu
1409 if(checkPerms()) {
1410 TimerEvent(0); // stop the NPC from ticking
1411 showMenu = TRUE;
1412 state default;
1413 } else {
1414 makeUserMenu();
1415 }
1416
1417 }
1418
1419
1420 changed(integer change) {
1421 if(change & CHANGED_REGION_RESTART)
1422 state NPCGo;
1423 }
1424 on_rez(integer num) {
1426 }
1427 state_exit() {
1428 TimerEvent(0.0);
1429 }
1430
1431 }
1432
1433 state wander
1434 {
1435 state_entry() {
1436 DEBUG("state wander");
1437 if(Sensor)
1438 {
1439 // DEBUG("Sensor on");
1440 llSensor("","",AGENT,RANGE,TWO_PI); // sensor survive state switches.
1441 }
1442
1443 vector point = CirclePoint(RAMwd);
1444 DEBUG("CirclePoint:" + (string) point);
1445 vWanderPos = vDestPos + point;
1446 DEBUG("vWanderPos:" + (string) vWanderPos);
1447
1448 fTimerVal = WANDERTIME; // default time to pause after each wander
1449 if(WANDERRAND)
1450 fTimerVal = llFrand(WANDERTIME); // override, they want random times
1451
1452 NPCStart(WALK);
1453
1454 DEBUG("Wander to:" + (string) vWanderPos);
1455
1456 osNpcMoveToTarget(NPCKey(), vWanderPos, NPCWalkOption);
1457 iWaitCounter = WAIT; // wait 60 seconds to get to a destination.
1458
1459 TimerEvent(TIMER); // first time we wait for the short timer.
1460 }
1461 link_message(integer sender, integer num, string str, key id) {
1462 ProcessLink(str);
1463 NPCStart(STAND);
1464 state ProcessNPCLine;
1465 }
1466 timer() {
1467
1468 if(--iWaitCounter) // wait 60 seconds to get to a destination.
1469 {
1470 vector tDest = vWanderPos;
1471 tDest.z = 0;
1472 vector hisDest = osNpcGetPos(NPCKey());
1473 hisDest.z = 0;
1474
1475 if(llVecDist(hisDest, tDest) > MAXDIST) return;
1476 }
1477
1478 // see if wander counter == 0, if so, stop walking, go to stand and process next line
1479 if(RAMwc == 0) {
1480 NPCStart(STAND);
1481 state ProcessNPCLine;
1482 }
1483
1484 // one less time to wander around
1485 RAMwc--;
1486
1487 NPCStart(STAND);
1488 state wanderhold;
1489 }
1490 sensor(integer n) {
1491 ProcessSensor(n);
1492 }
1493 no_sensor() {
1494 ProcessSensor(0);
1495 }
1496 // if touched by owner while running code, make a menu
1498 if(checkPerms()) {
1499 TimerEvent(0); // stop the NPC from ticking
1500 showMenu = TRUE;
1501 state default;
1502 } else {
1503 makeUserMenu();
1504 }
1505 }
1506
1507
1508 changed(integer change) {
1509 if(change & CHANGED_REGION_RESTART)
1510 state NPCGo;
1511 }
1512 on_rez(integer num) {
1514 }
1515 state_exit() {
1516 TimerEvent(0.0);
1517 }
1518 }
1519
1520
1521 state wanderhold
1522 {
1523 state_entry(){
1524 // now that we have reached a wander spot, slow the timer down to the desired value
1525 TimerEvent(fTimerVal);
1526 if(Sensor)
1527 {
1528 // DEBUG("Sensor on");
1529 llSensor("","",AGENT,RANGE,TWO_PI); // sensor survive state switches.
1530 }
1531 }
1532 timer() {
1533 state wander;
1534 }
1535 sensor(integer n){
1536 ProcessSensor(n);
1537 }
1538 no_sensor(){
1539 ProcessSensor(0);
1540 }
1541 // if touched by owner while running code, make a menu
1543 if(checkPerms()) {
1544 TimerEvent(0); // stop the NPC from ticking
1545 showMenu = TRUE;
1546 state default;
1547 } else {
1548 makeUserMenu();
1549 }
1550
1551 }
1552
1553
1554 changed(integer change) {
1555 if(change & CHANGED_REGION_RESTART)
1556 state NPCGo;
1557 }
1558 on_rez(integer num) {
1560 }
1561 state_exit() {
1562 TimerEvent(0.0);
1563 }
1564
1565
1566 }
1567
1568
1569
1570 // @pause=10 will stand for 10 seconds
1571 state pause {
1572 state_entry() {
1573 DEBUG("state pause");
1574 NPCStart(STAND);
1575 if(Sensor)
1576 {
1577 // DEBUG("Sensor on");
1578 llSensor("","",AGENT,RANGE,TWO_PI); // sensor survive state switches.
1579 }
1580 llSetTimerEvent(RAMPause);
1581 }
1582 link_message(integer sender, integer num, string str, key id){
1583 ProcessLink(str);
1584 state ProcessNPCLine;
1585 }
1586 timer() {
1587 NPCStart(STAND);
1588 state ProcessNPCLine;
1589 }
1590 sensor(integer n)
1591 {
1592 ProcessSensor(n);
1593 }
1594 no_sensor()
1595 {
1596 ProcessSensor(0);
1597 }
1598 // if touched by owner while running code, make a menu
1600 if(checkPerms()) {
1601 TimerEvent(0); // stop the NPC from ticking
1602 showMenu = TRUE;
1603 state default;
1604 } else
1605 makeUserMenu();
1606
1607 }
1608 changed(integer change) {
1609 if(change & CHANGED_REGION_RESTART)
1610 state NPCGo;
1611 }
1612 on_rez(integer num) {
1614 }
1615 state_exit()
1616 {
1617 TimerEvent(0.0);
1618 }
1619 }
1620
1621 // @stop makes the NPC stand there. You have to linkmessage to get moving again
1622 state stop {
1623 state_entry() {
1624 NPCStart(STAND);
1625 }
1626 link_message(integer sender, integer num, string str, key id){
1627 if(str=="@go")
1628 state ProcessNPCLine;
1629 ProcessLink(str);
1630 }
1631 // if touched by owner while running code, make a menu
1633 if(checkPerms()) {
1634 TimerEvent(0); // stop the NPC from ticking
1635 showMenu = TRUE;
1636 state default;
1637 } else
1638 makeUserMenu();
1639
1640 }
1641
1642 changed(integer change) {
1643 if(change & CHANGED_REGION_RESTART)
1644 state NPCGo;
1645 }
1646 on_rez(integer num) {
1648 }
1649 }
1650
1651
1652
1653 state delete {
1654 state_entry() {
1655 // DEBUG("state delete");
1656 llSleep(llFrand(5));
1657 osNpcRemove(NPCKey());
1658 }
1659 link_message(integer sender, integer num, string str, key id) {
1660 if(str == "@npc_start")
1661 {
1662 state NPCGo;
1663 }
1664
1665 }
1666
1667 // No on_rez or changed event needed, the only way out is a link message
1668 }
1669
1670 // __END__

All In One NPC Recorder and Player

All in one NPC recorder player appearance notecard will go here

Category: NPC
By : Ferd Frederix
Created: 2015-10-12 Edited: 2016-11-09
Worlds: OpenSim


This script by Ferd Frederix may be used in any manner, modified, and republished.  Unless specified otherwise, my scripts are always free and open source.  Objects made with these scripts may be sold with no restrictions.  All I ask is that you point others to this location should they ask you about it and to not sell this script, unless it is for $0 L. Please help improve my work by reporting bugs and improvements.

1
2 the player appearance notecard will go here - it is automatically generated.

All In One NPC Recorder and Player

All in one NPC recorder player appearance notecard will go here

Category: NPC
By : Ferd Frederix
Created: 2015-10-12 Edited: 2016-11-09
Worlds: OpenSim


This script by Ferd Frederix may be used in any manner, modified, and republished.  Unless specified otherwise, my scripts are always free and open source.  Objects made with these scripts may be sold with no restrictions.  All I ask is that you point others to this location should they ask you about it and to not sell this script, unless it is for $0 L. Please help improve my work by reporting bugs and improvements.

1
2 A second player appearance notecard can go here. You load it with the @apperance=!Appearance2 command

All In One NPC Recorder and Player

All-In-One Animator with new commands

Category: NPC
By : Ferd Frederix
Created: 2015-10-12 Edited: 2016-11-09
Worlds: OpenSim


This script by Ferd Frederix may be used in any manner, modified, and republished.  Unless specified otherwise, my scripts are always free and open source.  Objects made with these scripts may be sold with no restrictions.  All I ask is that you point others to this location should they ask you about it and to not sell this script, unless it is for $0 L. Please help improve my work by reporting bugs and improvements.

1 I have someting new that may help greatly with NPC's. It's a new version of the All-In-One animator - it has the usualy commands to crate a NPC, control it ina routine, such as walking, sitting, or animating,
2
3 It also accepts the new command, @appearance=Notecard which can switch the appearance at any time.
4
5 It also accepts external commands from simple sensors, collision, or touch events that will control the NPC.

All In One NPC Recorder and Player

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 : Ferd Frederix
Created: 2015-10-12 Edited: 2016-11-09
Worlds: OpenSim


This script by Ferd Frederix may be used in any manner, modified, and republished.  Unless specified otherwise, my scripts are always free and open source.  Objects made with these scripts may be sold with no restrictions.  All I ask is that you point others to this location should they ask you about it and to not sell this script, unless it is for $0 L. Please help improve my work by reporting bugs and improvements.

1 // This is Rev 3.0.1 07/17/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
21 //*******************************************************************//
22
23 // Instructions on how to use this is at http://www.outworldz.com/opensim/posts/NPC/
24 // This is an OpenSim-only script.
25 // Author: Ferd Frederix aka Fred Beckhusen - fred@mitsi.com
26
27 ////////////////////////////////////////////////////////////////////////////////////////////
28 // Original code was Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 //
29 ///////////////////////////////////////////////////////////////////////////////////////////
30 // Please see: http://www.gnu.org/licenses/gpl.html for legal details, //
31 // rights of fair usage, the disclaimer and warranty conditions. //
32 ///////////////////////////////////////////////////////////////////////////////////////////
33 // The original NPC controller was from http://was.fm/opensim:npc
34 // Extensive additions and bug fixes by Fred Beckhusem, aka Ferd Frederix, fred@mitsi.com
35 // llSensor had two params swapped
36 // @Wander would wander where it had rezzed, not where it was.
37 // There was no 'no_sensor' event in sit, so if a @sit failed, the NPC got stuck
38 // 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.
39 // New code:
40 // Merged with new Route recorder and notecard writer
41 // If the NPC failed to reach a destination it never moved on. Added WAIT global to tune this
42 // Exposed many tunable variables and ported the code to LSLEditor.
43 // Added floating point to times in notecard.
44
45 // Added @sound, @randsound, @whisper, @shout, and @cmd controls.
46 //
47 // notecards integers are not floats for better control
48 //
49 // Link Messages may be used to perform external control by injecting @commands into the stream of actions
50 // Example:
51 // To chat something, such as with a chat robot
52 // llMessageLinked(LINK_SET,0,"@npc_say=Hello","");
53
54 // This script assumes that NPCs and OSSl scripting is enabled in the OpenSim configuration.
55 // In order to enable them, the following changes must be made in the OpenSim.ini configuration file:
56 //
57 // ; Turn on OSSL
58 // AllowOSFunctions = true
59 // OSFunctionThreatLevel = Severe
60
61 //[NPC]
62 // ;# {Enabled} {} {Enable Non Player Character (NPC) facilities} {true false}
63 // Enabled = true
64 //
65 // and then the server has to be restarted.
66
67
68 // Commands: All commands begin with an @ sign. All other lines are ignored
69 // @commands may have optional parameters. The syntax is always:
70 // @cmd=parm1|parm2
71 // NaN in the table below meand Not a Number. This means there is no parameter
72
73 //Command First Parameter Second Parameter Description
74 //@spawn name location (vector) Rezzes an NPC with name at a location.
75 //@appearance NoteCardName NaN switch the NPC appearance to a new notecard
76 //@walk destination (vector) NaN Makes the NPC walk to destination.
77 //@fly destination (vector) NaN Makes the NPC fly to destination.
78 //@land destination (vector) NaN Makes the NPC land at destination.
79 //@say string NaN Makes the NPC speak a phrase.
80 //@whisper string NaN Makes the NPC whisper a phrase.
81 //@shout string NaN Makes the NPC shout a phrase.
82 //@pause seconds (float) NaN Makes the NPC wait for a multiple of seconds.
83 //@wander radius (float) cycles (integer) Makes the NPC wander in radius, for cycles seconds.
84 //@delete NaN NaN Removes the NPC. Requires a link message to continue
85 //@goto label (string) NaN Jump to the label label in the script.
86 //@animate animation (string) time (float) Makes the NPC trigger the animation animation for time seconds.
87 //@sound sound_name NaN plays a sound from inventory
88 //@randsound NaN NaN Plays a random sound from inventory
89 //@rotate degrees (float) NaN Rotate the NPC degrees around the Z axis.
90 //@sit primitive name NaN Sit on a primitive with a given name.
91 //@stand NaN NaN If sitting on a primitive, stand up.
92 //@cmd channel (integer) string Says string on channel, for controlling external gadgets
93 //@stop NaN NaN Halts the NPC script indefinitely. Can be started with a link message
94 //@go NaN NaN Continues on next notecard line, for use in link messages
95 //@speed speed (float) NaN from 0 to N, where 1.0 ius a normal speed of an avatar. 0.2 is a turtle.
96 //@notecard notename (string) NaN load a new Path notecard
97
98 //////////////////////////////////////////////////////////
99 // DEBUG //
100 //////////////////////////////////////////////////////////
101 integer debug = TRUE; // set to TRUE or FALSE for debug chat on various actions
102 integer Editor = FALSE; // set to to TRUE to working in LSLEditor, FALSE for in-world.
103 // you must also include the NPC commands found in the other script since LSLEditor does not support OpenSim
104 integer iTitleText = TRUE; // set to TRUE to see debug info in text above the controller
105
106 //////////////////////////////////////////////////////////
107 // TUNABLE CONFIGURATION //
108 //////////////////////////////////////////////////////////
109 float TIMER = 0.5; // how often the system checks the distance traveled. Fastest you can go is 0.5 seconds
110 float QUICK = 0.020; // when we need to move to the next state, we use a QUICK timer
111 string Appearance = "!Appearance"; // The name of the recorded Appearance notecard
112 string Notecard = "!Path"; // The name of the recorded routes
113 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
114 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
115 integer WANDERRAND = TRUE; // set to TRUE and they will pause during wanders a random number of seconds
116 float WANDERTIME = 3.0; // how long they stand after each @wander,if WANDERRAND is FALSE. If WANDERRAND is TRUE, this is the max time
117 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.
118 float RANGE = 96.0; // 1 to N meters - anyone this close to the controller will start NPCS if Sensor button is clicked
119 float REZTIME = 5.0; // wait this long for NPC to rez in, then start the process
120 string STAND = "Stand"; // the name of the default Stand animation
121 string WALK = "Walk"; // the name of the default Walk animation
122 string FLY = "Fly"; // the name of the default Fly animation
123 string RUN = "Run"; // the name of the default Run animation
124 string LAND = "Land"; // the name of the default land animation ( for birds only)
125 float OffsetZ = 0.5; // appear 0.5 meter above ground, this is added to all destinations to keep them from sinking in.
126 float SPEEDMULT =1.0; // 1.0 = regular vatar speed. Smaller numbers slow down walks. Large numbers speed them up.
127 integer FLIGHT = 299; // For controllign wings. A channel that is shouted at when flight starts and ends. "flying" or "landing"
128
129 // DESCRIPTIONS FIELDS HAVE TO SURVIVE A RESET
130 // These vars are stored by saving them with KeyValueSet
131 // "pr" is a 0 if it is set for Owner Only, 1 for Group control
132 // "se" is "on" if Started
133 // "co" = "R" or "A" for relative or absolute addressing mode
134 // "key" = NPC key
135
136 // These Globals used to be stored in description. Moved to RAM in V1.6
137 float RAMPause; // @pause param
138 float RAMwd ; // @wander distance
139 integer RAMwc; // @wander count
140 float RAMrot; // @rotate
141 string RAMsit; // @sit primname
142 string RAManimationName; // @animate animation (string) time (float)
143 float RAManimationTime;
144
145 // other globals section
146 integer iChannel; // a listen channel, randomly assigned
147 integer iHandle; // the handle to it
148
149 // NPC controls
150 vector newDest ; // tmp storage for the walks
151 integer iWaitCounter ; // wait for this number of seconds for the NPC to reach a desrtination
152 string sNPCName; // the name of the NPC that may be in world. So we can remove it.
153 integer bNPC_STOP = FALSE; // boolean to reuse a listener
154 integer LinkControlled = FALSE; // set to TRUE by link messages so we do not remember them
155 float fTimerVal ; // how long we wait when wandering (calculated)
156
157 // 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.
158 // OS_NPC_NOT_OWNED will create an 'unowned' NPC that will respond to any script that has OSSL permissions to call osNpc* commands.
159 integer NPCOptions = OS_NPC_CREATOR_OWNED; // only yhe owner of this box can control this NPC.
160
161 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
162
163 integer NPCWalkOption; // Some notes for what happens to NPCWalkOption:
164 // 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.
165 // 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
166 //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.
167 // OS_NPC_RUNNING - if given, NPC avatar moves at running/fast flying speed, otherwise moves at walking/slow flying speed.
168
169 // menus
170 string mSensor="Sensor"; // Sensor or "No Sensor"
171
172 list lAtButtons = ["Menu","-", "More", "@run","@walk","@fly", "@land","@wander","@sit", "@stand","@animate","@rotate"];
173 list lMenu2 = ["<<", "@comment", "@stop", "@say","@whisper","@shout", "@sound","@randsound", "@speed", "@cmd", "@pause", "@delete" ];
174
175 string sCommand; // place to store a command for two-prompted ones
176 string sParam2; // place to store a prompt for two-prompted ones
177 string priPub = "Owner Only"; // Private or Group
178 key kUserKey; // the person who is controlling the avatar, not the Owner
179 // the command lists
180 list lCommands; // commands are stored here
181 list lNPCScript; // Storage for the NPC script.
182 string npcAction; // Storage for the next action. @cmd=0|hello, this becomes @cmd
183 string npcParams; // Storage for the param, @cmd=0|hello, this becomes 0|hello
184
185 // misc vars
186 string sNotecard; // commands are stored here temporarily for dumping
187 vector vWanderPos; // a place to wander to
188 string lastANIM ; // last animation run
189 // Sensor
190 integer avatarPresent; // Sensor sets this flag when people are within Range.
191
192 // Coordinate control
193 vector vInitialPos ; // Vector that will be filled by the script with the initial starting position in region coordinates.
194 vector vDestPos = ZERO_VECTOR; // Storage for destination position.
195 string relAbs = "Relative"; // absolute vs relative positioning
196
197
198 // STATES
199 integer MENU ; // processing a dialog box state, may be concurrent with STATE
200 integer STATE; // state storage
201 integer MakeNotecard = 1; // displaying a text box for NPC name
202 integer RecordPath = 2; // displaying a path notecard menu
203 integer NobodyHome = 3; // looking for an avatar
204 integer Spawning = 4; // spawning an avatar
205 integer Animate = 5; // animation timer needed
206 integer Walking = 6; // Hey! I am walking here!
207 integer Wander = 7; // Wandering around neeeds a timer, too
208 integer WanderHold = 8; // We reached a wander point
209 integer DoProcess = 9; // Set this to make it process a new command
210
211 key gNpcKey = NULL_KEY; // global key storage for the one NPC, to save CPU cycles
212
213 ///////////////////////////////////////////////////////////////////////////
214 // FUNCTIONS //
215 ///////////////////////////////////////////////////////////////////////////
216
217 // Do* functions are much like states from the old scripts.
218
219 // Save a Path notecard
220 DoSave()
221 {
222 STATE = MakeNotecard;
223 makeText("Stand where you want the NPC to appear, and enter the NPC Name");
224 }
225
226 // This function is used to record the path for the NPC
227 // Each command can take 0, 1, or 2 params
228 DoMenuForCommands() {
229 makeMenu(lAtButtons);
230 }
231
232
233 // No one is here when sensors were on, so we kill off the NPC
234 DoNobodyHome()
235 {
236 DEBUG("Nobody Home");
237 STATE = NobodyHome;
238 osNpcRemove(NPCKey());
239 SaveKey(NULL_KEY);
240 TimerEvent(TIMER); // keep ticking to sense avatars
241 }
242
243 // Create a NPC
244 DoSpawn() {
245 DEBUG("state spawn");
246
247 // see if there is already one out there.
248 if(NPCKey() != NULL_KEY)
249 return;
250
251 STATE = Spawning;
252
253 list name = llParseString2List(sNPCName, [" "], []);
254 // notecard is stored as offsets from this box with relative addressing. Convert to absolute
255 if(relAbs == "Relative"){
256 vInitialPos += llGetPos();
257 }
258
259 DEBUG("Rez NPC:" + (string) vInitialPos);
260 key aKey = osNpcCreate(llList2String(name, 0), llList2String(name, 1), vInitialPos, Appearance, NPCOptions);
261
262 SaveKey(aKey ); // save in desceription and global, too
263
264 osSetSpeed(aKey,SPEEDMULT); // 1.9 speed multiplier
265 TimerEvent(REZTIME);
266 NPCAnimate(STAND);
267 }
268
269 DoRotate() {
270 DEBUG("state rotate");
271 osNpcSetRot(NPCKey(), llEuler2Rot(<0,0,RAMrot> * DEG_TO_RAD));
272 }
273
274 DoSit() {
275 DEBUG ("state sit - looking for " + RAMsit);
276 llSensor(RAMsit, "", PASSIVE|ACTIVE|SCRIPTED, 96, PI);
277 }
278
279 DoStand() {
280
281 DEBUG("state stand");
282 osNpcStand(NPCKey());
283 }
284
285
286 DoAnimate() {
287
288 DEBUG("state animate");
289 STATE = Animate;
290 NPCAnimate(RAManimationName);
291 TimerEvent(RAManimationTime);
292 }
293
294 DoWalk() {
295
296 DEBUG("NPCWalkOption = " + (string) NPCWalkOption);
297 STATE = Walking;
298
299 // walk, fly, run, land
300 if(walkstate == 1) {
301 NPCAnimate(WALK);
302 } else if(walkstate == 2) {
303 llShout(FLIGHT,"flying");
304 NPCAnimate(FLY);
305 } else if(walkstate == 3) {
306 NPCAnimate(RUN);
307 } else if(walkstate == 4) {
308 NPCAnimate(LAND);
309 }
310 newDest = vDestPos ;
311 newDest.z += OffsetZ;
312
313 // notecard is stored as offsets from this box with relative addressing. Convert to absolute
314 if(relAbs == "Relative"){
315 newDest += llGetPos();
316 }
317
318 DEBUG("Moveto:" + (string) newDest);
319 osNpcMoveToTarget(NPCKey(), newDest, NPCWalkOption);
320 iWaitCounter = WAIT; // wait 60 seconds to get to a destination.
321 TimerEvent(TIMER);
322 }
323
324
325 DoWander(){
326 DEBUG("state wander");
327 STATE = Wander;
328
329 vector point = CirclePoint(RAMwd);
330 DEBUG("CirclePoint:" + (string) point);
331 vWanderPos = vDestPos + point;
332 DEBUG("vWanderPos:" + (string) vWanderPos);
333
334 fTimerVal = WANDERTIME; // default time to pause after each wander
335 if(WANDERRAND)
336 fTimerVal = llFrand(WANDERTIME); // override, they want random times
337
338 NPCAnimate(WALK);
339
340 DEBUG("Wander to:" + (string) vWanderPos);
341
342 osNpcMoveToTarget(NPCKey(), vWanderPos, NPCWalkOption);
343 iWaitCounter = WAIT; // wait 60 seconds to get to a destination.
344 TimerEvent(TIMER); // first time we wait for the short timer.
345 }
346
347 DoWanderhold() {
348
349 DEBUG("Wander Hold");
350 STATE = WanderHold;
351
352 // now that we have reached a wander spot, slow the timer down to the desired value
353 TimerEvent(fTimerVal);
354 }
355
356 // @pause=10 will do nothing for 10 seconds
357 DoPause() {
358
359 DEBUG("state pause");
360 TimerEvent(RAMPause);
361 }
362
363
364 // @stop makes the NPC stop moving in whatever state it is in. You have to linkmessage to get moving again
365 DoStop() {
366 DEBUG("NPC is Stopped");
367 LinkControlled = TRUE; // Link controlled - we mnust have a @go to continue with notecards
368 TimerEvent(0);
369 }
370
371
372 // @delete removes the NPC forever. next command starts it up again at the beginning
373 DoDelete() {
374 DEBUG("state delete");
375
376 osNpcRemove(NPCKey());
377 SaveKey(NULL_KEY);
378
379 lNPCScript = []; // next command starts the NPC into action at the beginning
380 TimerEvent(0);
381 }
382
383 // change the appearance of the NPC
384 DoAppearance(string notecard) {
385 DEBUG("state appearance");
387 DEBUG("Load appearance " + notecard);
388 osNpcLoadAppearance(NPCKey(),notecard);
389 }
390 }
391
392 // Change the avatar speed
393 DoSpeed(string speed) {
394 float newspeed = (float) speed;
395 if(newspeed > 0.1 && newspeed < 5.0) // sanity check
396 osSetSpeed(NPCKey(),newspeed);
397 }
398 DoNewNote (string card) {
399 DEBUG("Load Notecard " + card);
400 NPCReadNoteCard(card);
401 LinkControlled = FALSE;
402 }
403
404 // This loops over the notecard, processing each command
405 DoProcessNPCLine() {
406 DEBUG("ProcessNPCLine");
407 STATE = 0;
408
409 if(! llGetListLength(lNPCScript)) {
410 DEBUG("Read Notecard");
411 NPCReadNoteCard(Notecard);
412 LinkControlled = FALSE;
413 }
414
415 if(LinkControlled == TRUE) {
416 TimerEvent(0);
417 DEBUG("Waiting for input");
418 return;
419 }
420
421 string next = llList2String(lNPCScript, 0); // get the next command
422 DEBUG("Execute:" + next);
423 lNPCScript = llDeleteSubList(lNPCScript, 0, 0); // delete it
424
425 if(llGetListLength(lNPCScript) == 0) {
426 DEBUG("EOF");
427 }
428 ProcessCmd(next);
429
430 }
431
432 ProcessCmd(string cmd) {
433
434 DEBUG("ProcessCmd:" + cmd);
435
436 if(llGetSubString(cmd, 0, 0) != "@") {
437 DEBUG("ignoring");
438 STATE = DoProcess;
439 TimerEvent(QUICK); // this is so we do not recurse the stack
440 return;
441 }
442
443 list data = llParseString2List(cmd, ["="], []);
444 npcAction = llToLower(llStringTrim(llList2String(data, 0), STRING_TRIM));
445
446 DEBUG("Action:" + npcAction);
447 npcParams = llStringTrim(llList2String(data, 1), STRING_TRIM);
448
449 @commands;
450
451 ProcessSensor();
452 if(! avatarPresent){
453 DoNobodyHome();
454 DEBUG("No avatar nearby");
455 return;
456 }
457 else if(npcAction == "@spawn") {
458 DEBUG("@spawn");
459 list spawnData = llParseString2List(npcParams, ["|"], []);
460 sNPCName =llList2String(spawnData, 0); // V 1.6 name in RAM
461
462 list spawnDest = llParseString2List(llList2String(spawnData, 1), ["<", ",", ">"], []);
463 vInitialPos.x = llList2Float(spawnDest, 0);
464 vInitialPos.y = llList2Float(spawnDest, 1);
465 vInitialPos.z = llList2Float(spawnDest, 2);
466 DoSpawn();
467 return;
468 }
469 else if(npcAction == "@stop") {
470 DEBUG("@stop");
471 DoStop();
472 return;
473 }
474 else if(npcAction == "@goto") {
475 DEBUG("goto");
476 integer lastIdx = llGetListLength(lNPCScript)-1;
477 lNPCScript = llDeleteSubList(lNPCScript, lastIdx, lastIdx);
478 // Wind commands till goto label.
479 @wind;
480 string next1 = llList2String(lNPCScript, 0);
481 lNPCScript = llDeleteSubList(lNPCScript, 0, 0);
482 lNPCScript += next1;
483 if(next1 != npcParams) jump wind;
484 // Wind the label too.
485 next1 = llList2String(lNPCScript, 0);
486 lNPCScript = llDeleteSubList(lNPCScript, 0, 0);
487 lNPCScript += next1;
488 // Get next command.
489 list data1 = llParseString2List(next1, ["="], []);
490 npcAction = llToLower(llStringTrim(llList2String(data1, 0), STRING_TRIM));
491 npcParams = llStringTrim(llList2String(data1, 1), STRING_TRIM);
492 // Reschedule.
493 jump commands;
494 }
495 else if(npcAction == "@sound") {
496 DEBUG("sound");
497 llTriggerSound(npcParams,1.0);
498 }
499 else if(npcAction == "@randsound") {
500 DEBUG("@randsound");
502 integer rand = llCeil(llFrand(N)) -1; // pick a random sound
504 llTriggerSound(toPlay,1.0);
505 }
506 else if(npcAction == "@walk") {
507 DEBUG("@walk");
508 GetDest(npcParams);
509 walkstate = 1;// walking
510 NPCWalkOption = OS_NPC_NO_FLY ;
511 DoWalk();
512 return;
513 }
514 else if(npcAction == "@fly") {
515 GetDest(npcParams);
516 walkstate = 2;// flying
517 NPCWalkOption = OS_NPC_FLY ;
518 DoWalk();
519 return;
520 }
521 else if(npcAction == "@run") {
522 DEBUG("@run");
523 GetDest(npcParams);
524 walkstate = 3;// running
525 NPCWalkOption = OS_NPC_NO_FLY | OS_NPC_RUNNING;
526 DoWalk();
527 return;
528 }
529 else if(npcAction == "@land") {
530 DEBUG("@land");
531 GetDest(npcParams);
532 walkstate = 4;// landing
533 NPCWalkOption= OS_NPC_FLY | OS_NPC_LAND_AT_TARGET ;
534 DoWalk();
535 return;
536 }
537 else if(npcAction == "@say") {
538 DEBUG("@say " + npcParams);
539 osNpcSay(NPCKey(), 0, npcParams);
540 }
541 else if(npcAction == "@shout") {
542 DEBUG("@shout");
543 osNpcShout(NPCKey(),0, npcParams);
544 }
545 else if(npcAction == "@whisper") {
546 DEBUG("@whisper");
547 osNpcWhisper(NPCKey(),0, npcParams);
548 }
549 // speak a command on a channel, so you can open doors and control stuff.
550 else if(npcAction == "@cmd") {
551 DEBUG("@cmd");
552 list dataToSpeak = llParseString2List(npcParams, ["|"], []);
553 string channel = llList2String(dataToSpeak,0);
554 DEBUG("Channel:"+(string) channel);
555 integer iChannel = (integer) channel;
556 string stringToSpeak = llList2String(dataToSpeak,1);
557 llSay(iChannel, stringToSpeak);
558 }
559 // stop everything
560 else if(npcAction == "@pause") {
561 DEBUG("@pause");
562 RAMPause = (float) npcParams;
563 DoPause();
564 return;
565 }
566 else if(npcAction == "@wander") {
567 DEBUG("@wander");
568 list wanderData = llParseString2List(npcParams, ["|"], []);
569 RAMwd = (float) llList2String(wanderData, 0);
570 RAMwc = (integer) llList2String(wanderData, 1);
571
572 vDestPos = osNpcGetPos(NPCKey()); // set the wander start
573 DEBUG("Starting at " + (string) vDestPos);
574 DoWander();
575 return;
576 }
577 else if(npcAction == "@rotate") {
578 DEBUG("@rotate");
579 RAMrot = (float) npcParams;
580 DoRotate();
581 }
582 else if(npcAction == "@sit") {
583 DEBUG("@sit");
584 RAMsit= npcParams;
585 DoSit();
586 return;
587 }
588 else if(npcAction == "@stand") {
589 DEBUG("@stand");
590 DoStand();
591 }
592 else if(npcAction == "@delete") {
593 DEBUG("@delete");
594 DoDelete();
595 return;
596 }
597 else if(npcAction == "@animate") {
598 DEBUG("@animate");
599 list animateData = llParseString2List(npcParams, ["|"], []);
600 RAManimationName = llList2String(animateData, 0);
601 RAManimationTime = (float) llList2String(animateData, 1);
602 DoAnimate();
603 return;
604 }
605 else if(npcAction == "@appearance" )
606 {
607 DEBUG("@appearance");
608 DoAppearance(npcParams);
609 }
610 else if(npcAction =="@speed") {
611 DEBUG("@speed");
612 DoSpeed(npcParams);
613 }
614 else if(npcAction =="@notecard") {
615 DEBUG("@notecard");
616 DoNewNote(npcParams);
617 }
618
619 STATE = DoProcess;
620 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
621 }
622
623
624
625 /////////////////// UTILITY Functions, not state-like //////////////////
626
627 // DEBUG(string) will chat a string or display it as hovertext if debug == TRUE
628 DEBUG(string str) {
629 if(debug)
630 llOwnerSay( str); // Send the owner debug info so you can chase NPCS
631 if(iTitleText)
632 llSetText(str,<1.0,1.0,1.0>,1.0); // show hovertext
633 }
634
635 GetDest(string npcParams) {
636 list dest = llParseString2List(npcParams, ["<", ",", ">"], []);
637 vDestPos.x = llList2Float(dest, 0);
638 vDestPos.y = llList2Float(dest, 1);
639 vDestPos.z = llList2Float(dest, 2);
640 }
641
642 NPCReadNoteCard(string Note) {
643 DEBUG("NPCReadNoteCard");
644 lNPCScript = llParseString2List(osGetNotecard(Note), ["\n"], []);
645 }
646
647 integer SenseAvatar()
648 {
649 //Returns a strided list of the UUID, position, and name of each avatar in the region
650 list avatars = llGetAgentList(AGENT_LIST_REGION ,[]);
651 integer numOfAvatars = llGetListLength(avatars);
652 if(numOfAvatars == 0)
653 {
654 DEBUG("No people");
655 return 0;
656 }
657 //DEBUG("Located " + (string)numOfAvatars + " avatars and NPC's");
658
659 integer nAvatars;
660 integer i;
661 for( i = 0;i < numOfAvatars; i++) {
662 key aviKey = llList2Key(avatars,i);
663 if(!osIsNpc(aviKey)) {
664 list detail = llGetObjectDetails(aviKey,[OBJECT_POS]);
665 vector pos = llList2Vector(detail,0);
666 float dist = llVecDist(pos, llGetPos());
667 if(dist < RANGE)
668 {
669 nAvatars++;
670 DEBUG("In range:" + llKey2Name(aviKey));
671 }
672 }
673 }
674 //DEBUG("Located " + (string) nAvatars + " avatars");
675 return nAvatars;
676 }
677
678 // 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.
679 integer checkPerms() {
680
681 integer group = (integer) KeyValueGet("pr");
682 if(! group)
683 priPub = "Owner Only";
684 else
685 priPub = "Group";
686
687
689 kUserKey = llDetectedKey(0);
690 return TRUE;
691 }
692
693 if( group && llDetectedGroup(0)) {
694 kUserKey = llDetectedKey(0);
695 return TRUE;
696 }
697 kUserKey = llDetectedKey(0);
698 return FALSE;
699 }
700
701
702
703 NPCAnimate(string anim)
704 {
705 DEBUG("Start Anim: " + anim);
707
708 if(lastANIM != anim) {
709 if(llStringLength(lastANIM)) {
710 osNpcStopAnimation(NPCKey(), lastANIM);
711 }
712 osNpcPlayAnimation(NPCKey(), anim);
713 lastANIM = anim;
714 }
715 } else {
716 llSay(DEBUG_CHANNEL, "No animation named " + anim);
717 }
718 }
719
720
721 TimerEvent(float timesent)
722 {
723 DEBUG("Setting timer: " + (string) timesent);
724 llSetTimerEvent(timesent);
725 }
726
727 // Kill a NPC by Name
728 Kill(string param)
729 {
730 integer count;
731 list avatars = osGetAvatarList(); // Returns a strided list of the UUID, position, and name of each avatar in the region except the owner.\
732 integer i;
733 integer j = llGetListLength(avatars);
734 for (i=0 ; i <= j; i+=3){
735
736 string desired = llList2String(avatars,i+2);
737 desired = llStringTrim(desired,STRING_TRIM); // should not be needed but is needed
738
739 if(desired == param){
740 vector v = llList2Vector(avatars,i+1);
741 key target = llList2Key(avatars,i); // get the UUID of the avatar
742 osNpcRemove(target);
743 SaveKey(NULL_KEY );
744 llOwnerSay("Removed " + param+ " at location " + (string) v);
745 count++;
746 }
747 }
748 if(count)
749 llOwnerSay("Removed " + (string) count + " NPC's");
750 else
751 llOwnerSay("Could not locate " + param);
752 }
753
754
755 // return a String for the position we are at. Strings used as the caller wants strings
756 string Pos()
757 {
758 vector where = llGetPos(); // find the box position
759
760 where.z += OffsetZ; // use the ground position + an offset
761
762 if(Editor)
763 where = <128,128,31 + llFrand(1)>; // center of sim for editing
764
765 // if attached the height will be too high by 1/2 the agent size
766 if(llGetAttached()) {
768 float Z = size.z;
769 where.z -= Z/2;
770 }
771
772 // DEBUG("Pos= " + (string) where);
773 return (string) where;
774 }
775
776 // setup a menu with a timer for timeouts, called by all make*()
777 menu()
778 {
779 llListenRemove(iHandle);
780 iChannel = llCeil(llFrand(100000) + 20000);
781 iHandle = llListen(iChannel,"","","");
782 TimerEvent(120.0);
783 MENU = TRUE;
784 }
785
786 // make a text box
787 makeText(string Param)
788 {
789 menu();
790 llTextBox(kUserKey, Param, iChannel);
791 }
792
793 // top level menu
794 makeMainMenu()
795 {
796 menu();
797 list buttons = ["Appearance","Recording","Save","Help","-","Erase RAM", priPub,relAbs,"-","Stop NPC",mSensor,"Start NPC"];
798 llDialog(kUserKey,(string) llGetListLength(lCommands) + " Records",buttons,iChannel);
799 }
800
801
802 // Rev 1.4
803 // top level menu for non group/ non owners
804 makeUserMenu()
805 {
806 if(!allowUsers) return;
807
808 menu();
809 list buttons = ["Start NPC","Stop NPC"];
810 llDialog(kUserKey,"Choose",buttons,iChannel);
811 }
812
813
814
815 // programmable menu for @commands
816 makeMenu(list buttons)
817 {
818 menu();
819 llDialog(kUserKey,(string) llGetListLength(lCommands) + " Record",buttons,iChannel);
820 }
821
822
823 // make one or two text boxes with prompts
824 Text(string cmd, string p1, string p2)
825 {
826 sCommand = cmd;
827 sParam2 = "";
829 sParam2 = p2;
830
831 makeText(p1);
832 }
833
834 // Set the Avatar Present flag - if sensors are off and we are forece run, there will be one present.
835 ProcessSensor()
836 {
837 integer SensorOn;
838 if("on" == KeyValueGet("se"))
839 {
840 SensorOn = TRUE; // we need to scan for avatars
841 } else {
842 SensorOn = FALSE; // we need to scan for avatars
843 }
844 //DEBUG("Sensor:" + (string) SensorOn);
845
846 integer n = SenseAvatar();
847
848 if(SensorOn && n)
849 avatarPresent = TRUE; // someone is here and we need to tell the system to run
850 else if(SensorOn && !n)
851 avatarPresent = FALSE; // someone is not here and we need to tell the system to stop
852 else
853 avatarPresent = TRUE; // someone is effectivley always here when sensor is off and we are running
854
855 //DEBUG("Avatar Present: " + (string) avatarPresent);
856 }
857
858 vector CirclePoint(float radius) {
859 float x = llFrand(radius *2) - radius; // +/- radius, randomized
860 float y = llFrand(radius *2) - radius; // +/- radius, randomized
861 return <x, y, 0>; // so this should always happen
862 }
863
864 string KeyValueGet(string var) {
865 list dVars = llParseString2List(llGetObjectDesc(), ["&"], []);
866 do {
867 list data = llParseString2List(llList2String(dVars, 0), ["="], []);
868 string k = llList2String(data, 0);
869 if(k != var) jump continue;
870 //DEBUG("got " + var + " = " + llList2String(data, 1));
871 return llList2String(data, 1);
872 @continue;
873 dVars = llDeleteSubList(dVars, 0, 0);
874 } while(llGetListLength(dVars));
875 return "";
876 }
877
878 KeyValueSet(string var, string val) {
879
880 //DEBUG("set " + var + " = " + val);
881 list dVars = llParseString2List(llGetObjectDesc(), ["&"], []);
882 if(llGetListLength(dVars) == 0)
883 {
884 llSetObjectDesc(var + "=" + val);
885 return;
886 }
887 list result = [];
888 do {
889 list data = llParseString2List(llList2String(dVars, 0), ["="], []);
890 string k = llList2String(data, 0);
891 if(k == "") jump continue;
892 if(k == var && val == "") jump continue;
893 if(k == var) {
894 result += k + "=" + val;
895 val = "";
896 jump continue;
897 }
898 string v = llList2String(data, 1);
899 if(v == "") jump continue;
900 result += k + "=" + v;
901 @continue;
902 dVars = llDeleteSubList(dVars, 0, 0);
903 } while(llGetListLength(dVars));
904 if(val != "") result += var + "=" + val;
906 }
907
908
909 // clear RAM
910 Clr() {
911
912 lCommands = [];
913 llOwnerSay("RAM Memory cleared. Notecards, if any, are not modified.");
914 makeMainMenu();
915 }
916
917 integer checkNoteCards()
918 {
919 // Check that they have saved an Appeaance and Path notecard
920 integer num = llGetInventoryNumber(INVENTORY_NOTECARD); // how many notecards overall
921
922 integer i;
923 integer count;
924 for (; i < num; i++){
926 count++;
928 count++;
929 }
930 DEBUG("Checked " + (string) count + " Notecards");
931 // if we have both, run the NPC
932 return count;
933 }
934
935 Update(string SName) {
936
937 // delete all NPC*scripts except myself
938 integer i;
940 for (i = 0; i < j; i++) {
942 string match = llGetSubString(name,0,2);
943 if(match == SName && llGetScriptName() != name)
944 {
946 llOwnerSay("Upgraded");
947 }
948 }
949
950 }
951
952 // Get all default saved params from the Description
953 GetSwitches()
954 {
955 string rA = KeyValueGet("co"); // Get the remembered menu setting for Abs Vs Relative
956 if(rA == "A")
957 relAbs = "Absolute";
958 else if(rA == "R")
959 relAbs = "Relative";
960 else
961 relAbs = "Absolute";
962
963
964 // reenable NPC if sensor is on.
965 if("on" == KeyValueGet("se"))
966 {
967 mSensor = "Sensor";
968 ProcessSensor(); // fake 1 avatar to get it rezzed
969 } else {
970 mSensor = "No Sensor";
971 }
972 }
973
974
975 SaveKey(key akey)
976 {
977 DEBUG("Saving Key of " + (string) akey);
978 KeyValueSet("key", akey);
979 if(KeyValueGet("key") != akey)
980 {
981 DEBUG("Fatal error, cannot save key");
982 }
983 gNpcKey = akey;
984 }
985
986
987 key NPCKey()
988 {
989 key akey = gNpcKey; // from cached copy
990 // gNpcKey saves a lot of CPU processing by caching the key, if blank we get it from the description
991 if(gNpcKey == NULL_KEY)
992 {
993 DEBUG("Get DKey");
994 akey = KeyValueGet("key"); // from Description of the prim
995 }
996 DEBUG("NPC KEY:" + (string) akey);
997 return akey;
998 }
999
1000
1001 /////////////////// CODE BEGINS //////////////////
1002
1003
1004 default
1005 {
1006 changed(integer change) {
1007 if(change & CHANGED_REGION_START) {
1009 }
1010 }
1011
1012 on_rez(integer start_param)
1013 {
1015 }
1016
1017 state_entry() {
1018
1019 llSetText("",<1,1,1>,1.0); // clr all hovertext- we may not be using it.
1020 DoDelete(); // kill any NPC that is out running
1021 Update("NPC"); // If dragged and ropped into a prim with any script named "NPC...", this will replace it.
1022 GetSwitches(); // Get all default saved params from the Description
1023 }
1024
1025
1027 { // if touched, make a menu
1028
1029 if(checkPerms()) {
1030 if(RecordPath == STATE) {
1031 makeMenu(lAtButtons);
1032 } else {
1033 makeMainMenu();
1034 }
1035 } else {
1036 makeUserMenu();
1037 }
1038 }
1039
1040 // menu listener
1041 listen(integer iChannel, string name, key id, string message) {
1042
1043 if(MENU) {
1044 llListenRemove(iHandle);
1045 MENU = 0; // menu is off
1046 iHandle = 0;
1047 }
1048
1049 if(message == "Stop NPC")
1050 {
1051 lNPCScript = []; // force reload of notecard
1052 if(NPCKey() != NULL_KEY){
1053 Kill(sNPCName);
1054 sNPCName = "";
1055 } else {
1056 bNPC_STOP = TRUE;
1057 makeText("Enter name of an NPC to stop");
1058 }
1059 }
1060 else if(message == "Menu" ) {
1061 makeMainMenu();
1062 }
1063 else if(message == "Erase RAM"){
1064 Clr();
1065 }
1066 else if(message == "Relative"){
1067 relAbs = "Absolute";
1068 KeyValueSet("co","A"); // remember coordinates = A
1069 Clr();
1070 }
1071 else if(message == "Absolute"){
1072 relAbs = "Relative";
1073 KeyValueSet("co","R"); // remember coordinates = R
1074 Clr();
1075 }
1076 else if(message == "Recording"){
1077 DoMenuForCommands(); // show them the recording menu
1078 }
1079 else if(message == "Owner Only") {
1080 priPub = "Group";
1081 KeyValueSet("pr","1");
1082
1083 llOwnerSay("Group members have control");
1084 makeMainMenu();
1085 }
1086 else if(message == "Group") {
1087 priPub = "Owner Only";
1088 KeyValueSet("pr","0");
1089 llOwnerSay("Only you have control");
1090 makeMainMenu();
1091 }
1092 else if(message == "Sensor") {
1093 mSensor ="No Sensor";
1094 KeyValueSet("se", "off");
1095 llOwnerSay("Sensors now Off");
1096 makeMainMenu();
1097 }
1098 else if(message == "No Sensor") {
1099 mSensor ="Sensor";
1100 llOwnerSay("Sensors now On");
1101 KeyValueSet("se", "on");
1102
1103 integer count = checkNoteCards();
1104 if(count >= 2) {
1105 DEBUG("Notecards approved , calling DoProcessNPCLine");
1106 DoProcessNPCLine();
1107 return;
1108 }
1109 if(Editor) {
1110 DoProcessNPCLine();
1111 return;
1112 }
1113
1114 llOwnerSay("You have not saved a recording and/or appearance, so you cannot start a NPC");
1115 makeMainMenu();
1116 }
1117 else if(message == "Appearance") {
1118 llRemoveInventory(Appearance); // delete the notecard
1119 osAgentSaveAppearance(kUserKey,Appearance); // make the ntecard
1120 llOwnerSay("Your outfit has been saved");
1121 makeMainMenu();
1122 }
1123 else if(message == "Save") {
1124 if(llGetListLength(lCommands) == 0) {
1125 llOwnerSay("Nothing recorded, you need to make a recording first");
1126 makeMainMenu();
1127 return;
1128 }
1129 DoSave();
1130 }
1131 else if(message == "Help"){
1132 llLoadURL(kUserKey,"Click to view help","http://www.outworldz.com/opensim/posts/NPC/");
1133 makeMainMenu();
1134 }
1135 else if(message == "Start NPC") {
1136 integer count = checkNoteCards();
1137 if(Editor) {
1138 DoProcessNPCLine();
1139 return;
1140 }
1141
1142 if(count >= 2) {
1143 DEBUG("Notecards approved , calling DoProcessNPCLine");
1144 DoProcessNPCLine();
1145 return;
1146 }
1147
1148 llOwnerSay("You have not saved a recording or maybe an appearance, so we cannot start a NPC");
1149
1150 }
1151 else if(bNPC_STOP){
1152 bNPC_STOP = FALSE;
1153 Kill(message);
1154 }
1155 else if(message == "More"){
1156 makeMenu(lMenu2);
1157 }
1158 else if(message == "<<") {
1159 makeMenu(lAtButtons);
1160 }
1161 else if(message == "@comment"){
1162 Text("# ","Enter a comment","");
1163 }
1164 else if(message == "@stop"){
1165 lCommands += "@stop"+ "\n";
1166 makeMenu(lAtButtons);
1167 }
1168 else if(message == "@run"){
1169 lCommands += "@run=" + Pos() + "\n";
1170 llOwnerSay("Recorded position: " + Pos());
1171 makeMenu(lAtButtons);
1172 }
1173 else if(message == "@fly"){
1174 lCommands += "@fly=" + Pos() + "\n";
1175 llOwnerSay("Recorded position: " + Pos());
1176 makeMenu(lAtButtons);
1177 }
1178 else if(message == "@land"){
1179 lCommands += "@land=" + Pos() + "\n";
1180 llOwnerSay("Recorded position: " + Pos());
1181 makeMenu(lAtButtons);
1182 }
1183 else if(message == "@walk") {
1184 lCommands += "@walk=" + Pos() + "\n";
1185 llOwnerSay("Recorded position: " + Pos());
1186 makeMenu(lAtButtons);
1187 }
1188 else if(message == "@stop"){
1189 lCommands += "@stop"+ "\n";
1190 makeMenu(lAtButtons);
1191 }
1192 else if(message == "@sound"){
1193 Text("@sound=","Enter a sound name or UUID to trigger","");
1194 }
1195 else if(message == "@randsound"){
1196 lCommands += "@randsound"+ "\n";
1197 makeMenu(lAtButtons);
1198 }
1199 else if(message == "@say") {
1200 Text("@say=","Enter what the NPC will say","");
1201 }
1202 else if(message == "@whisper"){
1203 Text("@whisper=","Enter what the NPC will whisper","");
1204 }
1205 else if(message == "@shout"){
1206 Text("@shout=","Enter what the NPC will shout","");
1207 }
1208 else if(message == "@wander") {
1209 Text("@wander=","Enter radius to wander","Enter number of wanders");
1210 }
1211 else if(message == "@pause") {
1212 Text("@pause=","Enter time to pause","");
1213 }
1214 else if(message == "@rotate") {
1215 Text("@rotate=","Enter degrees to rotate","");
1216 }
1217 else if(message == "@sit"){
1218 Text("@sit=","Enter name of object to sit on","");
1219 }
1220 else if(message == "@cmd"){
1221 Text("@cmd=","Enter cjhannel to speak on","Enter text to speak");
1222 }
1223 else if(message == "@stand"){
1224 lCommands += "@stand\n";
1225 llOwnerSay("Stand Recorded");
1226 makeMenu(lAtButtons);
1227 }
1228 else if(message == "@animate"){
1229 Text("@animate=","Enter animation name to play","Enter time to play the animation");
1230 }
1231 else if(message == "@speed"){
1232 Text("@speed=","Enter a speed for the NPC, 1=100% normal speed, 0.5=50% speed","");
1233 }
1234 else if(! llStringLength(sParam2)) {
1235 lCommands += sCommand + message + "\n";
1236 llOwnerSay("Recorded");
1237 makeMenu(lAtButtons);
1238 }
1239 else if(llStringLength(sParam2)){
1240 sCommand = sCommand + message + "|";
1241 llOwnerSay("Recorded");
1242 makeText(sParam2);
1243 sParam2 = "";
1244 }
1245
1246
1247 // Save NPC name
1248 else if(MakeNotecard == STATE) {
1249 sNPCName = message; // in case we need to kill it.
1250
1251 vector vDest = (vector) Pos();
1252
1253 if(relAbs == "Relative")
1254 {
1255 vDest -= llGetPos(); // just an offset for relative
1256 }
1257 sNotecard = "@spawn=" + message + "|" + (string) vDest + "\n";
1258 integer i;
1259 integer j = llGetListLength(lCommands);
1260 for (; i < j; i++){
1261 // get the command to save to the notecard
1262 string line = llList2String(lCommands,i);
1263 if(relAbs == "Absolute") {
1264 sNotecard += line; // add the un-modified string to the notecard
1265 } else {
1266 // since we have to record absolute coords since we do not know where the box goes until they press Save,
1267 // we process the absolute to relative conversion for walks here
1268 list parts = llParseString2List(line,["="],[]); //get the @command
1269
1270 if(llList2String(parts,0) == "@walk") {
1271 vector vec = (vector) llList2String(parts,1) - llGetPos();
1272 sNotecard += "@walk=" + (string) vec + "\n";
1273 }
1274 else if(llList2String(parts,0) == "@fly") {
1275 vector vec = (vector) llList2String(parts,1) - llGetPos();
1276 sNotecard += "@fly=" + (string) vec + "\n";
1277 }
1278 else if(llList2String(parts,0) == "@run") {
1279 vector vec = (vector) llList2String(parts,1) - llGetPos();
1280 sNotecard += "@run=" + (string) vec + "\n";
1281 }
1282 else if(llList2String(parts,0) == "@land") {
1283 vector vec = (vector) llList2String(parts,1) - llGetPos();
1284 sNotecard += "@land=" + (string) vec + "\n";
1285 }
1286 else {
1287 sNotecard += line; // add the un-modified string to the notecard
1288 }
1289 }
1290 }
1291 llRemoveInventory(Notecard); // delete the old notecard
1292 osMakeNotecard(Notecard,sNotecard); // Makes the notecard.
1293 llOwnerSay("Commands notecard has been written");
1294 STATE = 0;
1295 } // MakeNotecard
1296 }
1297
1298 link_message(integer sender, integer num, string str, key id){
1299 DEBUG("Command In:" + str);
1300 if(str=="@go") {
1301 LinkControlled = FALSE; // No longer link controlled
1302 DEBUG("@go approved , calling DoProcessNPCLine");
1303 DoProcessNPCLine();
1304 } else {
1305 ProcessCmd(str);
1306 }
1307 }
1308
1309
1310 timer(){
1311 DEBUG("tick");
1312
1313 // if llDialog is up, kill the listener for the dialog box.
1314 if(iHandle) {
1315 llOwnerSay("Menu timed out");
1316 llListenRemove(iHandle);
1317 iHandle = 0;
1318 return; // ^^^^^^^^^^^^^^^^^^^^^^^
1319 }
1320 // if NoBodyHome, we are sensing for an avatar
1321 else if(NobodyHome == STATE) {
1323 DoNobodyHome();
1324 return; // ^^^^^^^^^^^^^^^^^^^^^^^
1325 }
1326 }
1327 // if we are spawning, we need time to rez the NPC, then start processing NPC Commands.
1328 else if(Spawning == STATE) {
1329 STATE = 0;
1330 TimerEvent(TIMER);
1331 }
1332 // We end aniamtions with a timer
1333 else if(Animate == STATE){
1334 NPCAnimate(STAND);
1335 TimerEvent(TIMER);
1336 }
1337
1338 else if(Walking == STATE) {
1339 if(--iWaitCounter) {
1340 if(llVecDist(osNpcGetPos(NPCKey()), newDest) > MAXDIST) {
1341 return;
1342 }
1343 }
1344
1345 // walk, fly, run, land
1346 if(walkstate == 1) {
1347 NPCAnimate(STAND);
1348 NPCWalkOption = OS_NPC_NO_FLY;
1349 } else if(walkstate == 2) {
1350 // nothing
1351 } else if(walkstate == 3) {
1352 NPCAnimate(STAND);
1353 NPCWalkOption = OS_NPC_NO_FLY;
1354 } else if(walkstate == 4) {
1355 llShout(FLIGHT,"landing");
1356 NPCAnimate(STAND);
1357 NPCWalkOption = OS_NPC_NO_FLY;
1358 }
1359 }
1360 // Wandering timer
1361 else if(Wander == STATE) {
1362 if(--iWaitCounter) // wait 60 seconds to get to a destination.
1363 if(llVecDist(osNpcGetPos(NPCKey()), vWanderPos) > MAXDIST) return;
1364
1365 // see if wander counter == 0, if so, stop walking, go to stand and process next line
1366 if(RAMwc == 0) {
1367 NPCAnimate(STAND);
1368 DEBUG("Wander ended, calling DoProcessNPCLine");
1369 STATE = 0;
1370 DoProcessNPCLine();
1371 return;
1372 }
1373 // one less time to wander around
1374 RAMwc--;
1375 NPCAnimate(STAND);
1376 TimerEvent(TIMER);
1377 DoWanderhold();
1378 return;
1379 }
1380 // Wandering requires us to re-wander when we reach a destination
1381 else if(WanderHold == STATE) {
1382 DoWander();
1383 TimerEvent(TIMER);
1384 }
1385 else if(DoProcess == STATE) {
1386 TimerEvent(QUICK);
1387 }
1388
1389 STATE = 0;
1390
1391 // We always process a NPC line at end of timer.
1392 DEBUG("Tick end, calling DoProcessNPCLine");
1393 DoProcessNPCLine();
1394 }
1395
1396 // sensors are used for sitting on prims
1397 sensor(integer num) {
1398 osNpcSit(NPCKey(), llDetectedKey(0), OS_NPC_SIT_NOW);
1399 DEBUG("Seated, calling DoProcessNPCLine");
1400 DoProcessNPCLine();
1401 }
1402 no_sensor(){
1403 DEBUG ("no seat located, calling DoProcessNPCLine");
1404 DoProcessNPCLine();
1405 }
1406
1407 }

All In One NPC Recorder and Player

test script for chatting commands to the NPC

Category: NPC
By : Ferd Frederix
Created: 2015-10-12 Edited: 2016-11-09
Worlds: OpenSim


This script by Ferd Frederix may be used in any manner, modified, and republished.  Unless specified otherwise, my scripts are always free and open source.  Objects made with these scripts may be sold with no restrictions.  All I ask is that you point others to this location should they ask you about it and to not sell this script, unless it is for $0 L. Please help improve my work by reporting bugs and improvements.

1
2 default
3 {
5 llSetText("Type commands in chat to control the NPC, such as '@say=Hello'",<1,1,1>,1.0);
6 llListen(0,"","","");
7 }
8
9 listen(integer channel, string name, key id, string message)
10 {
11 llMessageLinked(LINK_ROOT,0, message,"");
12 }
13 }

All In One NPC Recorder and Player

Sample collision script for NPC animator

Category: NPC
By : Ferd Frederix
Created: 2015-10-12 Edited: 2016-11-09
Worlds: OpenSim


This script by Ferd Frederix may be used in any manner, modified, and republished.  Unless specified otherwise, my scripts are always free and open source.  Objects made with these scripts may be sold with no restrictions.  All I ask is that you point others to this location should they ask you about it and to not sell this script, unless it is for $0 L. Please help improve my work by reporting bugs and improvements.

1 // rev 3: added on_rez() and STATUS_PHANTOM to state_entry - otherwise reset on Linux boxes did no collide any more.
2 default
3 {
5 {
6 llSetStatus(STATUS_PHANTOM,FALSE);
8 llSleep(0.1);
10 }
11
13 llMessageLinked(LINK_SET,0, "@animate=someanimation|10","");
15 }
16 timer()
17 {
18 llMessageLinked(LINK_SET,0, "@animate=Stand|1","");
20 }
21
22 changed(integer what)
23 {
24 if(what & CHANGED_REGION_START)
25 {
27 }
28 }
30 {
32 }
33 }

All In One NPC Recorder and Player

sample Stop processing script for NPC

Category: NPC
By : Ferd Frederix
Created: 2015-10-12 Edited: 2016-11-09
Worlds: OpenSim


This script by Ferd Frederix may be used in any manner, modified, and republished.  Unless specified otherwise, my scripts are always free and open source.  Objects made with these scripts may be sold with no restrictions.  All I ask is that you point others to this location should they ask you about it and to not sell this script, unless it is for $0 L. Please help improve my work by reporting bugs and improvements.

1
2 default
3 {
5 llSetText("Click to make the NPC stop processing commands",<1,1,1>,1.0);
6 }
7
8 touch_start(integer total_number)
9 {
10 llMessageLinked(LINK_ROOT,0, "@stop","");
11 }
12 }

All In One NPC Recorder and Player

Sample touch to go script for all in one NPC animator

Category: NPC
By : Ferd Frederix
Created: 2015-10-12 Edited: 2016-11-09
Worlds: OpenSim


This script by Ferd Frederix may be used in any manner, modified, and republished.  Unless specified otherwise, my scripts are always free and open source.  Objects made with these scripts may be sold with no restrictions.  All I ask is that you point others to this location should they ask you about it and to not sell this script, unless it is for $0 L. Please help improve my work by reporting bugs and improvements.

1
2 default
3 {
5 llSetText("Click to make the NPC continue processing commands",<1,1,1>,1.0);
6 }
7
8 touch_start(integer total_number)
9 {
10 llMessageLinked(LINK_ROOT,0, "@go","");
11 }
12 }

All In One NPC Recorder and Player

Sample touch to trigger a NPC script

Category: NPC
By : Ferd Frederix
Created: 2015-10-12 Edited: 2016-11-09
Worlds: OpenSim


This script by Ferd Frederix may be used in any manner, modified, and republished.  Unless specified otherwise, my scripts are always free and open source.  Objects made with these scripts may be sold with no restrictions.  All I ask is that you point others to this location should they ask you about it and to not sell this script, unless it is for $0 L. Please help improve my work by reporting bugs and improvements.

1
2 default
3 {
5 llSetText("Click to make the NPC say hello",<1,1,1>,1.0);
6 }
7
8 touch_start(integer total_number)
9 {
10 llMessageLinked(LINK_ROOT, 0, "@say=Hello there, " + llDetectedName(0), "");
11 }
12 }

All In One NPC Recorder and Player

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 : Ferd Frederix
Created: 2015-10-12 Edited: 2016-11-09
Worlds: OpenSim


This script by Ferd Frederix may be used in any manner, modified, and republished.  Unless specified otherwise, my scripts are always free and open source.  Objects made with these scripts may be sold with no restrictions.  All I ask is that you point others to this location should they ask you about it and to not sell this script, unless it is for $0 L. Please help improve my work by reporting bugs and improvements.

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

All In One NPC Recorder and Player

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 : Ferd Frederix
Created: 2015-10-12 Edited: 2016-11-09
Worlds: OpenSim


This script by Ferd Frederix may be used in any manner, modified, and republished.  Unless specified otherwise, my scripts are always free and open source.  Objects made with these scripts may be sold with no restrictions.  All I ask is that you point others to this location should they ask you about it and to not sell this script, unless it is for $0 L. Please help improve my work by reporting bugs and improvements.

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

All In One NPC Recorder and Player

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 : Ferd Frederix
Created: 2015-10-12 Edited: 2016-11-09
Worlds: OpenSim


This script by Ferd Frederix may be used in any manner, modified, and republished.  Unless specified otherwise, my scripts are always free and open source.  Objects made with these scripts may be sold with no restrictions.  All I ask is that you point others to this location should they ask you about it and to not sell this script, unless it is for $0 L. Please help improve my work by reporting bugs and improvements.

1 // This is Rev 4.1 09/20/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 //*******************************************************************//
34
35 // Instructions on how to use this are at http://www.outworldz.com/opensim/posts/NPC/
36 // This is an OpenSim-only script.
37 // Author: Ferd Frederix aka Fred Beckhusen - fred@mitsi.com
38
39 ////////////////////////////////////////////////////////////////////////////////////////////
40 // Original code was Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 //
41 ///////////////////////////////////////////////////////////////////////////////////////////
42 // Please see: http://www.gnu.org/licenses/gpl.html for legal details, //
43 // rights of fair usage, the disclaimer and warranty conditions. //
44 ///////////////////////////////////////////////////////////////////////////////////////////
45 // The original NPC controller was from http://was.fm/opensim:npc
46 // Extensive additions and bug fixes by Fred Beckhusen, aka Ferd Frederix
47 // llSensor had two params swapped
48 // @Wander would wander where it had rezzed, not where it was.
49 // There was no 'no_sensor' event in sit, so if a @sit failed, the NPC got stuck
50 // 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.
51 // New code:
52 // Merged with new Route recorder and notecard writer
53 // If the NPC failed to reach a destination it never moved on.
54 // Added WAIT global to tune this
55 // Exposed many tunable variables and ported the code
56 // Added floating point to times in notecard.
57
58 // Added @sound, @randsound, @whisper, @shout, and @cmd controls.
59 //
60 // notecards integers are not floats for better control
61 //
62 // Link Messages may be used to perform external control by injecting @commands into the stream of actions
63 // Example:
64 // To chat something, such as with a chat robot
65 // llMessageLinked(LINK_SET,0,"@npc_say=Hello","");
66
67 // This script assumes that NPCs and OSSl scripting is enabled in the OpenSim configuration.
68 // In order to enable them, the following changes must be made in the OpenSim.ini configuration file:
69 //
70 // ; Turn on OSSL
71 // AllowOSFunctions = true
72 // OSFunctionThreatLevel = Severe
73
74 //[NPC]
75 // ;# {Enabled} {} {Enable Non Player Character (NPC) facilities} {true false}
76 // Enabled = true
77 //
78 // and then the server has to be restarted.
79
80
81 // Commands: All commands begin with an @ sign. All other lines are ignored
82 // @commands may have optional parameters. The syntax is always:
83 // @cmd=parm1|parm2
84 // NaN in the table below meand Not a Number. This means there is no parameter
85
86 //Command First Parameter Second Parameter Description
87 //@spawn name location (vector) Rezzes an NPC with name at a location.
88 //@appearance NoteCardName NaN switch the NPC appearance to a new notecard
89 //@walk destination (vector) NaN Makes the NPC walk to destination.
90 //@fly destination (vector) NaN Makes the NPC fly to destination.
91 //@land destination (vector) NaN Makes the NPC land at destination.
92 //@say string NaN Makes the NPC speak a phrase.
93 //@whisper string NaN Makes the NPC whisper a phrase.
94 //@shout string NaN Makes the NPC shout a phrase.
95 //@pause seconds (float) NaN Makes the NPC wait for a multiple of seconds.
96 //@wander radius (float) cycles (integer) Makes the NPC wander in radius, for cycles seconds.
97 //@delete NaN NaN Removes the NPC. Requires a link message to continue
98 //@goto label (string) NaN Jump to the label label in the script.
99 //@animate animation (string) time (float) Makes the NPC trigger the animation animation for time seconds.
100 //@sound sound_name NaN plays a sound from inventory
101 //@randsound NaN NaN Plays a random sound from inventory
102 //@rotate degrees (float) NaN Rotate the NPC degrees around the Z axis.
103 //@sit primitive name NaN Sit on a primitive with a given name.
104 //@touch primitive name NaN Touch on a primitive with a given name.
105 //@stand NaN NaN If sitting on a primitive, stand up.
106 //@cmd channel (integer) string Says string on channel, for controlling external gadgets
107 //@stop NaN NaN Halts the NPC script indefinitely. Can be started with a link message
108 //@go NaN NaN Continues on next notecard line, for use in link messages
109 //@speed speed (float) NaN from 0 to N, where 1.0 ius a normal speed of an avatar. 0.2 is a turtle.
110 //@notecard notename (string) NaN load a new Path notecard
111 //@attach InventoryName attachmentPoint load an attachment from the inventory to the NPC onto point
112
113 // Constant attachmentPoint Comment
114 // ATTACH_CHEST 1 chest/sternum
115 // ATTACH_HEAD 2 head
116 // ATTACH_LSHOULDER 3 left shoulder
117 // ATTACH_RSHOULDER 4 right shoulder
118 // ATTACH_LHAND 5 left hand
119 // ATTACH_RHAND 6 right hand
120 // ATTACH_LFOOT 7 left foot
121 // ATTACH_RFOOT 8 right foot
122 // ATTACH_BACK 9 back
123 // ATTACH_PELVIS 10 pelvis
124 // ATTACH_MOUTH 11 mouth
125 // ATTACH_CHIN 12 chin
126 // ATTACH_LEAR 13 left ear
127 // ATTACH_REAR 14 right ear
128 // ATTACH_LEYE 15 left eye
129 // ATTACH_REYE 16 right eye
130 // ATTACH_NOSE 17 nose
131 // ATTACH_RUARM 18 right upper arm
132 // ATTACH_RLARM 19 right lower arm
133 // ATTACH_LUARM 20 left upper arm
134 // ATTACH_LLARM 21 left lower arm
135 // ATTACH_RHIP 22 right hip
136 // ATTACH_RULEG 23 right upper leg
137 // ATTACH_RLLEG 24 right lower leg
138 // ATTACH_LHIP 25 left hip
139 // ATTACH_LULEG 26 left upper leg
140 // ATTACH_LLLEG 27 left lower leg
141 // ATTACH_BELLY 28 belly/stomach/tummy
142 // ATTACH_LEFT_PEC 29 left pectoral
143 // ATTACH_RIGHT_PEC 30 right pectoral
144 // ATTACH_HUD_CENTER_2 31 HUD Center 2
145 // ATTACH_HUD_TOP_RIGHT 32 HUD Top Right
146 // ATTACH_HUD_TOP_CENTER 33 HUD Top
147 // ATTACH_HUD_TOP_LEFT 34 HUD Top Left
148 // ATTACH_HUD_CENTER_1 35 HUD Center
149 // ATTACH_HUD_BOTTOM_LEFT 36 HUD Bottom Left
150 // ATTACH_HUD_BOTTOM 37 HUD Bottom
151 // ATTACH_HUD_BOTTOM_RIGHT 38 HUD Bottom Right
152 // ATTACH_NECK 39 neck
153 // ATTACH_AVATAR_CENTER 40 avatar center/root
154
155
156
157 //////////////////////////////////////////////////////////
158 // DEBUG //
159 //////////////////////////////////////////////////////////
160 integer debug = TRUE; // set to TRUE or FALSE for debug chat on various actions
161 integer LSLEditor = TRUE; // set to to TRUE to working in LSLEditor, FALSE for in-world.
162 // you must also include the NPC commands found in the other script since LSLEditor does not support OpenSim
163 integer iTitleText = FALSE; // set to TRUE to see debug info in text above the controller
164
165 //////////////////////////////////////////////////////////
166 // TUNABLE CONFIGURATION //
167 //////////////////////////////////////////////////////////
168 integer allowListener = TRUE; // set to TRUE to anable a command listener. Usually, this is setto FALSE
169 integer link_Channel = 4223; // some random number you want to talk to this gadget on. Best if large and negative
170 float TIMER = 2; // faster = less jerky stopping. How often the system checks the distance traveled. Fastest you can go is 0.5 seconds
171 float QUICK = 1; // when we need to move to the next state, we use a QUICK timer
172 string Appearance = "!Appearance"; // The name of the recorded Appearance notecard
173 string Notecard = "!Path"; // The name of the recorded routes
174 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
175 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
176 integer WANDERRAND = TRUE; // set to TRUE and they will pause during wanders a random number of seconds
177 float WANDERTIME = 3.0; // how long they stand after each @wander,if WANDERRAND is FALSE. If WANDERRAND is TRUE, this is the max time
178 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.
179 float RANGE = 50; // 1 to N meters - anyone this close to the controller will start NPCS if Sensor button is clicked
180 float REZTIME = 2.0; // wait this long for NPC to rez in, then start the process
181 string STAND = "Stand"; // the name of the default Stand animation
182 string WALK = "Walk"; // the name of the default Walk animation
183 string FLY = "Fly"; // the name of the default Fly animation
184 string RUN = "Run"; // the name of the default Run animation
185 string LAND = "Land"; // the name of the default land animation ( for birds only)
186 float OffsetZ = 0.5; // appear 0.5 meter above ground, this is added to all destinations to keep them from sinking in.
187 float SPEEDMULT =0.5; // 1.0 = regular avatar speed. Smaller numbers slow down walks. Large numbers speed them up.
188 integer FLIGHT = 299; // For controlling wings. A channel that is shouted at when flight starts and ends. "flying" or "landing"
189
190 // DESCRIPTIONS FIELDS HAVE TO SURVIVE A RESET
191 // These vars are stored by saving them with KeyValueSet
192 // "pr" is a 0 if it is set for Owner Only, 1 for Group control
193 // "se" is "on" if Started
194 // "co" = "R" or "A" for relative or absolute addressing mode
195 // "key" = NPC key
196
197 // These Globals used to be stored in description. Moved to RAM in V1.6
198 float RAMPause; // @pause param
199 float RAMwd ; // @wander distance
200 integer RAMwc; // @wander count
201 float RAMrot; // @rotate
202 string RAMsit; // @sit primname
203 string RAMtouch; // @touch primname
204 string RAManimationName; // @animate animation (string) time (float)
205 float RAManimationTime;
206
207 // other globals section
208 integer iChannel; // a listen channel, randomly assigned
209 integer iHandle; // the handle to it
210
211 // NPC controls
212 vector newDest ; // tmp storage for the walks
213 integer iWaitCounter ; // wait for this number of seconds for the NPC to reach a desrtination
214 string sNPCName; // the name of the NPC that may be in world. So we can remove it.
215 integer bNPC_STOP = FALSE; // boolean to reuse a listener
216 integer Stopped = FALSE; // set to TRUE by link messages so we do not remember them
217 float fTimerVal ; // how long we wait when wandering (calculated)
218 float NPCEnabled; // true if the NPC is suppodes to be running
219
220 // 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.
221 // OS_NPC_NOT_OWNED will create an 'unowned' NPC that will respond to any script that has OSSL permissions to call osNpc* commands.
222 integer NPCOptions = OS_NPC_CREATOR_OWNED; // only yhe owner of this box can control this NPC.
223
224 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
225
226 integer NPCWalkOption; // Some notes for what happens to NPCWalkOption:
227 // 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.
228 // 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
229 //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.
230 // OS_NPC_RUNNING - if given, NPC avatar moves at running/fast flying speed, otherwise moves at walking/slow flying speed.
231
232 // menus
233 string mSensor="Sense is Off"; // Sensor or "No Sensor"
234
235 list lAtButtons = ["Menu","-", ">>", "@run", "@walk", "@fly", "@land", "@wander", "@sit", "@stand","@animate","@rotate"];
236 list lMenu2 = ["<<", "@comment", ">>>", "@stop", "@say", "@whisper","@shout","@sound","@randsound","@cmd", "@pause", "@delete"];
237 list lMenu3 = ["<<<","@notecard","@appearance", "@touch", "@speed", "@attach", "-","-", "-", "-", "-", "-" ];
238
239 string sCommand; // place to store a command for two-prompted ones
240 string sParam2; // place to store a prompt for two-prompted ones
241 string priPub = "Owner Only"; // Private or Group
242 key kUserKey; // the person who is controlling the avatar, not the Owner
243 // the command lists
244 list lCommands; // commands are stored here
245 list lNpcCommandList; // Storage for the NPC script.
246 string npcAction; // Storage for the next action. @cmd=0|hello, this becomes @cmd
247 string npcParams; // Storage for the param, @cmd=0|hello, this becomes 0|hello
248
249 // misc vars
250 string sNotecard; // commands are stored here temporarily for dumping
251 vector vWanderPos; // a place to wander to
252 string lastANIM ; // last animation run
253 // Sensor
254 integer avatarPresent; // Sensor sets this flag when people are within Range.
255
256 // Coordinate control
257 vector vInitialPos ; // Vector that will be filled by the script with the initial starting position in region coordinates.
258 vector vDestPos = ZERO_VECTOR; // Storage for destination position.
259 string relAbs = "Relative"; // absolute vs relative positioning
260
261
262 // STATES
263 integer MENU ; // processing a dialog box state, may be concurrent with STATE
264 integer STATE; // state storage
265 integer MakeNotecard = 1; // displaying a text box for NPC name
266 integer RecordPath = 2; // displaying a path notecard menu
267 integer NobodyHome = 3; // looking for an avatar
268 integer Spawning = 4; // spawning an avatar
269 integer Animate = 5; // animation timer needed
270 integer Walking = 6; // Hey! I am walking here!
271 integer Wander = 7; // Wandering around neeeds a timer, too
272 integer WanderHold = 8; // We reached a wander point
273 integer DoProcess = 9; // Set this to make it process a new command
274 integer Touch = 10; // Timer is busy sensing something to touch
275 integer Sit = 11; // Timer is busy sensing something to sit on
276 integer Paused = 12; // Timer is busy pausing
277
278 key gNpcKey = NULL_KEY; // global key storage for the one NPC, to save CPU cycles
279 list Stack ; // a command stack from link message input
280
281 integer SensorFunc = 0; // define which function shall be triggered inside the sensor function
282 // 0 means none, 1 sit, 2 touch
283 ///////////////////////////////////////////////////////////////////////////
284 // FUNCTIONS //
285 ///////////////////////////////////////////////////////////////////////////
286
287
288 TimerEvent(float timesent)
289 {
290 if(LSLEditor)
291 timesent *= 5; // slow thinggs doen when the LSLEDITOR is in use
292
293 DEBUG("Setting timer: " + (string) timesent);
294 llSetTimerEvent(timesent);
295 }
296
297 // for 4.1 parse a message from a Listen or a Link message
298 ParseMsg(string str) {
299 DEBUG("Command In:" + str);
300 if(str=="@go") {
301 SetStop(FALSE); // Let's run the notecard
302 DEBUG("@go running");
303 DoProcessNPCLine();
304 } else {
305 Stack += [str]; // take anything, the controller will filter away non @ stuff
306 if(STATE == 0) {
307 DEBUG("calling DoNPC");
308 DoProcessNPCLine();
309 } else{
310 DEBUG("Queued");
311 }
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 // Create a NPC
349 StateSpawn() {
350 DEBUG("state spawn");
351 STATE = Spawning;
352
353
354 NPCEnabled = TRUE; // in world
355 // see if there is already one out there.
356 if(NPCKey() != NULL_KEY) {
357 DEBUG("Already living");
358 return;
359 }
360
361
362 list name = llParseString2List(sNPCName, [" "], []);
363
364 vector vRezPos = vInitialPos;
365 if(relAbs == "Relative"){
366 vRezPos += llGetPos();
367 }
368
369 DEBUG("Rezzing the NPC:" + (string) vRezPos);
370 key aKey = osNpcCreate(llList2String(name, 0), llList2String(name, 1), vRezPos, Appearance, NPCOptions);
371
372 SaveKey(aKey); // save in desceription and global, too
373
374 osSetSpeed(aKey,SPEEDMULT); // 1.9 speed multiplier
375 TimerEvent(REZTIME);
376 NPCAnimate(STAND);
377 }
378
379 DoRotate() {
380 DEBUG("@rotate=" + (string) RAMrot);
381 osNpcSetRot(NPCKey(), llEuler2Rot(<0,0,RAMrot> * DEG_TO_RAD));
382 }
383
384 StateSit() {
385 DEBUG ("state sit - looking for " + RAMsit);
386 STATE=Sit;
387 SensorFunc = 1; //triggers osNpcSit
388 llSensor(RAMsit, "", PASSIVE|ACTIVE|SCRIPTED, 96, PI);
389 }
390
391 StateTouch() {
392 DEBUG ("state touch - looking for " + RAMtouch);
393 STATE = Touch;
394 SensorFunc = 2; //triggers osNpcTouch
395 llSensor(RAMtouch, "", PASSIVE|ACTIVE|SCRIPTED, 96, PI);
396 }
397
398 DoStand() {
399 DEBUG("state stand");
400 osNpcStand(NPCKey());
401 }
402
403
404 StateAnimate() {
405
406 DEBUG("state animate");
407 STATE = Animate;
408 NPCAnimate(RAManimationName);
409 if(RAManimationTime <=0 ) // V 3.4 tweak
410 RAManimationTime = 1;
411 TimerEvent(RAManimationTime);
412 }
413
414 StateWalk() {
415
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 // @pause=10 will do nothing for 10 seconds
477 DoPause() {
478 STATE =Paused;
479 if(RAMPause < 0.1)
480 RAMPause = 0.1;
481 DEBUG("@pause=" + (string)RAMPause);
482 TimerEvent(RAMPause);
483 }
484
485
486 // @stop makes the NPC stop moving in whatever state it is in. You have to linkmessage to get moving again
487 DoStop() {
488 DEBUG("NPC is Stopped");
489 STATE = 0; // accept commands
490 SetStop(TRUE); // Link controlled - we mnust have a @go to continue with notecards
491 TimerEvent(0);
492 Stack = []; // v3.8
493 }
494
495 // @delete removes the NPC forever. Next command starts it up again at the beginning
496 DoDelete() {
497 DEBUG("state delete");
498 STATE = 0; // accept commands
499 osNpcRemove(NPCKey());
500 SaveKey(NULL_KEY);
501
502 TimerEvent(0);
503 Stack = []; // v3.8
504 }
505
506 // change the appearance of the NPC
507 DoAppearance(string notecard) {
508 DEBUG("state appearance");
510 DEBUG("Load appearance " + notecard);
511 osNpcLoadAppearance(NPCKey(),notecard);
512 }
513 }
514
515 // Change the avatar speed
516 DoSpeed(string speed) {
517 float newspeed = (float) speed;
518 if(newspeed > 0.1 && newspeed < 5.0) {// sanity check
519 osSetSpeed(NPCKey(),newspeed);
520 }
521 }
522 DoNewNote (string card) {
523 DEBUG("Load Notecard " + card);
524 NPCReadNoteCard(card);
525 SetStop(FALSE);
526 }
527 DoAttach(string params) {
528
529 list Data = llParseString2List(params, ["|"], []);
530 string itemName = llList2String(Data, 0);
531 integer attachmentPoint = (integer) llList2String(Data, 1);
532 if(attachmentPoint > 0
533 && attachmentPoint < 40
535 )
536 {
537 osForceAttachToOtherAvatarFromInventory(NPCKey(),itemName,attachmentPoint);
538 }
539 }
540
541 // This loops over the notecard, processing each command
542 DoProcessNPCLine() {
543 DEBUG("ProcessNPCLine, stopped = " + (string) Stopped);
544 STATE = DoProcess;
545
546 // auto load a notecard
547 if(! llGetListLength(lNpcCommandList)) {
548 DEBUG("Read Notecard");
549 NPCReadNoteCard(Notecard);
550 SetStop(FALSE);
551 }
552
553 // look for link messages on the stack
554 string next = llList2String(Stack,0); // lets see if there is anithing from a link message
555 if(llStringLength(next))
556 {
557 Stack = llDeleteSubList(Stack,0,0);
558 ProcessCmd(next); //lets do this command instead.
559 return;
560 }
561
562 // @stop issued?
563 if(Stopped) {
564 TimerEvent(0);
565 DEBUG("Stopped, waiting for input");
566 STATE = 0;
567 return;
568 }
569
570 // No, we have an @go for liftoff
571 next = llList2String(lNpcCommandList, 0); // get the next command
572 DEBUG("Execute:" + next);
573 lNpcCommandList = llDeleteSubList(lNpcCommandList, 0, 0); // delete it
574
575 if(llGetListLength(lNpcCommandList) == 0) {
576 DEBUG("EOF");
577 }
578 ProcessCmd(next);
579 }
580
581
582
583 ProcessCmd(string cmd) {
584
585 DEBUG("ProcessCmd:" + cmd);
586
587 if(llGetSubString(cmd, 0, 0) != "@") {
588 DEBUG("ignoring");
589 TimerEvent(QUICK); // this is so we do not recurse the stack
590 STATE = 0;
591 return;
592 }
593
594 list data =