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 Hypergrid Story Three  

Hypergrid Story Three

Sample sequencer script for NPC animator. It sequeunces multiple NPCs in order thru a scenarios
each 'things' entry is the NPC number, a (float) time to take between sending commands ( 0 is not allowed, but a small number is() ///and a @command that is sent to the NPC.

Category: NPC
By : Ferd Frederix
Created: 2015-11-24 Edited: 2015-11-23
Worlds: Second Life

the Zip file

Download all files for Hypergrid Story Three
Contents are in zip format, with .LSL (text) source code and LSLEdit (text + Solution) formats.
Get file # 1. License and readme.txt
Get file # 2. [1237166 bytes] End Game.png
Get file # 3. [1237166 bytes] End Game Photo.png
Get file # 4. Game prim script 5.5.lsl
Get file # 5. Modified Hypergate to level four.lsl
Get file # 6. readme.txt
Get file # 7. NotASensor.lsl
Get file # 8. Sensor.lsl
Get file # 9. Collider.lsl
Get file # 10. NPC Controller - All In One Rev 4.0.lsl
Get file # 11. FallingWalkway.lsl
Get file # 12. HG water.lsl
Get file # 13. Sequence.lsl
Get file # 14. Advance to tree .lsl
Get file # 15. Advance to Edge.lsl
Get file # 16. Fall of edge of cliff.lsl
Get file # 17. NPC Rev 3.9.lsl
Get file # 18. Collision.lsl
Get file # 19. Collider.lsl
Get file # 20. NPC Rev 4.lsl
Get file # 21. [1551945 bytes] RockFall Image.png
Get file # 22. Notecard.txt
Get file # 23. rock controller.lsl
Get file # 24. Tree Collider.lsl
Get file # 25. NPC Controller - All In One Rev 4.6.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 // Notes: Link messages arte as follows
2 // 1 = Racoon
3 // 2 = Namaka
4 // 3 = Dylan
5
6 // Rev: 2 fixes the Linux bug for collisions.
7
8 integer busy = FALSE;
9
10 integer debug = FALSE;
11 integer SENSE = TRUE;
12 float RATE = 5;
13 integer COLLIDE = TRUE;
14 float RANGE = 2;
15
16
17 list things ;
18 RezIn()
19 {
20 things = [1,5,"@stop"];
21 things += [2,5,"@stop"];
22 things += [3,10,"@stop"];
23 Speak();
24 }
25
26 DoIt()
27 {
28 things = [-1,1,"@pause=1"];
29
30 things += [1,1,"@animate=avatar_jumpforjoy|1"];
31 things += [1,1,"@say=Hello! What happened to you two?"];
32 things += [1,1,"@run=<123.70119, 124.20567, 37.52779>"];
33 things += [1,1,"@walk=<124.75295, 125.30030, 37.52779>"];
34
35
36
37 // rezpoint
38 things += [2,1,"@walk=<125.02877, 121.68277, 37.39204>"];
39 things += [2,1,"@animate=SwayInBreeze|2"];
40 things += [2,2,"@animate=avatar_type|2"];
41 things += [2,2,"@say=We are cyber beings that were transformed into these appearances."];
42
43 things += [3,1,"@walk=<122.39304, 121.43833, 37.50384>"];
44
45 things += [3,1,"@animate=SwayInBreeze|2"];
46
47 things += [3,2,"@animate=avatar_type|3"];
48 things += [3,1,"@say=Fire and wood do not mix. I cannot be with you, my love, while in this form."];
49
50 things += [2,2,"@animate=avatar_type|2"];
51 things += [2,2,"@say=We must seek help to transform ourselves. I cannot live without you"];
52
53 things += [2,2,"@pause=2"];
54
55
56 things += [2,3,"@animate=avatar_type|3"];
57 things += [2,1,"@say=Now we are on a journey to find our home, and find ourselves again"];
58 things += [2,2,"@pause=2"];
59
60 things += [1,3,"@animate=avatar_type|3"];
61 things += [1,1,"@say=I see your friends have been changed by magic."];
62 things += [1,2,"@pause=2"];
63
64
65 things += [1,3,"@animate=avatar_type|3"];
66 things += [1,1,"@stand"];
67 things += [1,2,"@say=I can show a place that has potions to cure that."];
68
69 things += [1,2,"@animate=avatar_type|2"];
70 things += [1,2,"@say=But you need be careful here, the swamp is a dangerous place "];
71 things += [1,1,"@stand"];
72
73 things += [1,2,"@animate=avatar_type|2"];
74 things += [1,1,"@say=I can show you the way. Follow me"];
75 things += [1,1,"@walk=<119.90541, 124.44883, 37.51564>"];
76 things += [1,1,"@walk=<121.84517, 124.44883, 37.51564>"];
77
78 things += [3,1,"@animate=avatar_type|3"];
79 things += [3,1,"@whisper=I do not trust this racoon. He has a mask and could mislead us. "];
80
81
82
83 things += [2,1,"@animate=avatar_type|3"];
84 things += [2,1,"@say=We must stay here, my love, where we do not burn the woods down."];
85
86
87 things += [2,1,"@animate=avatar_type|2"];
88 things += [2,1,"@say=Be careful, friend!"];
89
90 things += [3,1,"@animate=avatar_type|3"];
91 things += [3,1,"@say=Thank you for helping us"];
92
93 things += [1,1,"@shout=Follow me!"];
94
95
96 Speak();
97 }
98
99 Speak() {
100
101 integer prim = llList2Integer(things,0);
102 float time = llList2Float(things,1);
103 string msg = llList2String(things,2);
104 if(debug) llOwnerSay("Prim:" + (string) prim + " time:" + (string) time + " Msg:" + msg);
105 if(prim) {
106 things = llDeleteSubList(things,0,2);
107 llMessageLinked(prim,0, msg,"");
108 if(time > 0) {
109 llSetTimerEvent(time);
110 } else {
111 llOwnerSay("Whooops, time = 0!");
112 if(debug) llOwnerSay("Prim:" + (string) prim + " time:" + (string) time + " Msg:" + msg);
114 Reset();
115 busy = FALSE;
116 }
117 } else {
118 if(debug) llOwnerSay("Done");
119 busy = FALSE;
120 Reset();
122 if(SENSE)
123 llSensorRepeat("","",AGENT,RANGE,PI,RATE);
124 }
125 }
126
127 Reset()
128 {
129 llSetStatus(STATUS_PHANTOM, FALSE);
131 llSleep(0.1);
133 }
134
135 default
136 {
138 {
139 llSetText("",<1,1,1>,1.0);
140 Reset();
142 if(SENSE)
143 llSensorRepeat("","",AGENT,RANGE,PI,RATE);
144 }
145
146 sensor(integer n) {
147 if(debug) llOwnerSay("Bumped");
148 if(! osIsNpc(llDetectedKey(0))) {
149 if(debug) llOwnerSay("Sensed avatar");
151 RezIn();
152 }
153 }
154
155 timer()
156 {
157 Speak();
158 }
159
161 if(debug) llOwnerSay("Collided with " + llKey2Name(llDetectedKey(0)));
162 if(busy)
163 return;
165 {
166 DoIt();
167 }
168
169 }
170
172 {
174 }
175
176 changed(integer what)
177 {
178 if(what & CHANGED_REGION_START)
179 {
181 }
182 }
183 }

Hypergrid Story Three

Sample collision script for NPC animator

Category: NPC
By : Ferd Frederix
Created: 2015-11-24 Edited: 2015-11-23
Worlds: Second Life


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 integer debug = FALSE;
3
4 DoIt()
5 {
6 llMessageLinked(1,5, "@walk=<121.16252, 121.84969, 37.72606>","");
7 llMessageLinked(1,1, "@say=The route is easy and not dangerous. Just follow me. ","");
8 llMessageLinked(1,1, "@walk=<103, 103, 39>","");
9 }
10
11 Reset()
12 {
14 llSetStatus(STATUS_PHANTOM, FALSE);
15 llSleep(0.1);
17 }
18
19 default
20 {
22 {
23 llSetText("",<1,1,1>,1.0);
24 llSetTimerEvent(3600);
25 Reset();
26 }
27
29
30 if(debug) llOwnerSay("Collided with " + llKey2Name(llDetectedKey(0)));
32 {
33 if(debug) llOwnerSay("Collided with " + llKey2Name(llDetectedKey(0)));
34 DoIt();
35 }
36 }
37
38 timer()
39 {
40 Reset();
41 llSetTimerEvent(3600);
42 }
43
45 {
47 }
48
49 changed(integer what)
50 {
51 if(what & CHANGED_REGION_START)
52 {
54 }
55 }
56 }

Hypergrid Story Three

Sample collision script for NPC animator. Moves the raccoon to the edge of the cliff

Category: NPC
By : Ferd Frederix
Created: 2015-11-24 Edited: 2015-11-23
Worlds: Second Life


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 // Rev: 2 fixes the Linux bug for collisions.
3
4
5 integer debug = FALSE;
6 Reset()
7 {
8 llSetStatus(STATUS_PHANTOM, FALSE);
10 llSleep(0.1);
12 }
13
14
15 DoIt()
16 {
17 if(debug) llOwnerSay("Racoon halfway");
18 llMessageLinked(1,0, "@say=Follow me. The bear will not hurt me.","");
19
20 llMessageLinked(1,0, "@walk=<88.10030, 74.18887, 38.53304>","");
21
22 }
23
24
25 default
26 {
28 {
29 llSetText("",<1,1,1>,1.0);
30 Reset();
31 llSetTimerEvent(3600);
32
33 }
34
36
38 {
39 if(debug) llOwnerSay("Collided with " + llKey2Name(llDetectedKey(0)));
40
41 DoIt();
42
43
44 }
45
46 }
48 {
50 }
52 {
53 DoIt();
54 }
55 timer()
56 {
57 Reset();
58 llSetTimerEvent(3600);
59 }
60
61 changed(integer what)
62 {
63 if(what & CHANGED_REGION_START)
64 {
66 }
67 }
68 }

Hypergrid Story Three

Sample collision script for NPC animator./ Makes the racoon run off the cliff edge

Category: NPC
By : Ferd Frederix
Created: 2015-11-24 Edited: 2015-11-23
Worlds: Second Life


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 // Rev: 2 fixes the Linux bug for collisions.
3 integer debug = FALSE;
4
5 DoIt()
6 {
7 // llOwnerSay("Racoon edge of cliff");
8 llMessageLinked(1,0, "@say=Follow me down. I will wait for you there. ","");
9 llMessageLinked(1,0, "@walk=<82.71397, 39.95649, 21.40128>","");
10 llMessageLinked(1,0, "@walk=<81.58669, 46.91171, 21.47848>","");
11 llMessageLinked(1,0, "@delete","");
12
13 }
14
15 Reset()
16 {
17 llSetStatus(STATUS_PHANTOM, FALSE);
19 llSleep(0.1);
21 }
22
23
24 default
25 {
27 {
28 llSetText("",<1,1,1>,1.0);
29 Reset();
30 llSetTimerEvent(3600);
31 }
32
34 {
36 }
37
39
41 {
42 if(debug) llOwnerSay("Collided with " + llKey2Name(llDetectedKey(0)));
43
44 DoIt();
45 }
46 }
47 timer()
48 {
49 Reset();
50 llSetTimerEvent(3600);
51 }
52
53 changed(integer what)
54 {
55 if(what & CHANGED_REGION_START)
56 {
58 }
59 }
60 }

Hypergrid Story Three

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-11-24 Edited: 2015-11-23
Worlds: Second Life


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.9 08/23/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 //*******************************************************************//
32
33 // Instructions on how to use this is at http://www.outworldz.com/opensim/posts/NPC/
34 // This is an OpenSim-only script.
35 // Author: Ferd Frederix aka Fred Beckhusen - fred@mitsi.com
36
37 ////////////////////////////////////////////////////////////////////////////////////////////
38 // Original code was Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 //
39 ///////////////////////////////////////////////////////////////////////////////////////////
40 // Please see: http://www.gnu.org/licenses/gpl.html for legal details, //
41 // rights of fair usage, the disclaimer and warranty conditions. //
42 ///////////////////////////////////////////////////////////////////////////////////////////
43 // The original NPC controller was from http://was.fm/opensim:npc
44 // Extensive additions and bug fixes by Fred Beckhusem, aka Ferd Frederix, fred@mitsi.com
45 // llSensor had two params swapped
46 // @Wander would wander where it had rezzed, not where it was.
47 // There was no 'no_sensor' event in sit, so if a @sit failed, the NPC got stuck
48 // 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.
49 // New code:
50 // Merged with new Route recorder and notecard writer
51 // If the NPC failed to reach a destination it never moved on. Added WAIT global to tune this
52 // Exposed many tunable variables and ported the code to LSLEditor.
53 // Added floating point to times in notecard.
54
55 // Added @sound, @randsound, @whisper, @shout, and @cmd controls.
56 //
57 // notecards integers are not floats for better control
58 //
59 // Link Messages may be used to perform external control by injecting @commands into the stream of actions
60 // Example:
61 // To chat something, such as with a chat robot
62 // llMessageLinked(LINK_SET,0,"@npc_say=Hello","");
63
64 // This script assumes that NPCs and OSSl scripting is enabled in the OpenSim configuration.
65 // In order to enable them, the following changes must be made in the OpenSim.ini configuration file:
66 //
67 // ; Turn on OSSL
68 // AllowOSFunctions = true
69 // OSFunctionThreatLevel = Severe
70
71 //[NPC]
72 // ;# {Enabled} {} {Enable Non Player Character (NPC) facilities} {true false}
73 // Enabled = true
74 //
75 // and then the server has to be restarted.
76
77
78 // Commands: All commands begin with an @ sign. All other lines are ignored
79 // @commands may have optional parameters. The syntax is always:
80 // @cmd=parm1|parm2
81 // NaN in the table below meand Not a Number. This means there is no parameter
82
83 //Command First Parameter Second Parameter Description
84 //@spawn name location (vector) Rezzes an NPC with name at a location.
85 //@appearance NoteCardName NaN switch the NPC appearance to a new notecard
86 //@walk destination (vector) NaN Makes the NPC walk to destination.
87 //@fly destination (vector) NaN Makes the NPC fly to destination.
88 //@land destination (vector) NaN Makes the NPC land at destination.
89 //@say string NaN Makes the NPC speak a phrase.
90 //@whisper string NaN Makes the NPC whisper a phrase.
91 //@shout string NaN Makes the NPC shout a phrase.
92 //@pause seconds (float) NaN Makes the NPC wait for a multiple of seconds.
93 //@wander radius (float) cycles (integer) Makes the NPC wander in radius, for cycles seconds.
94 //@delete NaN NaN Removes the NPC. Requires a link message to continue
95 //@goto label (string) NaN Jump to the label label in the script.
96 //@animate animation (string) time (float) Makes the NPC trigger the animation animation for time seconds.
97 //@sound sound_name NaN plays a sound from inventory
98 //@randsound NaN NaN Plays a random sound from inventory
99 //@rotate degrees (float) NaN Rotate the NPC degrees around the Z axis.
100 //@sit primitive name NaN Sit on a primitive with a given name.
101 //@touch primitive name NaN Touch on a primitive with a given name.
102 //@stand NaN NaN If sitting on a primitive, stand up.
103 //@cmd channel (integer) string Says string on channel, for controlling external gadgets
104 //@stop NaN NaN Halts the NPC script indefinitely. Can be started with a link message
105 //@go NaN NaN Continues on next notecard line, for use in link messages
106 //@speed speed (float) NaN from 0 to N, where 1.0 ius a normal speed of an avatar. 0.2 is a turtle.
107 //@notecard notename (string) NaN load a new Path notecard
108 //@attach InventoryName attachmentPoint load an attachment from the inventory to the NPC onto point
109
110 // Constant attachmentPoint Comment
111 // ATTACH_CHEST 1 chest/sternum
112 // ATTACH_HEAD 2 head
113 // ATTACH_LSHOULDER 3 left shoulder
114 // ATTACH_RSHOULDER 4 right shoulder
115 // ATTACH_LHAND 5 left hand
116 // ATTACH_RHAND 6 right hand
117 // ATTACH_LFOOT 7 left foot
118 // ATTACH_RFOOT 8 right foot
119 // ATTACH_BACK 9 back
120 // ATTACH_PELVIS 10 pelvis
121 // ATTACH_MOUTH 11 mouth
122 // ATTACH_CHIN 12 chin
123 // ATTACH_LEAR 13 left ear
124 // ATTACH_REAR 14 right ear
125 // ATTACH_LEYE 15 left eye
126 // ATTACH_REYE 16 right eye
127 // ATTACH_NOSE 17 nose
128 // ATTACH_RUARM 18 right upper arm
129 // ATTACH_RLARM 19 right lower arm
130 // ATTACH_LUARM 20 left upper arm
131 // ATTACH_LLARM 21 left lower arm
132 // ATTACH_RHIP 22 right hip
133 // ATTACH_RULEG 23 right upper leg
134 // ATTACH_RLLEG 24 right lower leg
135 // ATTACH_LHIP 25 left hip
136 // ATTACH_LULEG 26 left upper leg
137 // ATTACH_LLLEG 27 left lower leg
138 // ATTACH_BELLY 28 belly/stomach/tummy
139 // ATTACH_LEFT_PEC 29 left pectoral
140 // ATTACH_RIGHT_PEC 30 right pectoral
141 // ATTACH_HUD_CENTER_2 31 HUD Center 2
142 // ATTACH_HUD_TOP_RIGHT 32 HUD Top Right
143 // ATTACH_HUD_TOP_CENTER33 HUD Top
144 // ATTACH_HUD_TOP_LEFT 34 HUD Top Left
145 // ATTACH_HUD_CENTER_1 35 HUD Center
146 // ATTACH_HUD_BOTTOM_LEFT 36 HUD Bottom Left
147 // ATTACH_HUD_BOTTOM 37 HUD Bottom
148 // ATTACH_HUD_BOTTOM_RIGHT 38 HUD Bottom Right
149 // ATTACH_NECK 39 neck
150 // ATTACH_AVATAR_CENTER 40 avatar center/root
151
152
153
154 //////////////////////////////////////////////////////////
155 // DEBUG //
156 //////////////////////////////////////////////////////////
157 integer debug = FALSE; // set to TRUE or FALSE for debug chat on various actions
158 integer Editor = FALSE; // set to to TRUE to working in LSLEditor, FALSE for in-world.
159 // you must also include the NPC commands found in the other script since LSLEditor does not support OpenSim
160 integer iTitleText = FALSE; // set to TRUE to see debug info in text above the controller
161
162 //////////////////////////////////////////////////////////
163 // TUNABLE CONFIGURATION //
164 //////////////////////////////////////////////////////////
165 float TIMER = 2; // how often the system checks the distance traveled. Fastest you can go is 0.5 seconds
166 float QUICK = 1; // when we need to move to the next state, we use a QUICK timer
167 string Appearance = "!Appearance"; // The name of the recorded Appearance notecard
168 string Notecard = "!Path"; // The name of the recorded routes
169 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
170 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
171 integer WANDERRAND = TRUE; // set to TRUE and they will pause during wanders a random number of seconds
172 float WANDERTIME = 3.0; // how long they stand after each @wander,if WANDERRAND is FALSE. If WANDERRAND is TRUE, this is the max time
173 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.
174 float RANGE = 50; // 1 to N meters - anyone this close to the controller will start NPCS if Sensor button is clicked
175 float REZTIME = 2.0; // wait this long for NPC to rez in, then start the process
176 string STAND = "Stand"; // the name of the default Stand animation
177 string WALK = "Walk"; // the name of the default Walk animation
178 string FLY = "Fly"; // the name of the default Fly animation
179 string RUN = "Run"; // the name of the default Run animation
180 string LAND = "Land"; // the name of the default land animation ( for birds only)
181 float OffsetZ = 0.5; // appear 0.5 meter above ground, this is added to all destinations to keep them from sinking in.
182 float SPEEDMULT =0.5; // 1.0 = regular avatar speed. Smaller numbers slow down walks. Large numbers speed them up.
183 integer FLIGHT = 299; // For controlling wings. A channel that is shouted at when flight starts and ends. "flying" or "landing"
184
185 // DESCRIPTIONS FIELDS HAVE TO SURVIVE A RESET
186 // These vars are stored by saving them with KeyValueSet
187 // "pr" is a 0 if it is set for Owner Only, 1 for Group control
188 // "se" is "on" if Started
189 // "co" = "R" or "A" for relative or absolute addressing mode
190 // "key" = NPC key
191
192 // These Globals used to be stored in description. Moved to RAM in V1.6
193 float RAMPause; // @pause param
194 float RAMwd ; // @wander distance
195 integer RAMwc; // @wander count
196 float RAMrot; // @rotate
197 string RAMsit; // @sit primname
198 string RAMtouch; // @touch primname
199 string RAManimationName; // @animate animation (string) time (float)
200 float RAManimationTime;
201
202 // other globals section
203 integer iChannel; // a listen channel, randomly assigned
204 integer iHandle; // the handle to it
205
206 // NPC controls
207 vector newDest ; // tmp storage for the walks
208 integer iWaitCounter ; // wait for this number of seconds for the NPC to reach a desrtination
209 string sNPCName; // the name of the NPC that may be in world. So we can remove it.
210 integer bNPC_STOP = FALSE; // boolean to reuse a listener
211 integer Stopped = FALSE; // set to TRUE by link messages so we do not remember them
212 float fTimerVal ; // how long we wait when wandering (calculated)
213 float NPCEnabled; // true if the NPC is suppodes to be running
214
215 // 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.
216 // OS_NPC_NOT_OWNED will create an 'unowned' NPC that will respond to any script that has OSSL permissions to call osNpc* commands.
217 integer NPCOptions = OS_NPC_CREATOR_OWNED; // only yhe owner of this box can control this NPC.
218
219 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
220
221 integer NPCWalkOption; // Some notes for what happens to NPCWalkOption:
222 // 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.
223 // 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
224 //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.
225 // OS_NPC_RUNNING - if given, NPC avatar moves at running/fast flying speed, otherwise moves at walking/slow flying speed.
226
227 // menus
228 string mSensor="Sense is Off"; // Sensor or "No Sensor"
229
230 list lAtButtons = ["Menu","-", ">>", "@run", "@walk", "@fly", "@land", "@wander", "@sit", "@stand","@animate","@rotate"];
231 list lMenu2 = ["<<", "@comment", ">>>", "@stop", "@say", "@whisper","@shout","@sound","@randsound","@cmd", "@pause", "@delete"];
232 list lMenu3 = ["<<<","@notecard","@appearance", "@touch", "@speed", "@attach", "-","-", "-", "-", "-", "-" ];
233
234 string sCommand; // place to store a command for two-prompted ones
235 string sParam2; // place to store a prompt for two-prompted ones
236 string priPub = "Owner Only"; // Private or Group
237 key kUserKey; // the person who is controlling the avatar, not the Owner
238 // the command lists
239 list lCommands; // commands are stored here
240 list lNPCScript; // Storage for the NPC script.
241 string npcAction; // Storage for the next action. @cmd=0|hello, this becomes @cmd
242 string npcParams; // Storage for the param, @cmd=0|hello, this becomes 0|hello
243
244 // misc vars
245 string sNotecard; // commands are stored here temporarily for dumping
246 vector vWanderPos; // a place to wander to
247 string lastANIM ; // last animation run
248 // Sensor
249 integer avatarPresent; // Sensor sets this flag when people are within Range.
250
251 // Coordinate control
252 vector vInitialPos ; // Vector that will be filled by the script with the initial starting position in region coordinates.
253 vector vDestPos = ZERO_VECTOR; // Storage for destination position.
254 string relAbs = "Relative"; // absolute vs relative positioning
255 vector lastKnownPos; // last known NPC position when we deleted it
256
257 // STATES
258 integer MENU ; // processing a dialog box state, may be concurrent with STATE
259 integer STATE; // state storage
260 integer MakeNotecard = 1; // displaying a text box for NPC name
261 integer RecordPath = 2; // displaying a path notecard menu
262 integer NobodyHome = 3; // looking for an avatar
263 integer Spawning = 4; // spawning an avatar
264 integer Animate = 5; // animation timer needed
265 integer Walking = 6; // Hey! I am walking here!
266 integer Wander = 7; // Wandering around neeeds a timer, too
267 integer WanderHold = 8; // We reached a wander point
268 integer DoProcess = 9; // Set this to make it process a new command
269 integer Touch = 10; // Timer is busy sensing something to touch
270 integer Sit = 11; // Timer is busy sensing something to sit on
271 integer Paused = 12; // Timer is busy pausing
272
273 key gNpcKey = NULL_KEY; // global key storage for the one NPC, to save CPU cycles
274 list Stack ; // a command stack from link message input
275
276 integer SensorFunc = 0; // define which function shall be triggered inside the sensor function
277 // 0 means none, 1 sit, 2 touch
278 ///////////////////////////////////////////////////////////////////////////
279 // FUNCTIONS //
280 ///////////////////////////////////////////////////////////////////////////
281
282
283 SetStop(integer what)
284 {
285 DEBUG("Stopped set to " + (string ) what);
286 Stopped = what;
287 }
288 // Do* functions are much like states from the old V2 scripts.
289
290 // Save a Path notecard
291 DoSave()
292 {
293 STATE = MakeNotecard;
294 makeText("Stand where you want the NPC to appear, and enter the NPC Name");
295 }
296
297 // This function is used to record the path for the NPC
298 // Each command can take 0, 1, or 2 params
299 DoMenuForCommands() {
300 makeMenu(lAtButtons);
301 }
302
303
304 // No one is here when sensors were on, so we kill off the NPC
305 DoNobodyHome()
306 {
307 DEBUG("Nobody Home");
308 STATE = NobodyHome;
309 if(NPCKey() != NULL_KEY) {
310 osNpcRemove(NPCKey());
311 SaveKey(NULL_KEY);
312 }
313 TimerEvent(5); // keep ticking to sense avatars
314 }
315
316 // Create a NPC
317 StateSpawn() {
318 DEBUG("state spawn");
319 STATE = Spawning;
320
321
322 NPCEnabled = TRUE; // in world
323 // see if there is already one out there.
324 if(NPCKey() != NULL_KEY) {
325 DEBUG("Already living");
326 return;
327 }
328
329
330 list name = llParseString2List(sNPCName, [" "], []);
331
332 if(relAbs == "Relative"){
333 vInitialPos += llGetPos();
334 }
335
336 DEBUG("Rezzing the NPC:" + (string) vInitialPos);
337 key aKey = osNpcCreate(llList2String(name, 0), llList2String(name, 1), vInitialPos, Appearance, NPCOptions);
338
339 SaveKey(aKey ); // save in desceription and global, too
340
341 osSetSpeed(aKey,SPEEDMULT); // 1.9 speed multiplier
342 TimerEvent(REZTIME);
343 NPCAnimate(STAND);
344 }
345
346 DoRotate() {
347 DEBUG("@rotate=" + (string) RAMrot);
348 osNpcSetRot(NPCKey(), llEuler2Rot(<0,0,RAMrot> * DEG_TO_RAD));
349 }
350
351 StateSit() {
352 DEBUG ("state sit - looking for " + RAMsit);
353 STATE=Sit;
354 SensorFunc = 1; //triggers osNpcSit
355 llSensor(RAMsit, "", PASSIVE|ACTIVE|SCRIPTED, 96, PI);
356 }
357
358 StateTouch() {
359 DEBUG ("state touch - looking for " + RAMtouch);
360 STATE = Touch;
361 SensorFunc = 2; //triggers osNpcTouch
362 llSensor(RAMtouch, "", PASSIVE|ACTIVE|SCRIPTED, 96, PI);
363 }
364
365 DoStand() {
366 DEBUG("state stand");
367 osNpcStand(NPCKey());
368 }
369
370
371 StateAnimate() {
372
373 DEBUG("state animate");
374 STATE = Animate;
375 NPCAnimate(RAManimationName);
376 if(RAManimationTime <=0 ) // V 3.4 tweak
377 RAManimationTime = 1;
378 TimerEvent(RAManimationTime);
379 }
380
381 StateWalk() {
382
383 DEBUG("NPCWalkOption = " + (string) NPCWalkOption);
384 STATE = Walking;
385
386 // walk, fly, run, land
387 if(walkstate == 1) {
388 NPCAnimate(WALK);
389 } else if(walkstate == 2) {
390 llShout(FLIGHT,"flying");
391 NPCAnimate(FLY);
392 } else if(walkstate == 3) {
393 NPCAnimate(RUN);
394 } else if(walkstate == 4) {
395 NPCAnimate(LAND);
396 }
397 newDest = vDestPos ;
398 newDest.z += OffsetZ;
399
400 // notecard is stored as offsets from this box with relative addressing. Convert to absolute
401 if(relAbs == "Relative"){
402 newDest += llGetPos();
403 }
404
405 DEBUG("Moveto:" + (string) newDest);
406 osNpcMoveToTarget(NPCKey(), newDest, NPCWalkOption);
407 iWaitCounter = WAIT; // wait 60 seconds to get to a destination.
408 TimerEvent(TIMER);
409 }
410
411
412 StateWander(){
413 DEBUG("state wander");
414 STATE = Wander;
415
416 vector point = CirclePoint(RAMwd);
417 DEBUG("CirclePoint:" + (string) point);
418 vWanderPos = vDestPos + point;
419 DEBUG("vWanderPos:" + (string) vWanderPos);
420
421 fTimerVal = WANDERTIME; // default time to pause after each wander
422 if(WANDERRAND)
423 fTimerVal = llFrand(WANDERTIME) + 1; // override, they want random times
424
425 NPCAnimate(WALK);
426
427 DEBUG("Wander to:" + (string) vWanderPos);
428
429 osNpcMoveToTarget(NPCKey(), vWanderPos, NPCWalkOption);
430 iWaitCounter = WAIT; // wait 60 seconds to get to a destination.
431 TimerEvent(TIMER);
432 }
433
434 StateWanderhold() {
435
436 DEBUG("Wander Hold");
437 STATE = WanderHold;
438
439 // now that we have reached a wander spot, slow the timer down to the desired value
440 TimerEvent(fTimerVal);
441 }
442
443 // @pause=10 will do nothing for 10 seconds
444 DoPause() {
445 STATE =Paused;
446 if(RAMPause < 0.1)
447 RAMPause = 0.1;
448 DEBUG("@pause=" + (string)RAMPause);
449 TimerEvent(RAMPause);
450 }
451
452
453 // @stop makes the NPC stop moving in whatever state it is in. You have to linkmessage to get moving again
454 DoStop() {
455 DEBUG("NPC is Stopped");
456 STATE = 0; // accept commands
457 SetStop(TRUE); // Link controlled - we mnust have a @go to continue with notecards
458 TimerEvent(0);
459 Stack = []; // v3.8
460 }
461
462 // @delete removes the NPC forever. Next command starts it up again at the beginning
463 DoDelete() {
464 DEBUG("state delete");
465 STATE = 0; // accept commands
466 osNpcRemove(NPCKey());
467 SaveKey(NULL_KEY);
468
469 TimerEvent(0);
470 Stack = []; // v3.8
471 }
472
473 // change the appearance of the NPC
474 DoAppearance(string notecard) {
475 DEBUG("state appearance");
477 DEBUG("Load appearance " + notecard);
478 osNpcLoadAppearance(NPCKey(),notecard);
479 }
480 }
481
482 // Change the avatar speed
483 DoSpeed(string speed) {
484 float newspeed = (float) speed;
485 if(newspeed > 0.1 && newspeed < 5.0) {// sanity check
486 osSetSpeed(NPCKey(),newspeed);
487 }
488 }
489 DoNewNote (string card) {
490 DEBUG("Load Notecard " + card);
491 NPCReadNoteCard(card);
492 SetStop(FALSE);
493 }
494 DoAttach(string params) {
495
496 list Data = llParseString2List(params, ["|"], []);
497 string itemName = llList2String(Data, 0);
498 integer attachmentPoint = (integer) llList2String(Data, 1);
499 if(attachmentPoint > 0
500 && attachmentPoint < 40
502 )
503 {
504 osForceAttachToOtherAvatarFromInventory(NPCKey(),itemName,attachmentPoint);
505 }
506 }
507
508 // This loops over the notecard, processing each command
509 DoProcessNPCLine() {
510 DEBUG("ProcessNPCLine, stopped = " + (string) Stopped);
511 STATE = DoProcess;
512
513 // auto load a notecard
514 if(! llGetListLength(lNPCScript)) {
515 DEBUG("Read Notecard");
516 NPCReadNoteCard(Notecard);
517 }
518
519 // look for link messages on the stack
520 string next = llList2String(Stack,0); // lets see if there is anithing from a link message
521 if(llStringLength(next))
522 {
523 Stack = llDeleteSubList(Stack,0,0);
524 ProcessCmd(next); //lets do this command instead.
525 return;
526 }
527
528 // @stop issued?
529 if(Stopped) {
530 TimerEvent(0);
531 DEBUG("Stopped, waiting for input");
532 STATE = 0;
533 return;
534 }
535
536 // No, we have an @go for liftoff
537 next = llList2String(lNPCScript, 0); // get the next command
538 DEBUG("Execute:" + next);
539 lNPCScript = llDeleteSubList(lNPCScript, 0, 0); // delete it
540
541 if(llGetListLength(lNPCScript) == 0) {
542 DEBUG("EOF");
543 }
544 ProcessCmd(next);
545 }
546
547
548
549 ProcessCmd(string cmd) {
550
551 DEBUG("ProcessCmd:" + cmd);
552
553 if(llGetSubString(cmd, 0, 0) != "@") {
554 DEBUG("ignoring");
555 STATE = DoProcess;
556 TimerEvent(QUICK); // this is so we do not recurse the stack
557 STATE = 0;
558 return;
559 }
560
561 list data = llParseString2List(cmd, ["="], []);
562 npcAction = llToLower(llStringTrim(llList2String(data, 0), STRING_TRIM));
563
564 DEBUG("Action:" + npcAction);
565 npcParams = llStringTrim(llList2String(data, 1), STRING_TRIM);
566
567 @commands;
568
569 ProcessSensor();
570
571
572 if(npcAction == "@spawn") {
573 DEBUG("@spawn");
574 list spawnData = llParseString2List(npcParams, ["|"], []);
575 sNPCName =llList2String(spawnData, 0); // V 1.6 name in RAM
576
577 list spawnDest = llParseString2List(llList2String(spawnData, 1), ["<", ",", ">"], []);
578 vInitialPos.x = llList2Float(spawnDest, 0);
579 vInitialPos.y = llList2Float(spawnDest, 1);
580 vInitialPos.z = llList2Float(spawnDest, 2);
581
582 DEBUG("Coords for NPC at " + (string) vInitialPos);
583 StateSpawn();
584 return;
585 }
586
587 if(! avatarPresent){
588 DoNobodyHome();
589 DEBUG("No avatar nearby");
590 STATE = 0;
591 return;
592 } else {
593 if( NPCKey() == NULL_KEY) {
594 StateSpawn();
595 }
596 }
597
598 if(npcAction == "@stop") {
599 DoStop();
600 STATE = 0;
601 return;
602 }
603 else if(npcAction == "@goto") {
604 DEBUG("goto");
605 integer lastIdx = llGetListLength(lNPCScript)-1;
606 lNPCScript = llDeleteSubList(lNPCScript, lastIdx, lastIdx);
607 // Wind commands till goto label.
608 @wind;
609 string next1 = llList2String(lNPCScript, 0);
610 lNPCScript = llDeleteSubList(lNPCScript, 0, 0);
611 lNPCScript += next1;
612 if(next1 != npcParams) jump wind;
613 // Wind the label too.
614 next1 = llList2String(lNPCScript, 0);
615 lNPCScript = llDeleteSubList(lNPCScript, 0, 0);
616 lNPCScript += next1;
617 // Get next command.
618 list data1 = llParseString2List(next1, ["="], []);
619 npcAction = llToLower(llStringTrim(llList2String(data1, 0), STRING_TRIM));
620 npcParams = llStringTrim(llList2String(data1, 1), STRING_TRIM);
621 // Reschedule.
622 jump commands;
623 }
624 else if(npcAction == "@sound") {
625 DEBUG("sound");
626 llTriggerSound(npcParams,1.0);
627 }
628 else if(npcAction == "@randsound") {
629 DEBUG("@randsound");
631 integer rand = llCeil(llFrand(N)) -1; // pick a random sound
633 llTriggerSound(toPlay,1.0);
634 }
635 else if(npcAction == "@walk") {
636 DEBUG("@walk");
637 GetDest(npcParams);
638 walkstate = 1;// walking
639 NPCWalkOption = OS_NPC_NO_FLY ;
640 StateWalk();
641 return;
642 }
643 else if(npcAction == "@fly") {
644 GetDest(npcParams);
645 walkstate = 2;// flying
646 NPCWalkOption = OS_NPC_FLY ;
647 StateWalk();
648 return;
649 }
650 else if(npcAction == "@run") {
651 DEBUG("@run");
652 GetDest(npcParams);
653 walkstate = 3;// running
654 NPCWalkOption = OS_NPC_NO_FLY | OS_NPC_RUNNING;
655 StateWalk();
656 return;
657 }
658 else if(npcAction == "@land") {
659 DEBUG("@land");
660 GetDest(npcParams);
661 walkstate = 4;// landing
662 NPCWalkOption= OS_NPC_FLY | OS_NPC_LAND_AT_TARGET ;
663 StateWalk();
664 return;
665 }
666 else if(npcAction == "@say") {
667 DEBUG("@say " + npcParams);
668 osNpcSay(NPCKey(), 0, npcParams);
669 }
670 else if(npcAction == "@shout") {
671 DEBUG("@shout");
672 osNpcShout(NPCKey(),0, npcParams);
673 }
674 else if(npcAction == "@whisper") {
675 DEBUG("@whisper " + npcParams);
676 osNpcWhisper(NPCKey(),0, npcParams);
677 }
678 // speak a command on a channel, so you can open doors and control stuff.
679 else if(npcAction == "@cmd") {
680 DEBUG("@cmd");
681 list dataToSpeak = llParseString2List(npcParams, ["|"], []);
682 string channel = llList2String(dataToSpeak,0);
683 DEBUG("Channel:"+(string) channel);
684 integer iChannel = (integer) channel;
685 string stringToSpeak = llList2String(dataToSpeak,1);
686 llSay(iChannel, stringToSpeak);
687 }
688 // stop everything
689 else if(npcAction == "@pause") {
690 RAMPause = (float) npcParams;
691 DoPause();
692 return;
693 }
694 else if(npcAction == "@wander") {
695 list wanderData = llParseString2List(npcParams, ["|"], []);
696 RAMwd = (float) llList2String(wanderData, 0);
697 RAMwc = (integer) llList2String(wanderData, 1);
698 vDestPos = osNpcGetPos(NPCKey()); // set the wander start
699 DEBUG("Starting at " + (string) vDestPos);
700 StateWander();
701 return;
702 }
703 else if(npcAction == "@rotate") {
704 RAMrot = (float) npcParams;
705 DoRotate();
706 }
707 else if(npcAction == "@sit") {
708 RAMsit= npcParams;
709 StateSit();
710 return;
711 }
712 else if(npcAction == "@touch") {
713 RAMtouch= npcParams;
714 StateTouch();
715 return;
716 }
717 else if(npcAction == "@stand") {
718 DoStand();
719 }
720 else if(npcAction == "@delete") {
721 DoDelete();
722 SetStop(TRUE); // Link controlled - we mnust have a @go to continue with notecards
723 return;
724 }
725 else if(npcAction == "@animate") {
726 list animateData = llParseString2List(npcParams, ["|"], []);
727 RAManimationName = llList2String(animateData, 0);
728 RAManimationTime = (float) llList2String(animateData, 1);
729 StateAnimate();
730 return;
731 }
732 else if(npcAction == "@appearance" ){
733 DoAppearance(npcParams);
734 }
735 else if(npcAction =="@speed") {
736 DoSpeed(npcParams);
737 }
738 else if(npcAction =="@notecard") {
739 DoNewNote(npcParams);
740 Notecard = npcParams;
741 }
742 else if(npcAction == "@attach")
743 {
744 DoAttach(npcParams);
745 }
746
747 STATE = 0;
748 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
749 }
750
751
752
753 /////////////////// UTILITY Functions, not state-like //////////////////
754
755 // DEBUG(string) will chat a string or display it as hovertext if debug == TRUE
756 DEBUG(string str) {
757 if(debug)
758 llOwnerSay( str); // Send the owner debug info so you can chase NPCS
759 if(iTitleText) {
760 llSetText(str,<1.0,1.0,1.0>,1.0); // show hovertext
761
762 }
763 }
764
765 GetDest(string npcParams) {
766 list dest = llParseString2List(npcParams, ["<", ",", ">"], []);
767 vDestPos.x = llList2Float(dest, 0);
768 vDestPos.y = llList2Float(dest, 1);
769 vDestPos.z = llList2Float(dest, 2);
770 }
771
772 NPCReadNoteCard(string Note) {
773 DEBUG("NPCReadNoteCard");
774 lNPCScript = llParseString2List(osGetNotecard(Note), ["\n"], []);
775 }
776
777 integer SenseAvatar()
778 {
779 //Returns a strided list of the UUID, position, and name of each avatar in the region
780 list avatars = llGetAgentList(AGENT_LIST_REGION ,[]);
781 integer numOfAvatars = llGetListLength(avatars);
782 if(numOfAvatars == 0)
783 {
784 DEBUG("No people");
785 return 0;
786 }
787 //DEBUG("Located " + (string)numOfAvatars + " avatars and NPC's");
788
789 integer nAvatars;
790 integer i;
791 for( i = 0;i < numOfAvatars; i++) {
792 key aviKey = llList2Key(avatars,i);
793 if(!osIsNpc(aviKey)) {
794 list detail = llGetObjectDetails(aviKey,[OBJECT_POS]);
795 vector pos = llList2Vector(detail,0);
796 float dist = llVecDist(pos, llGetPos());
797 if(dist < RANGE)
798 {
799 nAvatars++;
800 DEBUG("In range:" + llKey2Name(aviKey));
801 }
802 }
803 }
804 //DEBUG("Located " + (string) nAvatars + " avatars");
805 return nAvatars;
806 }
807
808 // 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.
809 integer checkPerms() {
810
811 integer group = (integer) KeyValueGet("pr");
812 if(! group)
813 priPub = "Owner Only";
814 else
815 priPub = "Group";
816
817
819 kUserKey = llDetectedKey(0);
820 return TRUE;
821 }
822
823 if( group && llDetectedGroup(0)) {
824 kUserKey = llDetectedKey(0);
825 return TRUE;
826 }
827 kUserKey = llDetectedKey(0);
828 return FALSE;
829 }
830
831
832
833 NPCAnimate(string anim)
834 {
835 DEBUG("Start Anim: " + anim);
837
838 if(lastANIM != anim) {
839 if(llStringLength(lastANIM)) {
840 osNpcStopAnimation(NPCKey(), lastANIM);
841 }
842 osNpcPlayAnimation(NPCKey(), anim);
843 lastANIM = anim;
844 }
845 } else {
846 llSay(DEBUG_CHANNEL, "No animation named " + anim);
847 }
848 }
849
850
851 TimerEvent(float timesent)
852 {
853 DEBUG("Setting timer: " + (string) timesent);
854 llSetTimerEvent(timesent);
855 }
856
857 // Kill a NPC by Name
858 Kill(string param)
859 {
860 integer count;
861 list avatars = osGetAvatarList(); // Returns a strided list of the UUID, position, and name of each avatar in the region except the owner.\
862 integer i;
863 integer j = llGetListLength(avatars);
864 for (i=0 ; i <= j; i+=3){
865
866 string desired = llList2String(avatars,i+2);
867 desired = llStringTrim(desired,STRING_TRIM); // should not be needed but is needed
868
869 if(desired == param){
870 vector v = llList2Vector(avatars,i+1);
871 key target = llList2Key(avatars,i); // get the UUID of the avatar
872 osNpcRemove(target);
873 SaveKey(NULL_KEY );
874 llOwnerSay("Removed " + param+ " at location " + (string) v);
875 count++;
876 }
877 }
878
879 NPCEnabled = FALSE; // not in world
880
881 if(count)
882 llOwnerSay("Removed " + (string) count + " NPC's");
883 else
884 llOwnerSay("Could not locate " + param);
885 }
886
887
888 // return a String for the position we are at. Strings used as the caller wants strings
889 string Pos()
890 {
891 vector where = llGetPos(); // find the box position
892
893 where.z += OffsetZ; // use the ground position + an offset
894
895 if(Editor)
896 where = <128,128,31 + llFrand(1)>; // center of sim for editing
897
898 // if attached the height will be too high by 1/2 the agent size
899 if(llGetAttached()) {
901 float Z = size.z;
902 where.z -= Z/2;
903 }
904
905 // DEBUG("Pos= " + (string) where);
906 return (string) where;
907 }
908
909 // setup a menu with a timer for timeouts, called by all make*()
910 menu()
911 {
912 llListenRemove(iHandle);
913 iChannel = llCeil(llFrand(100000) + 20000);
914 iHandle = llListen(iChannel,"","","");
915 TimerEvent(30.0);
916 MENU = TRUE;
917 }
918
919 // make a text box
920 makeText(string Param)
921 {
922 menu();
923 llTextBox(kUserKey, Param, iChannel);
924 }
925
926 // top level menu
927 makeMainMenu()
928 {
929 menu();
930 list buttons = ["Appearance","Recording","Save","Help","-","Erase RAM", priPub,relAbs,"-","Stop NPC",mSensor,"Start NPC"];
931 llDialog(kUserKey,(string) llGetListLength(lCommands) + " Records",buttons,iChannel);
932 }
933
934
935 // Rev 1.4
936 // top level menu for non group/ non owners
937 makeUserMenu()
938 {
939 if(!allowUsers) return;
940
941 menu();
942 list buttons = ["Start NPC","Stop NPC"];
943 llDialog(kUserKey,"Choose",buttons,iChannel);
944 }
945
946
947
948 // programmable menu for @commands
949 makeMenu(list buttons)
950 {
951 menu();
952 llDialog(kUserKey,(string) llGetListLength(lCommands) + " Record",buttons,iChannel);
953 }
954
955
956 // make one or two text boxes with prompts
957 Text(string cmd, string p1, string p2)
958 {
959 sCommand = cmd;
960 sParam2 = "";
962 sParam2 = p2;
963
964 makeText(p1);
965 }
966
967 // Set the Avatar Present flag - if sensors are off and we are forece run, there will be one present.
968 ProcessSensor()
969 {
970 integer SensorOn;
971 if("on" == KeyValueGet("se"))
972 {
973 SensorOn = TRUE; // we need to scan for avatars
974 } else {
975 SensorOn = FALSE; // we need to scan for avatars
976 }
977 DEBUG("Sensor:" + (string) SensorOn);
978
979 integer n = SenseAvatar();
980
981 DEBUG("Avatars:" + (string) n);
982 if(SensorOn && n)
983 avatarPresent = TRUE; // someone is here and we need to tell the system to run
984 else if(SensorOn && !n)
985 avatarPresent = FALSE; // someone is not here and we need to tell the system to stop
986 else { // sensor is off, lete see if there is a NPC. If so, we are ON
987 DEBUG("NPCEnabled:" + (string) NPCEnabled);
988 if(NPCEnabled)
989 avatarPresent = TRUE;
990 else
991 avatarPresent = FALSE;
992 }
993
994 // start up from when when no one is near
995 if(avatarPresent && STATE == NobodyHome)
996 STATE = 0;
997
998 //DEBUG("Avatar Present: " + (string) avatarPresent);
999 }
1000
1001 vector CirclePoint(float radius) {
1002 float x = llFrand(radius *2) - radius; // +/- radius, randomized
1003 float y = llFrand(radius *2) - radius; // +/- radius, randomized
1004 return <x, y, 0>; // so this should always happen
1005 }
1006
1007 string KeyValueGet(string var) {
1008 list dVars = llParseString2List(llGetObjectDesc(), ["&"], []);
1009 do {
1010 list data = llParseString2List(llList2String(dVars, 0), ["="], []);
1011 string k = llList2String(data, 0);
1012 if(k != var) jump continue;
1013 //DEBUG("got " + var + " = " + llList2String(data, 1));
1014 return llList2String(data, 1);
1015 @continue;
1016 dVars = llDeleteSubList(dVars, 0, 0);
1017 } while(llGetListLength(dVars));
1018 return "";
1019 }
1020
1021 KeyValueSet(string var, string val) {
1022
1023 //DEBUG("set " + var + " = " + val);
1024 list dVars = llParseString2List(llGetObjectDesc(), ["&"], []);
1025 if(llGetListLength(dVars) == 0)
1026 {
1027 llSetObjectDesc(var + "=" + val);
1028 return;
1029 }
1030 list result = [];
1031 do {
1032 list data = llParseString2List(llList2String(dVars, 0), ["="], []);
1033 string k = llList2String(data, 0);
1034 if(k == "") jump continue;
1035 if(k == var && val == "") jump continue;
1036 if(k == var) {
1037 result += k + "=" + val;
1038 val = "";
1039 jump continue;
1040 }
1041 string v = llList2String(data, 1);
1042 if(v == "") jump continue;
1043 result += k + "=" + v;
1044 @continue;
1045 dVars = llDeleteSubList(dVars, 0, 0);
1046 } while(llGetListLength(dVars));
1047 if(val != "") result += var + "=" + val;
1049 }
1050
1051
1052 // clear RAM
1053 Clr() {
1054
1055 lCommands = [];
1056 llOwnerSay("RAM Memory cleared. Notecards, if any, are not modified.");
1057 makeMainMenu();
1058 }
1059
1060 integer checkNoteCards()
1061 {
1062 // Check that they have saved an Appeaance and Path notecard
1063 integer num = llGetInventoryNumber(INVENTORY_NOTECARD); // how many notecards overall
1064
1065 integer i;
1066 integer count;
1067 for (; i < num; i++){
1069 count++;
1071 count++;
1072 }
1073 DEBUG("Checked " + (string) count + " Notecards");
1074 // if we have both, run the NPC
1075 return count;
1076 }
1077
1078 Update(string SName) {
1079
1080 // delete all NPC*scripts except myself
1081 integer i;
1083 for (i = 0; i < j; i++) {
1085 string match = llGetSubString(name,0,2);
1086 if(match == SName && llGetScriptName() != name)
1087 {
1088 llRemoveInventory(name);
1089 llOwnerSay("Upgraded");
1090 }
1091 }
1092
1093 }
1094
1095 // Get all default saved params from the Description
1096 GetSwitches()
1097 {
1098 string rA = KeyValueGet("co"); // Get the remembered menu setting for Abs Vs Relative
1099 if(rA == "A")
1100 relAbs = "Absolute";
1101 else if(rA == "R")
1102 relAbs = "Relative";
1103 else
1104 relAbs = "Absolute";
1105
1106
1107 // reenable NPC if sensor is on.
1108 if("on" == KeyValueGet("se"))
1109 {
1110 NPCEnabled = TRUE;
1111 mSensor = "Sense is On";
1112 ProcessSensor(); // fake 1 avatar to get it rezzed
1113 } else {
1114 mSensor = "Sense is Off";
1115 }
1116 }
1117
1118
1119 SaveKey(key akey)
1120 {
1121 DEBUG("Saving Key of " + (string) akey);
1122 KeyValueSet("key", akey);
1123 if(akey != (key) KeyValueGet("key") )
1124 {
1125 DEBUG("Fatal error, cannot save key");
1126 }
1127 gNpcKey = akey;
1128 }
1129
1130
1131 key NPCKey()
1132 {
1133 key akey = gNpcKey; // from cached copy
1134 // gNpcKey saves a lot of CPU processing by caching the key, if blank we get it from the description
1135 if(gNpcKey == NULL_KEY)
1136 {
1137 //DEBUG("Get DKey");
1138 akey = KeyValueGet("key"); // from Description of the prim
1139 }
1140 // DEBUG("NPC KEY:" + (string) akey);
1141 return akey;
1142 }
1143
1144
1145 /////////////////// CODE BEGINS //////////////////
1146
1147
1148 default
1149 {
1150 changed(integer change) {
1151 if(change & CHANGED_REGION_START) {
1153 }
1154 }
1155
1156 on_rez(integer start_param)
1157 {
1159 }
1160
1161 state_entry() {
1162
1163 llSetText("",<1,1,1>,1.0); // clr all hovertext- we may not be using it.
1164 DoDelete(); // kill any NPC that is out running
1165 Update("NPC"); // If dragged and ropped into a prim with any script named "NPC...", this will replace it.
1166 GetSwitches(); // Get all default saved params from the Description
1167 llSetTimerEvent(TIMER);
1168 }
1169
1170
1172 { // if touched, make a menu
1173
1174 if(checkPerms()) {
1175 if(RecordPath == STATE) {
1176 makeMenu(lAtButtons);
1177 } else {
1178 makeMainMenu();
1179 }
1180 } else {
1181 makeUserMenu();
1182 }
1183 }
1184
1185 // menu listener
1186 listen(integer iChannel, string name, key id, string message) {
1187
1188 if(MENU) {
1189 llListenRemove(iHandle);
1190 MENU = 0; // menu is off
1191 iHandle = 0;
1192 }
1193
1194 if(message == "Stop NPC")
1195 {
1196 lNPCScript = []; // force reload of notecard
1197 NPCEnabled = FALSE;
1198 if(NPCKey() != NULL_KEY){
1199 Kill(sNPCName);
1200 sNPCName = "";
1201 } else {
1202 bNPC_STOP = TRUE;
1203 makeText("Enter name of an NPC to stop");
1204 }
1205 }
1206 else if(message == "Menu" ) {
1207 makeMainMenu();
1208 }
1209 else if(message == "Erase RAM"){
1210 Clr();
1211 }
1212 else if(message == "Relative"){
1213 relAbs = "Absolute";
1214 KeyValueSet("co","A"); // remember coordinates = A
1215 Clr();
1216 }
1217 else if(message == "Absolute"){
1218 relAbs = "Relative";
1219 KeyValueSet("co","R"); // remember coordinates = R
1220 Clr();
1221 }
1222 else if(message == "Recording"){
1223 DoMenuForCommands(); // show them the recording menu
1224 }
1225 else if(message == "Owner Only") {
1226 priPub = "Group";
1227 KeyValueSet("pr","1");
1228
1229 llOwnerSay("Group members have control");
1230 makeMainMenu();
1231 }
1232 else if(message == "Group") {
1233 priPub = "Owner Only";
1234 KeyValueSet("pr","0");
1235 llOwnerSay("Only you have control");
1236 makeMainMenu();
1237 }
1238 else if(message == "Sense is On") {
1239 mSensor ="Sense is Off";
1240 KeyValueSet("se", "off");
1241 llOwnerSay(mSensor);
1242 makeMainMenu();
1243 }
1244 else if(message == "Sense is Off") {
1245 mSensor ="Sense is On";
1246 llOwnerSay(mSensor);
1247 KeyValueSet("se", "on");
1248
1249 NPCEnabled = FALSE;
1250
1251 integer count = checkNoteCards();
1252 if(count >= 2) {
1253 DEBUG("Notecards approved , calling DoProcessNPCLine");
1254 DoProcessNPCLine();
1255 return;
1256 }
1257 if(Editor) {
1258 DoProcessNPCLine();
1259 return;
1260 }
1261
1262 llOwnerSay("You have not saved a recording and/or appearance, so you cannot start a NPC");
1263 makeMainMenu();
1264 }
1265 else if(message == "Appearance") {
1266 llRemoveInventory(Appearance); // delete the notecard
1267 osAgentSaveAppearance(kUserKey,Appearance); // make the ntecard
1268 llOwnerSay("Your outfit has been saved");
1269 makeMainMenu();
1270 }
1271 else if(message == "Save") {
1272 if(llGetListLength(lCommands) == 0) {
1273 llOwnerSay("Nothing recorded, you need to make a recording first");
1274 makeMainMenu();
1275 return;
1276 }
1277 DoSave();
1278 }
1279 else if(message == "Help"){
1280 llLoadURL(kUserKey,"Click to view help","http://www.outworldz.com/opensim/posts/NPC/");
1281 makeMainMenu();
1282 }
1283 else if(message == "Start NPC") {
1284 integer count = checkNoteCards();
1285
1286 NPCEnabled = TRUE;
1287
1288 if(Editor) {
1289 DoProcessNPCLine();
1290 return;
1291 }
1292
1293 if(count >= 2) {
1294 DEBUG("Notecards approved , calling DoProcessNPCLine");
1295 SetStop(FALSE); // Let's run the notecard
1296 DoProcessNPCLine();
1297 return;
1298 }
1299
1300 llOwnerSay("You have not saved a recording or maybe an appearance, so we cannot start a NPC");
1301
1302 }
1303 else if(bNPC_STOP){
1304 bNPC_STOP = FALSE;
1305 Kill(message);
1306 }
1307 else if(message == ">>"){
1308 makeMenu(lMenu2);
1309 }
1310 else if(message == ">>>"){
1311 makeMenu(lMenu3);
1312 }
1313 else if(message == "<<") {
1314 makeMenu(lAtButtons);
1315 }
1316 else if(message == "<<<") {
1317 makeMenu(lMenu2);
1318 }
1319 else if(message == "@comment"){
1320 Text("# ","Enter a comment","");
1321 }
1322 else if(message == "@stop"){
1323 lCommands += "@stop"+ "\n";
1324 makeMenu(lAtButtons);
1325 }
1326 else if(message == "@run"){
1327 lCommands += "@run=" + Pos() + "\n";
1328 llOwnerSay("Recorded position: " + Pos());
1329 makeMenu(lAtButtons);
1330 }
1331 else if(message == "@fly"){
1332 lCommands += "@fly=" + Pos() + "\n";
1333 llOwnerSay("Recorded position: " + Pos());
1334 makeMenu(lAtButtons);
1335 }
1336 else if(message == "@land"){
1337 lCommands += "@land=" + Pos() + "\n";
1338 llOwnerSay("Recorded position: " + Pos());
1339 makeMenu(lAtButtons);
1340 }
1341 else if(message == "@walk") {
1342 lCommands += "@walk=" + Pos() + "\n";
1343 llOwnerSay("Recorded position: " + Pos());
1344 makeMenu(lAtButtons);
1345 }
1346 else if(message == "@stop"){
1347 lCommands += "@stop"+ "\n";
1348 makeMenu(lAtButtons);
1349 }
1350 else if(message == "@sound"){
1351 Text("@sound=","Enter a sound name or UUID to trigger","");
1352 }
1353 else if(message == "@randsound"){
1354 lCommands += "@randsound"+ "\n";
1355 makeMenu(lAtButtons);
1356 }
1357 else if(message == "@say") {
1358 Text("@say=","Enter what the NPC will say","");
1359 }
1360 else if(message == "@whisper"){
1361 Text("@whisper=","Enter what the NPC will whisper","");
1362 }
1363 else if(message == "@shout"){
1364 Text("@shout=","Enter what the NPC will shout","");
1365 }
1366 else if(message == "@wander") {
1367 Text("@wander=","Enter radius to wander","Enter number of wanders");
1368 }
1369 else if(message == "@pause") {
1370 Text("@pause=","Enter time to pause","");
1371 }
1372 else if(message == "@rotate") {
1373 Text("@rotate=","Enter degrees to rotate","");
1374 }
1375 else if(message == "@sit"){
1376 Text("@sit=","Enter name of object to sit on","");
1377 }
1378 else if(message == "@touch"){
1379 Text("@touch=","Enter name of object to touch","");
1380 }
1381 else if(message == "@cmd"){
1382 Text("@cmd=","Enter cjhannel to speak on","Enter text to speak");
1383 }
1384 else if(message == "@stand"){
1385 lCommands += "@stand\n";
1386 llOwnerSay("Stand Recorded");
1387 makeMenu(lAtButtons);
1388 }
1389 else if(message == "@animate"){
1390 Text("@animate=","Enter animation name to play","Enter time to play the animation");
1391 }
1392 else if(message == "@attach"){
1393 Text("@animate=","Enter inventory name to attach","Enter number of the attachment point (1-40)");
1394 }
1395 else if(message == "@speed"){
1396 Text("@speed=","Enter a speed for the NPC, 1=100% normal speed, 0.5=50% speed","");
1397 }
1398
1399
1400 // Save NPC name
1401 else if(MakeNotecard == STATE) {
1402 sNPCName = message; // in case we need to kill it.
1403
1404 vector vDest = (vector) Pos();
1405
1406 if(relAbs == "Relative")
1407 {
1408 vDest -= llGetPos(); // just an offset for relative
1409 }
1410 sNotecard = "@spawn=" + message + "|" + (string) vDest + "\n";
1411 integer i;
1412 integer j = llGetListLength(lCommands);
1413 for (; i < j; i++){
1414 // get the command to save to the notecard
1415 string line = llList2String(lCommands,i);
1416 if(relAbs == "Absolute") {
1417 sNotecard += line; // add the un-modified string to the notecard
1418 } else {
1419 // since we have to record absolute coords since we do not know where the box goes until they press Save,
1420 // we process the absolute to relative conversion for walks here
1421 list parts = llParseString2List(line,["="],[]); //get the @command
1422
1423 if(llList2String(parts,0) == "@walk") {
1424 vector vec = (vector) llList2String(parts,1) - llGetPos();
1425 sNotecard += "@walk=" + (string) vec + "\n";
1426 }
1427 else if(llList2String(parts,0) == "@fly") {
1428 vector vec = (vector) llList2String(parts,1) - llGetPos();
1429 sNotecard += "@fly=" + (string) vec + "\n";
1430 }
1431 else if(llList2String(parts,0) == "@run") {
1432 vector vec = (vector) llList2String(parts,1) - llGetPos();
1433 sNotecard += "@run=" + (string) vec + "\n";
1434 }
1435 else if(llList2String(parts,0) == "@land") {
1436 vector vec = (vector) llList2String(parts,1) - llGetPos();
1437 sNotecard += "@land=" + (string) vec + "\n";
1438 }
1439 else {
1440 sNotecard += line; // add the un-modified string to the notecard
1441 }
1442 }
1443 }
1444 llRemoveInventory(Notecard); // delete the old notecard
1445 osMakeNotecard(Notecard,sNotecard); // Makes the notecard.
1446 llSay(0,sNotecard);
1447 llOwnerSay("Commands notecard has been written");
1448 STATE = 0;
1449 } // MakeNotecard
1450
1451 else if(! llStringLength(sParam2)) {
1452 lCommands += sCommand + message + "\n";
1453 llOwnerSay("Recorded");
1454 makeMenu(lAtButtons);
1455 }
1456 else if(llStringLength(sParam2)){
1457 sCommand = sCommand + message + "|";
1458 llOwnerSay("Recorded");
1459 makeText(sParam2);
1460 sParam2 = "";
1461 }
1462
1463 }
1464
1465
1466
1467 timer(){
1468 // DEBUG("tick");
1469
1470 // if llDialog is up, kill the listener for the dialog box.
1471 if(iHandle) {
1472 llOwnerSay("Menu timed out");
1473 llListenRemove(iHandle);
1474 iHandle = 0;
1475 return; // ^^^^^^^^^^^^^^^^^^^^^^^
1476 }
1477 // if NoBodyHome, we are sensing for an avatar
1478 else if(NobodyHome == STATE) {
1479 ProcessSensor();
1480 return;
1481 }
1482 // if we are spawning, we need time to rez the NPC, then start processing NPC Commands.
1483 else if(Spawning == STATE) {
1484 STATE = 0;
1485 TimerEvent(TIMER);
1486 }
1487 // We end aniamtions with a timer
1488 else if(Animate == STATE){
1489 NPCAnimate(STAND);
1490 TimerEvent(TIMER);
1491 }
1492
1493 else if(Walking == STATE) {
1494 if(--iWaitCounter) {
1495 if(llVecDist(osNpcGetPos(NPCKey()), newDest) > MAXDIST) {
1496 return;
1497 }
1498 }
1499
1500 DEBUG("At Destination: " + (string) newDest);
1501
1502 // walk, fly, run, land
1503 if(walkstate == 1) {
1504 NPCAnimate(STAND);
1505 NPCWalkOption = OS_NPC_NO_FLY;
1506 } else if(walkstate == 2) {
1507 // nothing
1508 } else if(walkstate == 3) {
1509 NPCAnimate(STAND);
1510 NPCWalkOption = OS_NPC_NO_FLY;
1511 } else if(walkstate == 4) {
1512 llShout(FLIGHT,"landing");
1513 NPCAnimate(STAND);
1514 NPCWalkOption = OS_NPC_NO_FLY;
1515 }
1516 }
1517 // Wandering timer
1518 else if(Wander == STATE) {
1519 if(--iWaitCounter) { // wait 60 seconds to get to a destination.
1520 if(llVecDist(osNpcGetPos(NPCKey()), vWanderPos) > MAXDIST)
1521 return;
1522 }
1523
1524 // see if wander counter == 0, if so, stop walking, go to stand and process next line
1525 if(RAMwc == 0) {
1526 NPCAnimate(STAND);
1527 DEBUG("Wander ended, calling DoProcessNPCLine");
1528 STATE = 0;
1529 DoProcessNPCLine();
1530 return;
1531 }
1532 // one less time to wander around
1533 RAMwc--;
1534 NPCAnimate(STAND);
1535 TimerEvent(TIMER);
1536 StateWanderhold();
1537 return;
1538 }
1539 // Wandering requires us to re-wander when we reach a destination
1540 else if(WanderHold == STATE) {
1541 StateWander();
1542 TimerEvent(TIMER);
1543 return;
1544 }
1545 else if(DoProcess == STATE) {
1546 TimerEvent(QUICK);
1547 }
1548
1549
1550 STATE = 0;
1551
1552 // We always process a NPC line at end of timer.
1553 DEBUG("Tick end, calling DoProcessNPCLine");
1554 DoProcessNPCLine();
1555 }
1556
1557 // sensors are used for sitting on prims
1558 // Neo Cortex: added different SensorFunc states to trigger sit or touch
1559 sensor(integer num) {
1560 if(SensorFunc == 1) {
1561 osNpcSit(NPCKey(), llDetectedKey(0), OS_NPC_SIT_NOW);
1562 DEBUG("Seated, calling DoProcessNPCLine");
1563 SensorFunc = 0;
1564 } else if(SensorFunc == 2) {
1565 osNpcTouch(NPCKey(), llDetectedKey(0), LINK_THIS);
1566 DEBUG("Touched, calling DoProcessNPCLine");
1567 SensorFunc = 0;
1568 }
1569 DoProcessNPCLine();
1570 }
1571 no_sensor(){
1572 DEBUG ("no target prim located, calling DoProcessNPCLine");
1573 SensorFunc = 0;
1574 DoProcessNPCLine();
1575 }
1576
1577
1578 link_message(integer sender, integer num, string str, key id){
1579 DEBUG("Command In:" + str);
1580 if(str=="@go") {
1581 SetStop(FALSE); // Let's run the notecard
1582 DEBUG("@go running");
1583 DoProcessNPCLine();
1584 } else {
1585 Stack += [str]; // take anything, the controller will filter away non @ stuff
1586 if(STATE == 0) {
1587 DEBUG("calling DoNPC");
1588 DoProcessNPCLine();
1589 } else{
1590 DEBUG("Queued");
1591
1592 }
1593 }
1594 }
1595
1596 }
1597
1598
1599
1600
1601
1602
1603
1604 // This is Rev 3.7 08/11/2015 which added @attach
1605
1606 // Revision History
1607 // Rev 1.1 10-2-2014 @Sit did not work. Minor tweaks to casting for lslEditor
1608 // Rev 1.2 10-14-2014 @ sit had wrong type.
1609 // Rev 1.3 relative movement fixed for @fly
1610 // Rev 1.4 4-3-2014 allow anyone to use this, non owners and non group members can only start and stop.
1611 // Rev 1.5 5-17-2014 set sensor to auto start on reboot of sim
1612 // Rev 1.6 5-24-2014 move menu so you can get it by touching, removed many of the KeyValues to RAM for efficiency
1613 // Rev 1.7 CHANGED_REGION_START, not CHANGED_REGION_START (Opensim difference)
1614 // Rev 1.8 tuned up Kill NPC, added more flexible upgrader
1615 // Rev 1.9 Better script injection by link message// Rev 2.0 Added osSetSpeed so you can speed up or slow down an NPC.
1616 // Rev 2.1 No laggy sensor used exept to sit on stuff
1617 // Rev 2.2 Various sensor fixes
1618 // Rev 2.3 Sets No Sensor in menu, must be started by hand
1619 // Rev 2.4 - reserved for patches to 2.3 if needed
1620 // Rev 3.0 Refactor out into subs, not states to make command injection easier
1621 // New command: @appearance=Notecardname so you can switch to a new notecard on the fly
1622 // New command: @speed=1.0 which slows up ( < 1 ) or speeds up ( > 1)
1623 // Rev 3.1 Commands are not interruptible by Link Message
1624 // Rev 3.2 Sensor patches for consistency in removing the NPC
1625 // Rev 3.3 Added Touch command by Neo.Cortex@hbase42/hopto/org:8002
1626 // Added Menu 3 for notecard and appearance commands
1627 // Rev 3.4 animation timer cannot be zero or it shuts off timer tweaked
1628 // solves the NPC starting up when no sensor is set.
1629 // Rev 3.5 fixes saving to !Path notecard
1630 // Rev 3.6 08-11-2015 @delete acts like @stop. TYjhe NPC now rezzes after an @go back in where it was deleted
1631 // Rev 3.7 08-11-2015 @attach command added to load an attachment from the inventory to the NPC
1632 // Rev 3.8 08-17-2015 process queued commands one at a time without calling ProcessNPCLine on link message
1633 //*******************************************************************//
1634
1635 // Instructions on how to use this is at http://www.outworldz.com/opensim/posts/NPC/
1636 // This is an OpenSim-only script.
1637 // Author: Ferd Frederix aka Fred Beckhusen - fred@mitsi.com
1638
1639 ////////////////////////////////////////////////////////////////////////////////////////////
1640 // Original code was Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 //
1641 ///////////////////////////////////////////////////////////////////////////////////////////
1642 // Please see: http://www.gnu.org/licenses/gpl.html for legal details, //
1643 // rights of fair usage, the disclaimer and warranty conditions. //
1644 ///////////////////////////////////////////////////////////////////////////////////////////
1645 // The original NPC controller was from http://was.fm/opensim:npc
1646 // Extensive additions and bug fixes by Fred Beckhusem, aka Ferd Frederix, fred@mitsi.com
1647 // llSensor had two params swapped
1648 // @Wander would wander where it had rezzed, not where it was.
1649 // There was no 'no_sensor' event in sit, so if a @sit failed, the NPC got stuck
1650 // 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.
1651 // New code:
1652 // Merged with new Route recorder and notecard writer
1653 // If the NPC failed to reach a destination it never moved on. Added WAIT global to tune this
1654 // Exposed many tunable variables and ported the code to LSLEditor.
1655 // Added floating point to times in notecard.
1656
1657 // Added @sound, @randsound, @whisper, @shout, and @cmd controls.
1658 //
1659 // notecards integers are not floats for better control
1660 //
1661 // Link Messages may be used to perform external control by injecting @commands into the stream of actions
1662 // Example:
1663 // To chat something, such as with a chat robot
1664 // llMessageLinked(LINK_SET,0,"@npc_say=Hello","");
1665
1666 // This script assumes that NPCs and OSSl scripting is enabled in the OpenSim configuration.
1667 // In order to enable them, the following changes must be made in the OpenSim.ini configuration file:
1668 //
1669 // ; Turn on OSSL
1670 // AllowOSFunctions = true
1671 // OSFunctionThreatLevel = Severe
1672
1673 //[NPC]
1674 // ;# {Enabled} {} {Enable Non Player Character (NPC) facilities} {true false}
1675 // Enabled = true
1676 //
1677 // and then the server has to be restarted.
1678
1679
1680 // Commands: All commands begin with an @ sign. All other lines are ignored
1681 // @commands may have optional parameters. The syntax is always:
1682 // @cmd=parm1|parm2
1683 // NaN in the table below meand Not a Number. This means there is no parameter
1684
1685 //Command First Parameter Second Parameter Description
1686 //@spawn name location (vector) Rezzes an NPC with name at a location.
1687 //@appearance NoteCardName NaN switch the NPC appearance to a new notecard
1688 //@walk destination (vector) NaN Makes the NPC walk to destination.
1689 //@fly destination (vector) NaN Makes the NPC fly to destination.
1690 //@land destination (vector) NaN Makes the NPC land at destination.
1691 //@say string NaN Makes the NPC speak a phrase.
1692 //@whisper string NaN Makes the NPC whisper a phrase.
1693 //@shout string NaN Makes the NPC shout a phrase.
1694 //@pause seconds (float) NaN Makes the NPC wait for a multiple of seconds.
1695 //@wander radius (float) cycles (integer) Makes the NPC wander in radius, for cycles seconds.
1696 //@delete NaN NaN Removes the NPC. Requires a link message to continue
1697 //@goto label (string) NaN Jump to the label label in the script.
1698 //@animate animation (string) time (float) Makes the NPC trigger the animation animation for time seconds.
1699 //@sound sound_name NaN plays a sound from inventory
1700 //@randsound NaN NaN Plays a random sound from inventory
1701 //@rotate degrees (float) NaN Rotate the NPC degrees around the Z axis.
1702 //@sit primitive name NaN Sit on a primitive with a given name.
1703 //@touch primitive name NaN Touch on a primitive with a given name.
1704 //@stand NaN NaN If sitting on a primitive, stand up.
1705 //@cmd channel (integer) string Says string on channel, for controlling external gadgets
1706 //@stop NaN NaN Halts the NPC script indefinitely. Can be started with a link message
1707 //@go NaN NaN Continues on next notecard line, for use in link messages
1708 //@speed speed (float) NaN from 0 to N, where 1.0 ius a normal speed of an avatar. 0.2 is a turtle.
1709 //@notecard notename (string) NaN load a new Path notecard
1710 //@attach InventoryName attachmentPoint load an attachment from the inventory to the NPC onto point
1711
1712 // Constant attachmentPoint Comment
1713 // ATTACH_CHEST 1 chest/sternum
1714 // ATTACH_HEAD 2 head
1715 // ATTACH_LSHOULDER 3 left shoulder
1716 // ATTACH_RSHOULDER 4 right shoulder
1717 // ATTACH_LHAND 5 left hand
1718 // ATTACH_RHAND 6 right hand
1719 // ATTACH_LFOOT 7 left foot
1720 // ATTACH_RFOOT 8 right foot
1721 // ATTACH_BACK 9 back
1722 // ATTACH_PELVIS 10 pelvis
1723 // ATTACH_MOUTH 11 mouth
1724 // ATTACH_CHIN 12 chin
1725 // ATTACH_LEAR 13 left ear
1726 // ATTACH_REAR 14 right ear
1727 // ATTACH_LEYE 15 left eye
1728 // ATTACH_REYE 16 right eye
1729 // ATTACH_NOSE 17 nose
1730 // ATTACH_RUARM 18 right upper arm
1731 // ATTACH_RLARM 19 right lower arm
1732 // ATTACH_LUARM 20 left upper arm
1733 // ATTACH_LLARM 21 left lower arm
1734 // ATTACH_RHIP 22 right hip
1735 // ATTACH_RULEG 23 right upper leg
1736 // ATTACH_RLLEG 24 right lower leg
1737 // ATTACH_LHIP 25 left hip
1738 // ATTACH_LULEG 26 left upper leg
1739 // ATTACH_LLLEG 27 left lower leg
1740 // ATTACH_BELLY 28 belly/stomach/tummy
1741 // ATTACH_LEFT_PEC 29 left pectoral
1742 // ATTACH_RIGHT_PEC 30 right pectoral
1743 // ATTACH_HUD_CENTER_2 31 HUD Center 2
1744 // ATTACH_HUD_TOP_RIGHT 32 HUD Top Right
1745 // ATTACH_HUD_TOP_CENTER33 HUD Top
1746 // ATTACH_HUD_TOP_LEFT 34 HUD Top Left
1747 // ATTACH_HUD_CENTER_1 35 HUD Center
1748 // ATTACH_HUD_BOTTOM_LEFT 36 HUD Bottom Left
1749 // ATTACH_HUD_BOTTOM 37 HUD Bottom
1750 // ATTACH_HUD_BOTTOM_RIGHT 38 HUD Bottom Right
1751 // ATTACH_NECK 39 neck
1752 // ATTACH_AVATAR_CENTER 40 avatar center/root
1753
1754
1755
1756 //////////////////////////////////////////////////////////
1757 // DEBUG //
1758 //////////////////////////////////////////////////////////
1759 integer debug = FALSE; // set to TRUE or FALSE for debug chat on various actions
1760 integer Editor = FALSE; // set to to TRUE to working in LSLEditor, FALSE for in-world.
1761 // you must also include the NPC commands found in the other script since LSLEditor does not support OpenSim
1762 integer iTitleText = TRUE; // set to TRUE to see debug info in text above the controller
1763
1764 //////////////////////////////////////////////////////////
1765 // TUNABLE CONFIGURATION //
1766 //////////////////////////////////////////////////////////
1767 float TIMER = 2; // how often the system checks the distance traveled. Fastest you can go is 0.5 seconds
1768 float QUICK = 1; // when we need to move to the next state, we use a QUICK timer
1769 string Appearance = "!Appearance"; // The name of the recorded Appearance notecard
1770 string Notecard = "!Path"; // The name of the recorded routes
1771 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
1772 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
1773 integer WANDERRAND = TRUE; // set to TRUE and they will pause during wanders a random number of seconds
1774 float WANDERTIME = 3.0; // how long they stand after each @wander,if WANDERRAND is FALSE. If WANDERRAND is TRUE, this is the max time
1775 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.
1776 float RANGE = 50; // 1 to N meters - anyone this close to the controller will start NPCS if Sensor button is clicked
1777 float REZTIME = 2.0; // wait this long for NPC to rez in, then start the process
1778 string STAND = "Stand"; // the name of the default Stand animation
1779 string WALK = "Walk"; // the name of the default Walk animation
1780 string FLY = "Fly"; // the name of the default Fly animation
1781 string RUN = "Run"; // the name of the default Run animation
1782 string LAND = "Land"; // the name of the default land animation ( for birds only)
1783 float OffsetZ = 0.5; // appear 0.5 meter above ground, this is added to all destinations to keep them from sinking in.
1784 float SPEEDMULT =0.5; // 1.0 = regular avatar speed. Smaller numbers slow down walks. Large numbers speed them up.
1785 integer FLIGHT = 299; // For controlling wings. A channel that is shouted at when flight starts and ends. "flying" or "landing"
1786
1787 // DESCRIPTIONS FIELDS HAVE TO SURVIVE A RESET
1788 // These vars are stored by saving them with KeyValueSet
1789 // "pr" is a 0 if it is set for Owner Only, 1 for Group control
1790 // "se" is "on" if Started
1791 // "co" = "R" or "A" for relative or absolute addressing mode
1792 // "key" = NPC key
1793
1794 // These Globals used to be stored in description. Moved to RAM in V1.6
1795 float RAMPause; // @pause param
1796 float RAMwd ; // @wander distance
1797 integer RAMwc; // @wander count
1798 float RAMrot; // @rotate
1799 string RAMsit; // @sit primname
1800 string RAMtouch; // @touch primname
1801 string RAManimationName; // @animate animation (string) time (float)
1802 float RAManimationTime;
1803
1804 // other globals section
1805 integer iChannel; // a listen channel, randomly assigned
1806 integer iHandle; // the handle to it
1807
1808 // NPC controls
1809 vector newDest ; // tmp storage for the walks
1810 integer iWaitCounter ; // wait for this number of seconds for the NPC to reach a desrtination
1811 string sNPCName; // the name of the NPC that may be in world. So we can remove it.
1812 integer bNPC_STOP = FALSE; // boolean to reuse a listener
1813 integer Stopped = FALSE; // set to TRUE by link messages so we do not remember them
1814 float fTimerVal ; // how long we wait when wandering (calculated)
1815 float NPCEnabled; // true if the NPC is suppodes to be running
1816
1817 // 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.
1818 // OS_NPC_NOT_OWNED will create an 'unowned' NPC that will respond to any script that has OSSL permissions to call osNpc* commands.
1819 integer NPCOptions = OS_NPC_CREATOR_OWNED; // only yhe owner of this box can control this NPC.
1820
1821 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
1822
1823 integer NPCWalkOption; // Some notes for what happens to NPCWalkOption:
1824 // 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.
1825 // 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
1826 //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.
1827 // OS_NPC_RUNNING - if given, NPC avatar moves at running/fast flying speed, otherwise moves at walking/slow flying speed.
1828
1829 // menus
1830 string mSensor="Sense is Off"; // Sensor or "No Sensor"
1831
1832 list lAtButtons = ["Menu","-", ">>", "@run", "@walk", "@fly", "@land", "@wander", "@sit", "@stand","@animate","@rotate"];
1833 list lMenu2 = ["<<", "@comment", ">>>", "@stop", "@say", "@whisper","@shout","@sound","@randsound","@cmd", "@pause", "@delete"];
1834 list lMenu3 = ["<<<","@notecard","@appearance", "@touch", "@speed", "@attach", "-","-", "-", "-", "-", "-" ];
1835
1836 string sCommand; // place to store a command for two-prompted ones
1837 string sParam2; // place to store a prompt for two-prompted ones
1838 string priPub = "Owner Only"; // Private or Group
1839 key kUserKey; // the person who is controlling the avatar, not the Owner
1840 // the command lists
1841 list lCommands; // commands are stored here
1842 list lNPCScript; // Storage for the NPC script.
1843 string npcAction; // Storage for the next action. @cmd=0|hello, this becomes @cmd
1844 string npcParams; // Storage for the param, @cmd=0|hello, this becomes 0|hello
1845
1846 // misc vars
1847 string sNotecard; // commands are stored here temporarily for dumping
1848 vector vWanderPos; // a place to wander to
1849 string lastANIM ; // last animation run
1850 // Sensor
1851 integer avatarPresent; // Sensor sets this flag when people are within Range.
1852
1853 // Coordinate control
1854 vector vInitialPos ; // Vector that will be filled by the script with the initial starting position in region coordinates.
1855 vector vDestPos = ZERO_VECTOR; // Storage for destination position.
1856 string relAbs = "Relative"; // absolute vs relative positioning
1857 vector lastKnownPos; // last known NPC position when we deleted it
1858
1859 // STATES
1860 integer MENU ; // processing a dialog box state, may be concurrent with STATE
1861 integer STATE; // state storage
1862 integer MakeNotecard = 1; // displaying a text box for NPC name
1863 integer RecordPath = 2; // displaying a path notecard menu
1864 integer NobodyHome = 3; // looking for an avatar
1865 integer Spawning = 4; // spawning an avatar
1866 integer Animate = 5; // animation timer needed
1867 integer Walking = 6; // Hey! I am walking here!
1868 integer Wander = 7; // Wandering around neeeds a timer, too
1869 integer WanderHold = 8; // We reached a wander point
1870 integer DoProcess = 9; // Set this to make it process a new command
1871
1872 key gNpcKey = NULL_KEY; // global key storage for the one NPC, to save CPU cycles
1873 list Stack ; // a command stack from link message input
1874
1875 integer SensorFunc = 0; // define which function shall be triggered inside the sensor function
1876 // 0 means none, 1 sit, 2 touch
1877 ///////////////////////////////////////////////////////////////////////////
1878 // FUNCTIONS //
1879 ///////////////////////////////////////////////////////////////////////////
1880
1881 // Do* functions are much like states from the old V2 scripts.
1882
1883 // Save a Path notecard
1884 DoSave()
1885 {
1886 STATE = MakeNotecard;
1887 makeText("Stand where you want the NPC to appear, and enter the NPC Name");
1888 }
1889
1890 // This function is used to record the path for the NPC
1891 // Each command can take 0, 1, or 2 params
1892 DoMenuForCommands() {
1893 makeMenu(lAtButtons);
1894 }
1895
1896
1897 // No one is here when sensors were on, so we kill off the NPC
1898 DoNobodyHome()
1899 {
1900 DEBUG("Nobody Home");
1901 STATE = NobodyHome;
1902 if(NPCKey() != NULL_KEY) {
1903 osNpcRemove(NPCKey());
1904 SaveKey(NULL_KEY);
1905 }
1906 TimerEvent(5); // keep ticking to sense avatars
1907 }
1908
1909 // Create a NPC
1910 DoSpawn() {
1911 DEBUG("state spawn");
1912 NPCEnabled = TRUE; // in world
1913 // see if there is already one out there.
1914 if(NPCKey() != NULL_KEY) {
1915 DEBUG("Already living");
1916 return;
1917 }
1918
1919 STATE = Spawning;
1920
1921 list name = llParseString2List(sNPCName, [" "], []);
1922
1923 if(relAbs == "Relative"){
1924 vInitialPos += llGetPos();
1925 }
1926
1927 DEBUG("Rezzing the NPC:" + (string) vInitialPos);
1928 key aKey = osNpcCreate(llList2String(name, 0), llList2String(name, 1), vInitialPos, Appearance, NPCOptions);
1929
1930 SaveKey(aKey ); // save in desceription and global, too
1931
1932 osSetSpeed(aKey,SPEEDMULT); // 1.9 speed multiplier
1933 TimerEvent(REZTIME);
1934 NPCAnimate(STAND);
1935 }
1936
1937 DoRotate() {
1938 DEBUG("@rotate=" + (string) RAMrot);
1939 osNpcSetRot(NPCKey(), llEuler2Rot(<0,0,RAMrot> * DEG_TO_RAD));
1940 }
1941
1942 DoSit() {
1943 DEBUG ("state sit - looking for " + RAMsit);
1944 SensorFunc = 1; //triggers osNpcSit
1945 llSensor(RAMsit, "", PASSIVE|ACTIVE|SCRIPTED, 96, PI);
1946 }
1947
1948 DoTouch() {
1949 DEBUG ("state touch - looking for " + RAMtouch);
1950 SensorFunc = 2; //triggers osNpcTouch
1951 llSensor(RAMtouch, "", PASSIVE|ACTIVE|SCRIPTED, 96, PI);
1952 }
1953
1954 DoStand() {
1955 DEBUG("state stand");
1956 osNpcStand(NPCKey());
1957 }
1958
1959
1960 DoAnimate() {
1961
1962 DEBUG("state animate");
1963 STATE = Animate;
1964 NPCAnimate(RAManimationName);
1965 if(RAManimationTime <=0 ) // V 3.4 tweak
1966 RAManimationTime = 1;
1967 TimerEvent(RAManimationTime);
1968 }
1969
1970 DoWalk() {
1971
1972 DEBUG("NPCWalkOption = " + (string) NPCWalkOption);
1973 STATE = Walking;
1974
1975 // walk, fly, run, land
1976 if(walkstate == 1) {
1977 NPCAnimate(WALK);
1978 } else if(walkstate == 2) {
1979 llShout(FLIGHT,"flying");
1980 NPCAnimate(FLY);
1981 } else if(walkstate == 3) {
1982 NPCAnimate(RUN);
1983 } else if(walkstate == 4) {
1984 NPCAnimate(LAND);
1985 }
1986 newDest = vDestPos ;
1987 newDest.z += OffsetZ;
1988
1989 // notecard is stored as offsets from this box with relative addressing. Convert to absolute
1990 if(relAbs == "Relative"){
1991 newDest += llGetPos();
1992 }
1993
1994 DEBUG("Moveto:" + (string) newDest);
1995 osNpcMoveToTarget(NPCKey(), newDest, NPCWalkOption);
1996 iWaitCounter = WAIT; // wait 60 seconds to get to a destination.
1997 TimerEvent(TIMER);
1998 }
1999
2000
2001 DoWander(){
2002 DEBUG("state wander");
2003 STATE = Wander;
2004
2005 vector point = CirclePoint(RAMwd);
2006 DEBUG("CirclePoint:" + (string) point);
2007 vWanderPos = vDestPos + point;
2008 DEBUG("vWanderPos:" + (string) vWanderPos);
2009
2010 fTimerVal = WANDERTIME; // default time to pause after each wander
2011 if(WANDERRAND)
2012 fTimerVal = llFrand(WANDERTIME) + 1; // override, they want random times
2013
2014 NPCAnimate(WALK);
2015
2016 DEBUG("Wander to:" + (string) vWanderPos);
2017
2018 osNpcMoveToTarget(NPCKey(), vWanderPos, NPCWalkOption);
2019 iWaitCounter = WAIT; // wait 60 seconds to get to a destination.
2020 TimerEvent(TIMER);
2021 }
2022
2023 DoWanderhold() {
2024
2025 DEBUG("Wander Hold");
2026 STATE = WanderHold;
2027
2028 // now that we have reached a wander spot, slow the timer down to the desired value
2029 TimerEvent(fTimerVal);
2030 }
2031
2032 // @pause=10 will do nothing for 10 seconds
2033 DoPause() {
2034 if(RAMPause < 0.1)
2035 RAMPause = 0.1;
2036 DEBUG("@pause=" + (string)RAMPause);
2037 TimerEvent(RAMPause);
2038 }
2039
2040
2041 // @stop makes the NPC stop moving in whatever state it is in. You have to linkmessage to get moving again
2042 DoStop() {
2043 DEBUG("NPC is Stopped");
2044 Stopped = TRUE; // Link controlled - we mnust have a @go to continue with notecards
2045 TimerEvent(0);
2046 Stack = []; // v3.8
2047 }
2048
2049 // @delete removes the NPC forever. Next command starts it up again at the beginning
2050 DoDelete() {
2051 DEBUG("state delete");
2052
2053 osNpcRemove(NPCKey());
2054 SaveKey(NULL_KEY);
2055 Stopped = TRUE; // Link controlled - we mnust have a @go to continue with notecards
2056 TimerEvent(0);
2057 Stack = []; // v3.8
2058 }
2059
2060 // change the appearance of the NPC
2061 DoAppearance(string notecard) {
2062 DEBUG("state appearance");
2064 DEBUG("Load appearance " + notecard);
2065 osNpcLoadAppearance(NPCKey(),notecard);
2066 }
2067 }
2068
2069 // Change the avatar speed
2070 DoSpeed(string speed) {
2071 float newspeed = (float) speed;
2072 if(newspeed > 0.1 && newspeed < 5.0) // sanity check
2073 osSetSpeed(NPCKey(),newspeed);
2074 }
2075 DoNewNote (string card) {
2076 DEBUG("Load Notecard " + card);
2077 NPCReadNoteCard(card);
2078 Stopped = FALSE;
2079 }
2080 DoAttach(string params) {
2081
2082 list Data = llParseString2List(params, ["|"], []);
2083 string itemName = llList2String(Data, 0);
2084 integer attachmentPoint = (integer) llList2String(Data, 1);
2085 if(attachmentPoint > 0
2086 && attachmentPoint < 40
2088 )
2089 {
2090 osForceAttachToOtherAvatarFromInventory(NPCKey(),itemName,attachmentPoint);
2091 }
2092 }
2093
2094 // This loops over the notecard, processing each command
2095 DoProcessNPCLine() {
2096 DEBUG("ProcessNPCLine");
2097 STATE = 0;
2098
2099 // auto load a notecard
2100 if(! llGetListLength(lNPCScript)) {
2101 DEBUG("Read Notecard");
2102 NPCReadNoteCard(Notecard);
2103 Stopped = FALSE;
2104 }
2105
2106 // look for link messages on the stack
2107 string next = llList2String(Stack,0); // lets see if there is anithing from a link message
2108 if(llStringLength(next))
2109 {
2110 Stack = llDeleteSubList(Stack,0,0);
2111 ProcessCmd(next); //lets do this command instead.
2112 return;
2113 }
2114
2115 // @stop issued?
2116 if(Stopped) {
2117 TimerEvent(0);
2118 DEBUG("Waiting for input");
2119 return;
2120 }
2121
2122 // No, we have an @go for liftoff
2123 next = llList2String(lNPCScript, 0); // get the next command
2124 DEBUG("Execute:" + next);
2125 lNPCScript = llDeleteSubList(lNPCScript, 0, 0); // delete it
2126
2127 if(llGetListLength(lNPCScript) == 0) {
2128 DEBUG("EOF");
2129 }
2130 ProcessCmd(next);
2131
2132 }
2133
2134
2135
2136 ProcessCmd(string cmd) {
2137
2138 DEBUG("ProcessCmd:" + cmd);
2139
2140 if(llGetSubString(cmd, 0, 0) != "@") {
2141 DEBUG("ignoring");
2142 STATE = DoProcess;
2143 TimerEvent(QUICK); // this is so we do not recurse the stack
2144 return;
2145 }
2146
2147 list data = llParseString2List(cmd, ["="], []);
2148 npcAction = llToLower(llStringTrim(llList2String(data, 0), STRING_TRIM));
2149
2150 DEBUG("Action:" + npcAction);
2151 npcParams = llStringTrim(llList2String(data, 1), STRING_TRIM);
2152
2153 @commands;
2154
2155 ProcessSensor();
2156
2157
2158 if(npcAction == "@spawn") {
2159 DEBUG("@spawn");
2160 list spawnData = llParseString2List(npcParams, ["|"], []);
2161 sNPCName =llList2String(spawnData, 0); // V 1.6 name in RAM
2162
2163 list spawnDest = llParseString2List(llList2String(spawnData, 1), ["<", ",", ">"], []);
2164 vInitialPos.x = llList2Float(spawnDest, 0);
2165 vInitialPos.y = llList2Float(spawnDest, 1);
2166 vInitialPos.z = llList2Float(spawnDest, 2);
2167
2168 DEBUG("Coords for NPC at " + (string) vInitialPos);
2169 DoSpawn();
2170 return;
2171 }
2172
2173 if(! avatarPresent){
2174 DoNobodyHome();
2175 DEBUG("No avatar nearby");
2176 return;
2177 } else {
2178 if( NPCKey() == NULL_KEY) {
2179 DoSpawn();
2180 }
2181 }
2182
2183 if(npcAction == "@stop") {
2184 DoStop();
2185 return;
2186 }
2187 else if(npcAction == "@goto") {
2188 DEBUG("goto");
2189 integer lastIdx = llGetListLength(lNPCScript)-1;
2190 lNPCScript = llDeleteSubList(lNPCScript, lastIdx, lastIdx);
2191 // Wind commands till goto label.
2192 @wind;
2193 string next1 = llList2String(lNPCScript, 0);
2194 lNPCScript = llDeleteSubList(lNPCScript, 0, 0);
2195 lNPCScript += next1;
2196 if(next1 != npcParams) jump wind;
2197 // Wind the label too.
2198 next1 = llList2String(lNPCScript, 0);
2199 lNPCScript = llDeleteSubList(lNPCScript, 0, 0);
2200 lNPCScript += next1;
2201 // Get next command.
2202 list data1 = llParseString2List(next1, ["="], []);
2203 npcAction = llToLower(llStringTrim(llList2String(data1, 0), STRING_TRIM));
2204 npcParams = llStringTrim(llList2String(data1, 1), STRING_TRIM);
2205 // Reschedule.
2206 jump commands;
2207 }
2208 else if(npcAction == "@sound") {
2209 DEBUG("sound");
2210 llTriggerSound(npcParams,1.0);
2211 }
2212 else if(npcAction == "@randsound") {
2213 DEBUG("@randsound");
2215 integer rand = llCeil(llFrand(N)) -1; // pick a random sound
2217 llTriggerSound(toPlay,1.0);
2218 }
2219 else if(npcAction == "@walk") {
2220 DEBUG("@walk");
2221 GetDest(npcParams);
2222 walkstate = 1;// walking
2223 NPCWalkOption = OS_NPC_NO_FLY ;
2224 DoWalk();
2225 return;
2226 }
2227 else if(npcAction == "@fly") {
2228 GetDest(npcParams);
2229 walkstate = 2;// flying
2230 NPCWalkOption = OS_NPC_FLY ;
2231 DoWalk();
2232 return;
2233 }
2234 else if(npcAction == "@run") {
2235 DEBUG("@run");
2236 GetDest(npcParams);
2237 walkstate = 3;// running
2238 NPCWalkOption = OS_NPC_NO_FLY | OS_NPC_RUNNING;
2239 DoWalk();
2240 return;
2241 }
2242 else if(npcAction == "@land") {
2243 DEBUG("@land");
2244 GetDest(npcParams);
2245 walkstate = 4;// landing
2246 NPCWalkOption= OS_NPC_FLY | OS_NPC_LAND_AT_TARGET ;
2247 DoWalk();
2248 return;
2249 }
2250 else if(npcAction == "@say") {
2251 DEBUG("@say " + npcParams);
2252 osNpcSay(NPCKey(), 0, npcParams);
2253 }
2254 else if(npcAction == "@shout") {
2255 DEBUG("@shout");
2256 osNpcShout(NPCKey(),0, npcParams);
2257 }
2258 else if(npcAction == "@whisper") {
2259 DEBUG("@whisper " + npcParams);
2260 osNpcWhisper(NPCKey(),0, npcParams);
2261 }
2262 // speak a command on a channel, so you can open doors and control stuff.
2263 else if(npcAction == "@cmd") {
2264 DEBUG("@cmd");
2265 list dataToSpeak = llParseString2List(npcParams, ["|"], []);
2266 string channel = llList2String(dataToSpeak,0);
2267 DEBUG("Channel:"+(string) channel);
2268 integer iChannel = (integer) channel;
2269 string stringToSpeak = llList2String(dataToSpeak,1);
2270 llSay(iChannel, stringToSpeak);
2271 }
2272 // stop everything
2273 else if(npcAction == "@pause") {
2274 RAMPause = (float) npcParams;
2275 DoPause();
2276 return;
2277 }
2278 else if(npcAction == "@wander") {
2279 list wanderData = llParseString2List(npcParams, ["|"], []);
2280 RAMwd = (float) llList2String(wanderData, 0);
2281 RAMwc = (integer) llList2String(wanderData, 1);
2282 vDestPos = osNpcGetPos(NPCKey()); // set the wander start
2283 DEBUG("Starting at " + (string) vDestPos);
2284 DoWander();
2285 return;
2286 }
2287 else if(npcAction == "@rotate") {
2288 RAMrot = (float) npcParams;
2289 DoRotate();
2290 }
2291 else if(npcAction == "@sit") {
2292 RAMsit= npcParams;
2293 DoSit();
2294 return;
2295 }
2296 else if(npcAction == "@touch") {
2297 RAMtouch= npcParams;
2298 DoTouch();
2299 return;
2300 }
2301 else if(npcAction == "@stand") {
2302 DoStand();
2303 }
2304 else if(npcAction == "@delete") {
2305 DoDelete();
2306 return;
2307 }
2308 else if(npcAction == "@animate") {
2309 list animateData = llParseString2List(npcParams, ["|"], []);
2310 RAManimationName = llList2String(animateData, 0);
2311 RAManimationTime = (float) llList2String(animateData, 1);
2312 DoAnimate();
2313 return;
2314 }
2315 else if(npcAction == "@appearance" ){
2316 DoAppearance(npcParams);
2317 }
2318 else if(npcAction =="@speed") {
2319 DoSpeed(npcParams);
2320 }
2321 else if(npcAction =="@notecard") {
2322 DoNewNote(npcParams);
2323 Notecard = npcParams;
2324 }
2325 else if(npcAction == "@attach")
2326 {
2327 DoAttach(npcParams);
2328 }
2329
2330 STATE = DoProcess;
2331 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
2332 }
2333
2334
2335
2336 /////////////////// UTILITY Functions, not state-like //////////////////
2337
2338 // DEBUG(string) will chat a string or display it as hovertext if debug == TRUE
2339 DEBUG(string str) {
2340 if(debug)
2341 llOwnerSay( str); // Send the owner debug info so you can chase NPCS
2342 if(iTitleText) {
2343 llSetText(str,<1.0,1.0,1.0>,1.0); // show hovertext
2344
2345 }
2346 }
2347
2348 GetDest(string npcParams) {
2349 list dest = llParseString2List(npcParams, ["<", ",", ">"], []);
2350 vDestPos.x = llList2Float(dest, 0);
2351 vDestPos.y = llList2Float(dest, 1);
2352 vDestPos.z = llList2Float(dest, 2);
2353 }
2354
2355 NPCReadNoteCard(string Note) {
2356 DEBUG("NPCReadNoteCard");
2357 lNPCScript = llParseString2List(osGetNotecard(Note), ["\n"], []);
2358 }
2359
2360 integer SenseAvatar()
2361 {
2362 //Returns a strided list of the UUID, position, and name of each avatar in the region
2363 list avatars = llGetAgentList(AGENT_LIST_REGION ,[]);
2364 integer numOfAvatars = llGetListLength(avatars);
2365 if(numOfAvatars == 0)
2366 {
2367 DEBUG("No people");
2368 return 0;
2369 }
2370 //DEBUG("Located " + (string)numOfAvatars + " avatars and NPC's");
2371
2372 integer nAvatars;
2373 integer i;
2374 for( i = 0;i < numOfAvatars; i++) {
2375 key aviKey = llList2Key(avatars,i);
2376 if(!osIsNpc(aviKey)) {
2377 list detail = llGetObjectDetails(aviKey,[OBJECT_POS]);
2378 vector pos = llList2Vector(detail,0);
2379 float dist = llVecDist(pos, llGetPos());
2380 if(dist < RANGE)
2381 {
2382 nAvatars++;
2383 DEBUG("In range:" + llKey2Name(aviKey));
2384 }
2385 }
2386 }
2387 //DEBUG("Located " + (string) nAvatars + " avatars");
2388 return nAvatars;
2389 }
2390
2391 // 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.
2392 integer checkPerms() {
2393
2394 integer group = (integer) KeyValueGet("pr");
2395 if(! group)
2396 priPub = "Owner Only";
2397 else
2398 priPub = "Group";
2399
2400
2401 if(llDetectedKey(0) == llGetOwner()){
2402 kUserKey = llDetectedKey(0);
2403 return TRUE;
2404 }
2405
2406 if( group && llDetectedGroup(0)) {
2407 kUserKey = llDetectedKey(0);
2408 return TRUE;
2409 }
2410 kUserKey = llDetectedKey(0);
2411 return FALSE;
2412 }
2413
2414
2415
2416 NPCAnimate(string anim)
2417 {
2418 DEBUG("Start Anim: " + anim);
2420
2421 if(lastANIM != anim) {
2422 if(llStringLength(lastANIM)) {
2423 osNpcStopAnimation(NPCKey(), lastANIM);
2424 }
2425 osNpcPlayAnimation(NPCKey(), anim);
2426 lastANIM = anim;
2427 }
2428 } else {
2429 llSay(DEBUG_CHANNEL, "No animation named " + anim);
2430 }
2431 }
2432
2433
2434 TimerEvent(float timesent)
2435 {
2436 DEBUG("Setting timer: " + (string) timesent);
2437 llSetTimerEvent(timesent);
2438 }
2439
2440 // Kill a NPC by Name
2441 Kill(string param)
2442 {
2443 integer count;
2444 list avatars = osGetAvatarList(); // Returns a strided list of the UUID, position, and name of each avatar in the region except the owner.\
2445 integer i;
2446 integer j = llGetListLength(avatars);
2447 for (i=0 ; i <= j; i+=3){
2448
2449 string desired = llList2String(avatars,i+2);
2450 desired = llStringTrim(desired,STRING_TRIM); // should not be needed but is needed
2451
2452 if(desired == param){
2453 vector v = llList2Vector(avatars,i+1);
2454 key target = llList2Key(avatars,i); // get the UUID of the avatar
2455 osNpcRemove(target);
2456 SaveKey(NULL_KEY );
2457 llOwnerSay("Removed " + param+ " at location " + (string) v);
2458 count++;
2459 }
2460 }
2461
2462 NPCEnabled = FALSE; // not in world
2463
2464 if(count)
2465 llOwnerSay("Removed " + (string) count + " NPC's");
2466 else
2467 llOwnerSay("Could not locate " + param);
2468 }
2469
2470
2471 // return a String for the position we are at. Strings used as the caller wants strings
2472 string Pos()
2473 {
2474 vector where = llGetPos(); // find the box position
2475
2476 where.z += OffsetZ; // use the ground position + an offset
2477
2478 if(Editor)
2479 where = <128,128,31 + llFrand(1)>; // center of sim for editing
2480
2481 // if attached the height will be too high by 1/2 the agent size
2482 if(llGetAttached()) {
2484 float Z = size.z;
2485 where.z -= Z/2;
2486 }
2487
2488 // DEBUG("Pos= " + (string) where);
2489 return (string) where;
2490 }
2491
2492 // setup a menu with a timer for timeouts, called by all make*()
2493 menu()
2494 {
2495 llListenRemove(iHandle);
2496 iChannel = llCeil(llFrand(100000) + 20000);
2497 iHandle = llListen(iChannel,"","","");
2498 TimerEvent(30.0);
2499 MENU = TRUE;
2500 }
2501
2502 // make a text box
2503 makeText(string Param)
2504 {
2505 menu();
2506 llTextBox(kUserKey, Param, iChannel);
2507 }
2508
2509 // top level menu
2510 makeMainMenu()
2511 {
2512 menu();
2513 list buttons = ["Appearance","Recording","Save","Help","-","Erase RAM", priPub,relAbs,"-","Stop NPC",mSensor,"Start NPC"];
2514 llDialog(kUserKey,(string) llGetListLength(lCommands) + " Records",buttons,iChannel);
2515 }
2516
2517
2518 // Rev 1.4
2519 // top level menu for non group/ non owners
2520 makeUserMenu()
2521 {
2522 if(!allowUsers) return;
2523
2524 menu();
2525 list buttons = ["Start NPC","Stop NPC"];
2526 llDialog(kUserKey,"Choose",buttons,iChannel);
2527 }
2528
2529
2530
2531 // programmable menu for @commands
2532 makeMenu(list buttons)
2533 {
2534 menu();
2535 llDialog(kUserKey,(string) llGetListLength(lCommands) + " Record",buttons,iChannel);
2536 }
2537
2538
2539 // make one or two text boxes with prompts
2540 Text(string cmd, string p1, string p2)
2541 {
2542 sCommand = cmd;
2543 sParam2 = "";
2544 if(llStringLength(p2))
2545 sParam2 = p2;
2546
2547 makeText(p1);
2548 }
2549
2550 // Set the Avatar Present flag - if sensors are off and we are forece run, there will be one present.
2551 ProcessSensor()
2552 {
2553 integer SensorOn;
2554 if("on" == KeyValueGet("se"))
2555 {
2556 SensorOn = TRUE; // we need to scan for avatars
2557 } else {
2558 SensorOn = FALSE; // we need to scan for avatars
2559 }
2560 DEBUG("Sensor:" + (string) SensorOn);
2561
2562 integer n = SenseAvatar();
2563
2564 DEBUG("Avatars:" + (string) n);
2565 if(SensorOn && n)
2566 avatarPresent = TRUE; // someone is here and we need to tell the system to run
2567 else if(SensorOn && !n)
2568 avatarPresent = FALSE; // someone is not here and we need to tell the system to stop
2569 else { // sensor is off, lete see if there is a NPC. If so, we are ON
2570 DEBUG("NPCEnabled:" + (string) NPCEnabled);
2571 if(NPCEnabled)
2572 avatarPresent = TRUE;
2573 else
2574 avatarPresent = FALSE;
2575 }
2576
2577 // start up from when when no one is near
2578 if(avatarPresent && STATE == NobodyHome)
2579 STATE = 0;
2580
2581 //DEBUG("Avatar Present: " + (string) avatarPresent);
2582 }
2583
2584 vector CirclePoint(float radius) {
2585 float x = llFrand(radius *2) - radius; // +/- radius, randomized
2586 float y = llFrand(radius *2) - radius; // +/- radius, randomized
2587 return <x, y, 0>; // so this should always happen
2588 }
2589
2590 string KeyValueGet(string var) {
2591 list dVars = llParseString2List(llGetObjectDesc(), ["&"], []);
2592 do {
2593 list data = llParseString2List(llList2String(dVars, 0), ["="], []);
2594 string k = llList2String(data, 0);
2595 if(k != var) jump continue;
2596 //DEBUG("got " + var + " = " + llList2String(data, 1));
2597 return llList2String(data, 1);
2598 @continue;
2599 dVars = llDeleteSubList(dVars, 0, 0);
2600 } while(llGetListLength(dVars));
2601 return "";
2602 }
2603
2604 KeyValueSet(string var, string val) {
2605
2606 //DEBUG("set " + var + " = " + val);
2607 list dVars = llParseString2List(llGetObjectDesc(), ["&"], []);
2608 if(llGetListLength(dVars) == 0)
2609 {
2610 llSetObjectDesc(var + "=" + val);
2611 return;
2612 }
2613 list result = [];
2614 do {
2615 list data = llParseString2List(llList2String(dVars, 0), ["="], []);
2616 string k = llList2String(data, 0);
2617 if(k == "") jump continue;
2618 if(k == var && val == "") jump continue;
2619 if(k == var) {
2620 result += k + "=" + val;
2621 val = "";
2622 jump continue;
2623 }
2624 string v = llList2String(data, 1);
2625 if(v == "") jump continue;
2626 result += k + "=" + v;
2627 @continue;
2628 dVars = llDeleteSubList(dVars, 0, 0);
2629 } while(llGetListLength(dVars));
2630 if(val != "") result += var + "=" + val;
2632 }
2633
2634
2635 // clear RAM
2636 Clr() {
2637
2638 lCommands = [];
2639 llOwnerSay("RAM Memory cleared. Notecards, if any, are not modified.");
2640 makeMainMenu();
2641 }
2642
2643 integer checkNoteCards()
2644 {
2645 // Check that they have saved an Appeaance and Path notecard
2646 integer num = llGetInventoryNumber(INVENTORY_NOTECARD); // how many notecards overall
2647
2648 integer i;
2649 integer count;
2650 for (; i < num; i++){
2652 count++;
2654 count++;
2655 }
2656 DEBUG("Checked " + (string) count + " Notecards");
2657 // if we have both, run the NPC
2658 return count;
2659 }
2660
2661 Update(string SName) {
2662
2663 // delete all NPC*scripts except myself
2664 integer i;
2666 for (i = 0; i < j; i++) {
2668 string match = llGetSubString(name,0,2);
2669 if(match == SName && llGetScriptName() != name)
2670 {
2671 llRemoveInventory(name);
2672 llOwnerSay("Upgraded");
2673 }
2674 }
2675
2676 }
2677
2678 // Get all default saved params from the Description
2679 GetSwitches()
2680 {
2681 string rA = KeyValueGet("co"); // Get the remembered menu setting for Abs Vs Relative
2682 if(rA == "A")
2683 relAbs = "Absolute";
2684 else if(rA == "R")
2685 relAbs = "Relative";
2686 else
2687 relAbs = "Absolute";
2688
2689
2690 // reenable NPC if sensor is on.
2691 if("on" == KeyValueGet("se"))
2692 {
2693 NPCEnabled = TRUE;
2694 mSensor = "Sense is On";
2695 ProcessSensor(); // fake 1 avatar to get it rezzed
2696 } else {
2697 mSensor = "Sense is Off";
2698 }
2699 }
2700
2701
2702 SaveKey(key akey)
2703 {
2704 DEBUG("Saving Key of " + (string) akey);
2705 KeyValueSet("key", akey);
2706 if(akey != (key) KeyValueGet("key") )
2707 {
2708 DEBUG("Fatal error, cannot save key");
2709 }
2710 gNpcKey = akey;
2711 }
2712
2713
2714 key NPCKey()
2715 {
2716 key akey = gNpcKey; // from cached copy
2717 // gNpcKey saves a lot of CPU processing by caching the key, if blank we get it from the description
2718 if(gNpcKey == NULL_KEY)
2719 {
2720 //DEBUG("Get DKey");
2721 akey = KeyValueGet("key"); // from Description of the prim
2722 }
2723 // DEBUG("NPC KEY:" + (string) akey);
2724 return akey;
2725 }
2726
2727
2728 /////////////////// CODE BEGINS //////////////////
2729
2730
2731 default
2732 {
2733 changed(integer change) {
2734 if(change & CHANGED_REGION_START) {
2736 }
2737 }
2738
2739 on_rez(integer start_param)
2740 {
2742 }
2743
2744 state_entry() {
2745
2746 llSetText("",<1,1,1>,1.0); // clr all hovertext- we may not be using it.
2747 DoDelete(); // kill any NPC that is out running
2748 Update("NPC"); // If dragged and ropped into a prim with any script named "NPC...", this will replace it.
2749 GetSwitches(); // Get all default saved params from the Description
2750 llSetTimerEvent(TIMER);
2751 }
2752
2753
2755 { // if touched, make a menu
2756
2757 if(checkPerms()) {
2758 if(RecordPath == STATE) {
2759 makeMenu(lAtButtons);
2760 } else {
2761 makeMainMenu();
2762 }
2763 } else {
2764 makeUserMenu();
2765 }
2766 }
2767
2768 // menu listener
2769 listen(integer iChannel, string name, key id, string message) {
2770
2771 if(MENU) {
2772 llListenRemove(iHandle);
2773 MENU = 0; // menu is off
2774 iHandle = 0;
2775 }
2776
2777 if(message == "Stop NPC")
2778 {
2779 lNPCScript = []; // force reload of notecard
2780 NPCEnabled = FALSE;
2781 if(NPCKey() != NULL_KEY){
2782 Kill(sNPCName);
2783 sNPCName = "";
2784 } else {
2785 bNPC_STOP = TRUE;
2786 makeText("Enter name of an NPC to stop");
2787 }
2788 }
2789 else if(message == "Menu" ) {
2790 makeMainMenu();
2791 }
2792 else if(message == "Erase RAM"){
2793 Clr();
2794 }
2795 else if(message == "Relative"){
2796 relAbs = "Absolute";
2797 KeyValueSet("co","A"); // remember coordinates = A
2798 Clr();
2799 }
2800 else if(message == "Absolute"){
2801 relAbs = "Relative";
2802 KeyValueSet("co","R"); // remember coordinates = R
2803 Clr();
2804 }
2805 else if(message == "Recording"){
2806 DoMenuForCommands(); // show them the recording menu
2807 }
2808 else if(message == "Owner Only") {
2809 priPub = "Group";
2810 KeyValueSet("pr","1");
2811
2812 llOwnerSay("Group members have control");
2813 makeMainMenu();
2814 }
2815 else if(message == "Group") {
2816 priPub = "Owner Only";
2817 KeyValueSet("pr","0");
2818 llOwnerSay("Only you have control");
2819 makeMainMenu();
2820 }
2821 else if(message == "Sense is On") {
2822 mSensor ="Sense is Off";
2823 KeyValueSet("se", "off");
2824 llOwnerSay(mSensor);
2825 makeMainMenu();
2826 }
2827 else if(message == "Sense is Off") {
2828 mSensor ="Sense is On";
2829 llOwnerSay(mSensor);
2830 KeyValueSet("se", "on");
2831
2832 NPCEnabled = FALSE;
2833
2834 integer count = checkNoteCards();
2835 if(count >= 2) {
2836 DEBUG("Notecards approved , calling DoProcessNPCLine");
2837 DoProcessNPCLine();
2838 return;
2839 }
2840 if(Editor) {
2841 DoProcessNPCLine();
2842 return;
2843 }
2844
2845 llOwnerSay("You have not saved a recording and/or appearance, so you cannot start a NPC");
2846 makeMainMenu();
2847 }
2848 else if(message == "Appearance") {
2849 llRemoveInventory(Appearance); // delete the notecard
2850 osAgentSaveAppearance(kUserKey,Appearance); // make the ntecard
2851 llOwnerSay("Your outfit has been saved");
2852 makeMainMenu();
2853 }
2854 else if(message == "Save") {
2855 if(llGetListLength(lCommands) == 0) {
2856 llOwnerSay("Nothing recorded, you need to make a recording first");
2857 makeMainMenu();
2858 return;
2859 }
2860 DoSave();
2861 }
2862 else if(message == "Help"){
2863 llLoadURL(kUserKey,"Click to view help","http://www.outworldz.com/opensim/posts/NPC/");
2864 makeMainMenu();
2865 }
2866 else if(message == "Start NPC") {
2867 integer count = checkNoteCards();
2868 Stopped = FALSE; // Let's run the notecard
2869 NPCEnabled = TRUE;
2870
2871 if(Editor) {
2872 DoProcessNPCLine();
2873 return;
2874 }
2875
2876 if(count >= 2) {
2877 DEBUG("Notecards approved , calling DoProcessNPCLine");
2878 Stopped = FALSE; // Let's run the notecard
2879 DoProcessNPCLine();
2880 return;
2881 }
2882
2883 llOwnerSay("You have not saved a recording or maybe an appearance, so we cannot start a NPC");
2884
2885 }
2886 else if(bNPC_STOP){
2887 bNPC_STOP = FALSE;
2888 Kill(message);
2889 }
2890 else if(message == ">>"){
2891 makeMenu(lMenu2);
2892 }
2893 else if(message == ">>>"){
2894 makeMenu(lMenu3);
2895 }
2896 else if(message == "<<") {
2897 makeMenu(lAtButtons);
2898 }
2899 else if(message == "<<<") {
2900 makeMenu(lMenu2);
2901 }
2902 else if(message == "@comment"){
2903 Text("# ","Enter a comment","");
2904 }
2905 else if(message == "@stop"){
2906 lCommands += "@stop"+ "\n";
2907 makeMenu(lAtButtons);
2908 }
2909 else if(message == "@run"){
2910 lCommands += "@run=" + Pos() + "\n";
2911 llOwnerSay("Recorded position: " + Pos());
2912 makeMenu(lAtButtons);
2913 }
2914 else if(message == "@fly"){
2915 lCommands += "@fly=" + Pos() + "\n";
2916 llOwnerSay("Recorded position: " + Pos());
2917 makeMenu(lAtButtons);
2918 }
2919 else if(message == "@land"){
2920 lCommands += "@land=" + Pos() + "\n";
2921 llOwnerSay("Recorded position: " + Pos());
2922 makeMenu(lAtButtons);
2923 }
2924 else if(message == "@walk") {
2925 lCommands += "@walk=" + Pos() + "\n";
2926 llOwnerSay("Recorded position: " + Pos());
2927 makeMenu(lAtButtons);
2928 }
2929 else if(message == "@stop"){
2930 lCommands += "@stop"+ "\n";
2931 makeMenu(lAtButtons);
2932 }
2933 else if(message == "@sound"){
2934 Text("@sound=","Enter a sound name or UUID to trigger","");
2935 }
2936 else if(message == "@randsound"){
2937 lCommands += "@randsound"+ "\n";
2938 makeMenu(lAtButtons);
2939 }
2940 else if(message == "@say") {
2941 Text("@say=","Enter what the NPC will say","");
2942 }
2943 else if(message == "@whisper"){
2944 Text("@whisper=","Enter what the NPC will whisper","");
2945 }
2946 else if(message == "@shout"){
2947 Text("@shout=","Enter what the NPC will shout","");
2948 }
2949 else if(message == "@wander") {
2950 Text("@wander=","Enter radius to wander","Enter number of wanders");
2951 }
2952 else if(message == "@pause") {
2953 Text("@pause=","Enter time to pause","");
2954 }
2955 else if(message == "@rotate") {
2956 Text("@rotate=","Enter degrees to rotate","");
2957 }
2958 else if(message == "@sit"){
2959 Text("@sit=","Enter name of object to sit on","");
2960 }
2961 else if(message == "@touch"){
2962 Text("@touch=","Enter name of object to touch","");
2963 }
2964 else if(message == "@cmd"){
2965 Text("@cmd=","Enter cjhannel to speak on","Enter text to speak");
2966 }
2967 else if(message == "@stand"){
2968 lCommands += "@stand\n";
2969 llOwnerSay("Stand Recorded");
2970 makeMenu(lAtButtons);
2971 }
2972 else if(message == "@animate"){
2973 Text("@animate=","Enter animation name to play","Enter time to play the animation");
2974 }
2975 else if(message == "@attach"){
2976 Text("@animate=","Enter inventory name to attach","Enter number of the attachment point (1-40)");
2977 }
2978 else if(message == "@speed"){
2979 Text("@speed=","Enter a speed for the NPC, 1=100% normal speed, 0.5=50% speed","");
2980 }
2981
2982
2983 // Save NPC name
2984 else if(MakeNotecard == STATE) {
2985 sNPCName = message; // in case we need to kill it.
2986
2987 vector vDest = (vector) Pos();
2988
2989 if(relAbs == "Relative")
2990 {
2991 vDest -= llGetPos(); // just an offset for relative
2992 }
2993 sNotecard = "@spawn=" + message + "|" + (string) vDest + "\n";
2994 integer i;
2995 integer j = llGetListLength(lCommands);
2996 for (; i < j; i++){
2997 // get the command to save to the notecard
2998 string line = llList2String(lCommands,i);
2999 if(relAbs == "Absolute") {
3000 sNotecard += line; // add the un-modified string to the notecard
3001 } else {
3002 // since we have to record absolute coords since we do not know where the box goes until they press Save,
3003 // we process the absolute to relative conversion for walks here
3004 list parts = llParseString2List(line,["="],[]); //get the @command
3005
3006 if(llList2String(parts,0) == "@walk") {
3007 vector vec = (vector) llList2String(parts,1) - llGetPos();
3008 sNotecard += "@walk=" + (string) vec + "\n";
3009 }
3010 else if(llList2String(parts,0) == "@fly") {
3011 vector vec = (vector) llList2String(parts,1) - llGetPos();
3012 sNotecard += "@fly=" + (string) vec + "\n";
3013 }
3014 else if(llList2String(parts,0) == "@run") {
3015 vector vec = (vector) llList2String(parts,1) - llGetPos();
3016 sNotecard += "@run=" + (string) vec + "\n";
3017 }
3018 else if(llList2String(parts,0) == "@land") {
3019 vector vec = (vector) llList2String(parts,1) - llGetPos();
3020 sNotecard += "@land=" + (string) vec + "\n";
3021 }
3022 else {
3023 sNotecard += line; // add the un-modified string to the notecard
3024 }
3025 }
3026 }
3027 llRemoveInventory(Notecard); // delete the old notecard
3028 osMakeNotecard(Notecard,sNotecard); // Makes the notecard.
3029 llSay(0,sNotecard);
3030 llOwnerSay("Commands notecard has been written");
3031 STATE = 0;
3032 } // MakeNotecard
3033
3034 else if(! llStringLength(sParam2)) {
3035 lCommands += sCommand + message + "\n";
3036 llOwnerSay("Recorded");
3037 makeMenu(lAtButtons);
3038 }
3039 else if(llStringLength(sParam2)){
3040 sCommand = sCommand + message + "|";
3041 llOwnerSay("Recorded");
3042 makeText(sParam2);
3043 sParam2 = "";
3044 }
3045
3046 }
3047
3048
3049
3050 timer(){
3051 // DEBUG("tick");
3052
3053 // if llDialog is up, kill the listener for the dialog box.
3054 if(iHandle) {
3055 llOwnerSay("Menu timed out");
3056 llListenRemove(iHandle);
3057 iHandle = 0;
3058 return; // ^^^^^^^^^^^^^^^^^^^^^^^
3059 }
3060 // if NoBodyHome, we are sensing for an avatar
3061 else if(NobodyHome == STATE) {
3062 ProcessSensor();
3063 return;
3064 }
3065 // if we are spawning, we need time to rez the NPC, then start processing NPC Commands.
3066 else if(Spawning == STATE) {
3067 STATE = 0;
3068 TimerEvent(TIMER);
3069 }
3070 // We end aniamtions with a timer
3071 else if(Animate == STATE){
3072 NPCAnimate(STAND);
3073 TimerEvent(TIMER);
3074 }
3075
3076 else if(Walking == STATE) {
3077 if(--iWaitCounter) {
3078 if(llVecDist(osNpcGetPos(NPCKey()), newDest) > MAXDIST) {
3079 return;
3080 }
3081 }
3082
3083 DEBUG("At Destination: " + (string) newDest);
3084
3085 // walk, fly, run, land
3086 if(walkstate == 1) {
3087 NPCAnimate(STAND);
3088 NPCWalkOption = OS_NPC_NO_FLY;
3089 } else if(walkstate == 2) {
3090 // nothing
3091 } else if(walkstate == 3) {
3092 NPCAnimate(STAND);
3093 NPCWalkOption = OS_NPC_NO_FLY;
3094 } else if(walkstate == 4) {
3095 llShout(FLIGHT,"landing");
3096 NPCAnimate(STAND);
3097 NPCWalkOption = OS_NPC_NO_FLY;
3098 }
3099 }
3100 // Wandering timer
3101 else if(Wander == STATE) {
3102 if(--iWaitCounter) { // wait 60 seconds to get to a destination.
3103 if(llVecDist(osNpcGetPos(NPCKey()), vWanderPos) > MAXDIST)
3104 return;
3105 }
3106
3107 // see if wander counter == 0, if so, stop walking, go to stand and process next line
3108 if(RAMwc == 0) {
3109 NPCAnimate(STAND);
3110 DEBUG("Wander ended, calling DoProcessNPCLine");
3111 STATE = 0;
3112 DoProcessNPCLine();
3113 return;
3114 }
3115 // one less time to wander around
3116 RAMwc--;
3117 NPCAnimate(STAND);
3118 TimerEvent(TIMER);
3119 DoWanderhold();
3120 return;
3121 }
3122 // Wandering requires us to re-wander when we reach a destination
3123 else if(WanderHold == STATE) {
3124 DoWander();
3125 TimerEvent(TIMER);
3126 return;
3127 }
3128 else if(DoProcess == STATE) {
3129 TimerEvent(QUICK);
3130 }
3131
3132 STATE = 0;
3133
3134 // We always process a NPC line at end of timer.
3135 DEBUG("Tick end, calling DoProcessNPCLine");
3136 DoProcessNPCLine();
3137 }
3138
3139 // sensors are used for sitting on prims
3140 // Neo Cortex: added different SensorFunc states to trigger sit or touch
3141 sensor(integer num) {
3142 if(SensorFunc == 1) {
3143 osNpcSit(NPCKey(), llDetectedKey(0), OS_NPC_SIT_NOW);
3144 DEBUG("Seated, calling DoProcessNPCLine");
3145 SensorFunc = 0;
3146 } else if(SensorFunc == 2) {
3147 osNpcTouch(NPCKey(), llDetectedKey(0), LINK_THIS);
3148 DEBUG("Touched, calling DoProcessNPCLine");
3149 SensorFunc = 0;
3150 }
3151 DoProcessNPCLine();
3152 }
3153 no_sensor(){
3154 DEBUG ("no target prim located, calling DoProcessNPCLine");
3155 SensorFunc = 0;
3156 DoProcessNPCLine();
3157 }
3158
3159
3160 link_message(integer sender, integer num, string str, key id){
3161 DEBUG("Command In:" + str);
3162 if(str=="@go") {
3163 Stopped = FALSE; // Let's run the notecard
3164 DEBUG("@go approved, calling DoProcessNPCLine");
3165 DoProcessNPCLine();
3166 } else {
3167 Stack += [str]; // take anything, the controller will filter away non @ stuff
3168 if(llGetListLength(Stack) == 1) { // 3.8
3169 DEBUG("Stack=1");
3170 DoProcessNPCLine();
3171 }
3172 }
3173 }
3174
3175 }

Hypergrid Story Three

Sample collision script for NPC animator

Category: NPC
By : Ferd Frederix
Created: 2015-11-24 Edited: 2015-11-23
Worlds: Second Life


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 integer debug = FALSE;
4
5
6 DoIt()
7 {
8 llMessageLinked(2,1, "@animate=avatar_type","");
9 llMessageLinked(2,5, "@say=Be wary of the water, for it is deadly","");
10 }
11
12 Reset()
13 {
15 llSetStatus(STATUS_PHANTOM, FALSE);
16 llSleep(0.1);
18 }
19
20 default
21 {
23 {
24 llSetText("",<1,1,1>,1.0);
25 llSetTimerEvent(3600);
26 Reset();
27 }
28
30
31 if(debug) llOwnerSay("Collided with " + llKey2Name(llDetectedKey(0)));
32
34 {
35 if(debug) llOwnerSay("Collided with " + llKey2Name(llDetectedKey(0)));
36
37 DoIt();
38
39 }
40
41 }
42
43 timer()
44 {
45 Reset();
46 llSetTimerEvent(3600);
47 }
48
50 {
52 }
53
54 changed(integer what)
55 {
56 if(what & CHANGED_REGION_START)
57 {
59 }
60 }
61 }

Hypergrid Story Three

Sample collision script for NPC animator

Category: NPC
By : Ferd Frederix
Created: 2015-11-24 Edited: 2015-11-23
Worlds: Second Life


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 integer debug = FALSE;
4
5 DoIt()
6 {
7 if(debug) llOwnerSay("Raccoon at bottom");
8 llMessageLinked(LINK_SET,0, "@walk=<79.46262, 39.34087, 21.64303>","");
9 }
10
11
12 Reset()
13 {
14 llSetStatus(STATUS_PHANTOM, FALSE);
16 llSleep(0.1);
18 }
19
20
21
22 default
23 {
25 {
26 llSetText("",<1,1,1>,1.0);
27 Reset();
28 llSetTimerEvent(3600);
29 }
30
32
34 {
35 if(debug) llOwnerSay("Collided with " + llKey2Name(llDetectedKey(0)));
36
37 DoIt();
38 }
39
40 }
41 timer()
42 {
43 llSetTimerEvent(3600);
44 Reset();
45 }
47 {
49 }
51 {
52 DoIt();
53 }
54
55 changed(integer what)
56 {
57 if(what & CHANGED_REGION_START)
58 {
60 }
61 }
62 }

Hypergrid Story Three

Notecard for sample NPC Sequence

Category: NPC
By : Ferd Frederix
Created: 2015-11-24 Edited: 2015-11-23
Worlds: Second Life


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 @spawn="Trusty" Racoon|<82.40501, 60.73487, 21.49010>
2 @stop
3 @pause=10
4 @walk=<82.40501, 47.29763, 21.49010>
5 @walk=<82.40501, 49.19700, 21.49010>
6 @pause=30
7 @say=Follow me. I see a shack that could have the magic potions you need for your friends.
8 @walk=<79.46262, 39.34087, 21.64303>
9 @stop
10

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