aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/NPC_tool.lsl
diff options
context:
space:
mode:
authorDavid Walter Seikel2014-03-02 22:34:54 +1000
committerDavid Walter Seikel2014-03-02 22:34:54 +1000
commit9c9a333860debf85b403a79bd6ee93b4f6bc5ded (patch)
treea04fe89e2467782129dc6aba7b82d7e8cf57e97a /NPC_tool.lsl
parentInitial commit (diff)
downloadNPC-tool-9c9a333860debf85b403a79bd6ee93b4f6bc5ded.zip
NPC-tool-9c9a333860debf85b403a79bd6ee93b4f6bc5ded.tar.gz
NPC-tool-9c9a333860debf85b403a79bd6ee93b4f6bc5ded.tar.bz2
NPC-tool-9c9a333860debf85b403a79bd6ee93b4f6bc5ded.tar.xz
Initial import of everything.
Diffstat (limited to 'NPC_tool.lsl')
-rw-r--r--NPC_tool.lsl1291
1 files changed, 1291 insertions, 0 deletions
diff --git a/NPC_tool.lsl b/NPC_tool.lsl
new file mode 100644
index 0000000..29dfd67
--- /dev/null
+++ b/NPC_tool.lsl
@@ -0,0 +1,1291 @@
1// Onefang's general purpose NPC tool version 1.0.
2// Requires onefang's utilities script.
3
4// Copyright (C) 2013 David Seikel (onefang rejected).
5//
6// Permission is hereby granted, free of charge, to any person obtaining a copy
7// of this software and associated documentation files (the "Software"), to
8// deal in the Software without restriction, including without limitation the
9// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10// sell copies of the Software, and to permit persons to whom the Software is
11// furnished to do so, subject to the following conditions:
12//
13// The above copyright notice and this permission notice shall be included in
14// all copies of the Software and its Copyright notices. In addition publicly
15// documented acknowledgment must be given that this software has been used if no
16// source code of this software is made available publicly. This includes
17// acknowledgments in either Copyright notices, Manuals, Publicity and Marketing
18// documents or any documentation provided with any product containing this
19// software. This License does not apply to any software that links to the
20// libraries provided by this software (statically or dynamically), but only to
21// the software provided.
22//
23// Please see the COPYING-PLAIN for a plain-english explanation of this notice
24// and it's intent.
25//
26// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
27// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
28// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
29// THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
30// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
31// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32
33// This is bad listening for commands on channel 0.
34integer commandChannel = 0;
35
36list attention = []; // npc key or script number, listen handle, list of attentive NPCs
37integer ATTENT_KEY = 0;
38integer ATTENT_HANDLE = 1;
39integer ATTENT_NPCS = 2;
40integer ATTENT_STRIDE = 3;
41
42list chatters = []; // npc key, chat type, listen handle. In channel order.
43integer CHAT_KEY = 0;
44integer CHAT_TYPE = 1;
45integer CHAT_HANDLE = 2;
46integer CHAT_STRIDE = 3;
47
48list followers = []; // npc key, stalkee key, distance vector
49integer FOLLOW_KEY = 0;
50integer FOLLOW_STALK = 1;
51integer FOLLOW_DIST = 2;
52integer FOLLOW_STRIDE = 3;
53
54list movers = []; // npc key, destination position, least distance, timestamp
55integer MOVE_KEY = 0;
56integer MOVE_DEST = 1;
57integer MOVE_LEAST = 2;
58integer MOVE_TIME = 3;
59integer MOVE_STRIDE = 4;
60
61float scriptTick = 0.5;
62float lastTick = -1.0;
63list scripts = []; // user key, script card name, flags, command list LIST_SEP separated
64integer SCRIPTS_KEY = 0;
65integer SCRIPTS_NAME = 1;
66integer SCRIPTS_FLAGS = 2;
67integer SCRIPTS_COMMANDS = 3;
68integer SCRIPTS_STRIDE = 4;
69
70// Script flags.
71integer SCRIPT_READING = 1;
72integer SCRIPT_ATTENTION = 2;
73
74list sensorRequests = []; // name to search for, type of search, command, user key, npc key
75integer sensorInFlight = FALSE;
76
77integer recording = FALSE;
78list record = []; // recorded commands
79
80float restartTimer = -1.0;
81
82// NPC commands.
83string NPC_ANIMATE = "animate"; // NPC name, animation name
84string NPC_ATTENTION= "attention"; // NPC name.
85string NPC_CHANGE = "change"; // NPC name, NPC (notecard) name.
86string NPC_CLONE = "clone"; // Avatar's name.
87string NPC_COME = "come"; // NPC name.
88string NPC_COMPLETE = "complete"; // NPC name.
89string NPC_CREATE = "create"; // NPC (notecard) name, (optional) position vector.
90string NPC_DELETE = "delete"; // NPC name.
91string NPC_DISMISSED= "dismissed"; // NPC name.
92string NPC_FOLLOW = "follow"; // NPC name, (optional) distance (float or vector).
93string NPC_FLY = "fly"; // NPC name, position vector or object/agent name/key.
94string NPC_GO = "go"; // NPC name, position vector or object/agent name/key.
95string NPC_LAND = "land"; // NPC name, position vector or object/agent name/key.
96string NPC_LINK = "link"; // Link number, number, string, (optional) key
97string NPC_LISTEN = "listen"; // new command channel
98string NPC_LOCATE = "locate"; // NPC name
99string NPC_NUKE = "nuke"; // No arguments.
100string NPC_ROTATE = "rotate"; // NPC name, Z rotation in degrees.
101string NPC_SAY = "say"; // NPC name, thing to say, or relay channel.
102string NPC_SCRIPT = "script"; // Script notecard name.
103string NPC_SHOUT = "shout"; // NPC name, thing to shout, or relay channel.
104string NPC_SIT = "sit"; // NPC name, object to sit on.
105string NPC_SLEEP = "sleep"; // Seconds.
106string NPC_STALK = "stalk"; // NPC name, avatar name, (optional) distance (float or vector).
107string NPC_STAND = "stand"; // NPC name.
108string NPC_STOP = "stop"; // NPC name.
109string NPC_STOPANIM = "stopanim"; // NPC name, animation name
110string NPC_TOUCH = "touch"; // NPC name, object to touch.
111string NPC_WHISPER = "whisper"; // NPC name, thing to whisper, or relay channel.
112
113string NPC_MAIN = "Main";
114string NPC_PICK = "Pick";
115string NPC_NPC = "NPC";
116string NPC_RELAY = "Relay";
117
118// LSL allows setting variables at this level to the values of previously declared variables.
119// But the OpenSim script engine developer had other ideas.
120// Otherwise we would not need this duplication.
121list OUR_COMMANDS = ["0", "", "", ""
122 + "animate|LA|"
123 + "attention|L|"
124 + "change|LC.avatar|"
125 + "clone|L|"
126 + "come|L|"
127// "complete|L|" a script only command handled outside the usual system.
128 + "create|Nv|" // Yes first argument should be C, but we want an NPC name anyway.
129 + "delete|L|"
130 + "dismissed|L|"
131 + "follow|Ls|" // Just ask for a string second argument, sort them out when they get here.
132 + "fly|Ls|" // Just ask for a string second argument, sort them out when they get here.
133 + "go|Ls|" // Just ask for a string second argument, sort them out when they get here.
134 + "land|Ls|" // Just ask for a string second argument, sort them out when they get here.
135 + "link|IISk|" // TODO - the system can't handle that k on the end yet.
136 + "listen|I|"
137 + "locate|L|"
138 + "nuke||"
139 + "rotate|LF|"
140 + "say|Ls|" // Just ask for a string second argument, sort them out when they get here.
141 + "script|X.npc|"
142 + "shout|Ls|" // Just ask for a string second argument, sort them out when they get here.
143 + "sit|Ls|"
144 + "sleep|F|"
145 + "stalk|LLs|" // Just ask for a string second argument, sort them out when they get here.
146 + "stand|L|"
147 + "stop|L|"
148 + "stopanim|LA|"
149 + "touch|LS|"
150 + "whisper|Ls|" // Just ask for a string second argument, sort them out when they get here.
151 ];
152
153// When sending multiple commands, some don't need to be expanded.
154list OUR_COMMANDS_NONAMES = ["attention", "create", "link", "listen", "nuke", "script", "sleep"];
155
156integer IS_NPC;
157integer IS_BOTH;
158integer OBJECT;
159
160vector STALK_DISTANCE = <-3.0, 0.0, 0.0>;
161integer SIM_CHANNEL = -65767365;
162
163string NPC_NPC_EXT = ".avatar";
164string NPC_SCRIPT_EXT = ".npc";
165string NPC_BACKUP_CARD = "Restore";
166string NPC_RECORD_CARD = "Recorded";
167string NPC_TEMP_CARD = "Temporary";
168
169integer NPC_RECORD = -100;
170integer NPC_RECORD_STOP = -101;
171integer NPC_NEW_NPC = -102;
172integer NPC_ADD_CHATTER = -103;
173integer NPC_DEL_CHATTER = -104;
174
175
176// Stuff for onefangs common utilities.
177key scriptKey;
178
179string UTILITIES_SCRIPT_NAME = "onefang's utilities";
180string LIST_SEP = "$!#"; // Used to seperate lists when sending them as strings.
181integer UTILITIES_RESET = -1;
182integer UTILITIES_RESET_DONE = -2;
183integer UTILITIES_READ = -3;
184integer UTILITIES_READ_DONE = -4;
185integer UTILITIES_SUBSTITUTE = -5;
186integer UTILITIES_SUBSTITUTE_DONE = -6;
187integer UTILITIES_NEXT_WORD = -7;
188integer UTILITIES_NEXT_WORD_DONE = -8;
189integer UTILITIES_PAYMENT_MADE = -9;
190integer UTILITIES_PAYMENT_MADE_DONE = -10;
191integer UTILITIES_MENU = -11;
192integer UTILITIES_MENU_DONE = -12;
193integer UTILITIES_WARP = -13;
194integer UTILITIES_WARP_DONE = -14;
195integer UTILITIES_AVATAR_KEY = -15;
196integer UTILITIES_AVATAR_KEY_DONE = -16;
197integer UTILITIES_CHAT = -17;
198integer UTILITIES_CHAT_DONE = -18;
199integer UTILITIES_CHAT_FAKE = -19;
200integer UTILITIES_CHAT_FAKE_DONE = -20;
201
202
203integer checkChat(key npc, string message, integer type, key newNpc)
204{
205 // Check if it's a single word.
206 if (-1 == llSubStringIndex(message, " "))
207 {
208 integer channel = (integer) message;
209
210 // Check if it's a sane number.
211 if ((message == (string) channel) && (channel < 12))
212 {
213 integer old = llListFindList(chatters, [npc]);
214
215 if (NULL_KEY != newNpc)
216 chatters = llListReplaceList(chatters, [newNpc], old, old);
217 else
218 {
219 // We are using llList2String here, coz the above find might return -1, and only llList2String can handle that.
220 key oldNpc = (key) llList2String(chatters, old + CHAT_KEY);
221 integer oldType = (integer) llList2String(chatters, old + CHAT_TYPE);
222 integer handle = (integer) llList2String(chatters, old + CHAT_HANDLE);
223
224 // No matter what, remove the old one for this NPC.
225 if (-1 != old)
226 {
227 llListenRemove(handle);
228 chatters = llListReplaceList(chatters, ["", 0, 0], old, old + CHAT_STRIDE - 1);
229 }
230 if (0 < channel)
231 {
232 handle = (integer) llList2String(chatters, (channel * CHAT_STRIDE) + CHAT_HANDLE);
233 if (handle)
234 llListenRemove(handle);
235 handle = llListen(channel, "", NULL_KEY, "");
236 chatters = llListReplaceList(chatters, [npc, type, handle], channel * CHAT_STRIDE, (channel * CHAT_STRIDE) + CHAT_STRIDE - 1);
237 llMessageLinked(LINK_SET, NPC_ADD_CHATTER, npc + "|" + (string) channel, scriptKey);
238 }
239 else
240 llMessageLinked(LINK_SET, NPC_DEL_CHATTER, npc, scriptKey);
241 }
242 return TRUE;
243 }
244 }
245
246 if (NULL_KEY != npc)
247 {
248 string command = "#";
249
250 if (0 == type) {command = "whisper"; osNpcWhisper(npc, 0, message);}
251 else if (1 == type) {command = "say"; osNpcSay(npc, message);}
252 else if (2 == type) {command = "shout"; osNpcShout(npc, 0, message);}
253 if (recording)
254 recordIt(command, [llKey2Name(npc), message]);
255 }
256 return FALSE;
257}
258
259startSensor(key user, key npc, string name, integer type, string command)
260{
261 sensorRequests += [name, type, command, user, npc];
262 nextSensor();
263}
264
265nextSensor()
266{
267 if (sensorInFlight)
268 return;
269 if (0 < llGetListLength(sensorRequests))
270 {
271 string name = llList2String(sensorRequests, 0);
272 integer type = llList2Integer(sensorRequests, 1);
273 string command = llList2String(sensorRequests, 2);
274 key user = llList2String(sensorRequests, 3);
275 key npc = llList2String(sensorRequests, 4);
276
277 sensorInFlight = TRUE;
278 llSensor(name, "", type, 1024.0, TWO_PI);
279 }
280}
281
282integer newScript(key user, string name)
283{
284 integer i;
285 integer length = llGetListLength(scripts);
286
287 if (NULL_KEY == llGetInventoryKey(name))
288 {
289 llSay(0, "No such script notecard - " + name);
290 return -1;
291 }
292 // Scan one past the end, so that if there's no free ones, we stick it at the end.
293 for (i = 0; i <= length; i += SCRIPTS_STRIDE)
294 {
295 if ("" == llList2String(scripts, i + SCRIPTS_KEY))
296 {
297 scripts = llListReplaceList(scripts, [user, name, SCRIPT_READING, ""], i, i + SCRIPTS_STRIDE - 1);
298 llMessageLinked(LINK_SET, UTILITIES_READ, name, scriptKey + "|" + (string) i);
299 // A break command is a wonderful thing LL.
300 return i;
301 }
302 }
303 // Should never get here, but just in case, try to trigger an error later on.
304 return -1;
305}
306
307recordIt(string command, list arguments)
308{
309 integer length = llGetListLength(arguments);
310 integer i;
311 float now = llGetTime();
312
313 // Record pauses between commands, but not if it's less than the script tick time, no point.
314 if ((0.0 < lastTick) && ((now - lastTick) > scriptTick))
315 record += ["sleep " + (string) (now - lastTick)];
316 lastTick = now;
317 for (i = 0; i < length; ++i)
318 {
319 string arg = llList2String(arguments, i);
320
321 if (osIsUUID(arg))
322 arg = llKey2Name(arg);
323 command += " " + arg;
324 }
325 record += [command];
326}
327
328key string2key(string name, integer type)
329{
330 if (osIsUUID(name))
331 return (key) name;
332 else if (AGENT == type)
333 {
334 list names = llParseString2List(name, [" "], []);
335 key uuid = osAvatarName2Key(llList2String(names, 0), llList2String(names, 1));
336
337 return uuid;
338 }
339 else if ((IS_BOTH == type) || (IS_NPC == type)) // osAvatarName2Key() does not work on NPCs, so we gotta scan the entire sim.
340 {
341 // osGetAvatarList() skips the owner, so we need to add it..
342 list avatars = [llGetOwner(), ZERO_VECTOR, llKey2Name(llGetOwner())] + osGetAvatarList(); // Strided list, UUID, position, name.
343 integer length = llGetListLength(avatars);
344 integer i;
345
346 for (i = 0; i < length; i +=3)
347 {
348 key this = (key) llList2String(avatars, i);
349 string thisName = llList2String(avatars, i + 2);
350
351 if ((thisName == name) && (osIsNpc(this) || (IS_BOTH == type)))
352 return this;
353 }
354 }
355 else if (llGetObjectName() == name)
356 return llGetKey();
357
358 return NULL_KEY;
359}
360
361vector string2pos(string name)
362{
363 key uuid = NULL_KEY;
364
365 if (("<" == llGetSubString(name, 0, 0)) && (">" == llGetSubString(name, -1, -1)))
366 {
367 // Looks like a vector, cast it.
368 return (vector) name;
369 }
370
371 uuid = string2key(name, IS_BOTH);
372 if (NULL_KEY != uuid)
373 return (vector) llList2String(llGetObjectDetails(uuid, [OBJECT_POS]), 0);
374
375 return ZERO_VECTOR;
376}
377
378integer goThere(key user, string name, string dest, string type)
379{
380 key npc = string2key(name, IS_NPC);
381 integer executed = FALSE;
382
383 if (NULL_KEY != npc)
384 {
385 vector pos = string2pos(dest);
386
387 if (ZERO_VECTOR == pos)
388 startSensor(user, npc, dest, ACTIVE | PASSIVE, type);
389 else
390 {
391 integer found = llListFindList(movers, [npc]);
392 integer method = OS_NPC_FLY | OS_NPC_LAND_AT_TARGET;
393 list this = [npc, pos, llVecMag((vector) llList2String(llGetObjectDetails(npc, [OBJECT_POS]), 0) - pos), llGetTime()];
394
395 if ((0 <= found) && ((found % MOVE_STRIDE) == 0))
396 llListReplaceList(movers, this, found, found + MOVE_STRIDE - 1);
397 else
398 movers += this;
399
400 if (NPC_GO == type)
401 method = OS_NPC_NO_FLY;
402 else if (NPC_FLY == type)
403 method = OS_NPC_FLY;
404 else if (NPC_LAND == type)
405 method = OS_NPC_FLY | OS_NPC_LAND_AT_TARGET;
406 // Telling a sitting NPC to move results in an error, so tell them to stand up, just in case.
407 osNpcStand(npc);
408 osNpcMoveToTarget(npc, pos, method);
409 executed = TRUE;
410 }
411 }
412 return executed;
413}
414
415killNPC(key npc, key newNpc)
416{
417 integer found = llListFindList(followers, [npc]);
418 integer length = llGetListLength(attention);
419
420 osNpcRemove(npc);
421 // Stop this attention whore from chatting, moving, and stalking.
422 checkChat(npc, "0", 0, newNpc);
423
424 while (0 <= found)
425 {
426 if ((found % FOLLOW_STRIDE) == FOLLOW_KEY) // Change of stalker.
427 {
428 if (NULL_KEY != newNpc)
429 followers = llListReplaceList(followers, [newNpc], found, found);
430 else
431 followers = llDeleteSubList(followers, found, found + FOLLOW_STRIDE - 1);
432 }
433 else if ((found % FOLLOW_STRIDE) == FOLLOW_STALK) // Change of stalkee.
434 {
435 if (NULL_KEY != newNpc)
436 followers = llListReplaceList(followers, [newNpc], found, found);
437 else
438 followers = llDeleteSubList(followers, found - FOLLOW_STALK, found - FOLLOW_STALK + FOLLOW_STRIDE - 1);
439 }
440 found = llListFindList(followers, [npc]);
441 }
442 found = llListFindList(movers, [npc]);
443 if ((0 <= found) && ((found % MOVE_STRIDE) == 0))
444 {
445 // The only user of newNpc wants even the new one gone from the movers list.
446 // But we do this anyway, coz it will get confused, and might need it later.
447 if (NULL_KEY != newNpc)
448 movers = llListReplaceList(movers, [newNpc], found, found);
449 else
450 movers = llDeleteSubList(movers, found, found + MOVE_STRIDE - 1);
451 }
452 // Search the attention lists to and remove/replace them from that.
453 for (found = 0; found < length; found += ATTENT_STRIDE)
454 delAttention(llList2String(attention, found + ATTENT_KEY), npc, newNpc);
455}
456
457addAttention(key user, key npc)
458{
459 list npcs = [];
460 integer handle = 0;
461 integer isUser = FALSE;
462 integer found = llListFindList(attention, [(string) user]);
463
464 // TODO - This should not be happening, I think, but it does.
465 if (NULL_KEY == npc)
466 return;
467
468 if (osIsUUID(user))
469 isUser = TRUE;
470
471 if (0 != (found % ATTENT_STRIDE))
472 found = -1;
473
474 if (-1 == found)
475 {
476 if (isUser)
477 handle = llListen(commandChannel, llKey2Name(user), user, "");
478 }
479 else
480 {
481 handle = llList2Integer(attention, found + ATTENT_HANDLE);
482 npcs = llParseString2List(llList2String(attention, found + ATTENT_NPCS), ["|"], []);
483 attention = llDeleteSubList(attention, found, found + ATTENT_STRIDE - 1);
484 found = llListFindList(npcs, [(string) npc]);
485 if (-1 != found)
486 npcs = llDeleteSubList(npcs, found, found);
487 }
488 attention += [user, handle, llDumpList2String(npcs + [npc], "|")];
489 if (isUser)
490 llSay(0, llKey2Name(npc) + " pays attention to " + llKey2Name(user));
491}
492
493// Replaces instead of deletes if newNpc is not NULL.
494// Deletes them all if npc is NULL.
495delAttention(key user, key npc, key newNpc)
496{
497 list npcs = [];
498 integer handle;
499 integer isUser = FALSE;
500 integer found = llListFindList(attention, [(string) user]);
501
502 if (osIsUUID(user))
503 isUser = TRUE;
504 if (0 != (found % ATTENT_STRIDE))
505 found = -1;
506
507 if (-1 != found)
508 {
509 handle = llList2Integer(attention, found + ATTENT_HANDLE);
510 npcs = llParseString2List(llList2String(attention, found + ATTENT_NPCS), ["|"], []);
511 attention = llDeleteSubList(attention, found, found + ATTENT_STRIDE - 1);
512 if (NULL_KEY == npc)
513 npcs = [];
514 else
515 {
516 found = llListFindList(npcs, [(string) npc]);
517 if (-1 != found)
518 {
519 if (NULL_KEY != newNpc)
520 npcs = llListReplaceList(npcs, [newNpc], found, found);
521 else
522 npcs = llDeleteSubList(npcs, found, found);
523 }
524 }
525 if (0 == llGetListLength(npcs))
526 llListenRemove(handle);
527 else
528 attention += [user, handle, llDumpList2String(npcs, "|")];
529 if (isUser && (NULL_KEY == newNpc))
530 llSay(0, llKey2Name(npc) + " ignores " + llKey2Name(user));
531 }
532}
533
534sendManyCommands(key user, string command, key index)
535{
536 list npcs = [];
537 integer handle;
538 integer found = llListFindList(attention, [(string) user]);
539
540 if (user != index)
541 found = llListFindList(attention, [(string) index]);
542 if (0 != (found % ATTENT_STRIDE))
543 found = -1;
544
545 if (-1 != found)
546 {
547 integer length;
548 integer i;
549 string thisCommand;
550 string rest = "";
551
552 handle = llList2Integer(attention, found + ATTENT_HANDLE);
553 npcs = llParseString2List(llList2String(attention, found + ATTENT_NPCS), ["|"], []);
554 length = llGetListLength(npcs);
555 // Split the command on the first space, if there is one.
556 found = llSubStringIndex(command, " ");
557 thisCommand = llGetSubString(command, 0, found);
558 if (-1 != found)
559 {
560 thisCommand = llGetSubString(command, 0, found - 1);
561 rest = " " + llGetSubString(command, found, -1);
562 }
563 else
564 thisCommand = command;
565// TODO - the original command will go through utilities as well,
566// if it's a chat command, and generate an error.
567 // If it's on the no names list, then don't bother expanding the names.
568 if (-1 != llListFindList(OUR_COMMANDS_NONAMES, [thisCommand]))
569 {
570 // Don't bother if it's from chat, it got done already through the usual method.
571 if (0 == handle)
572 sendCommand(user, command);
573 }
574 else
575 {
576 for (i = 0; i < length; ++i)
577 sendCommand(user, thisCommand + " " + llList2String(npcs, i) + rest);
578 }
579 }
580}
581
582sendCommand(key user, string command)
583{
584 llMessageLinked(LINK_SET, UTILITIES_CHAT_FAKE, llDumpList2String([0, llKey2Name(user), user, command], LIST_SEP), scriptKey);
585}
586
587init()
588{
589 integer i;
590 if (llGetAttached())
591 {
592 // We are attached to an avatar, so get it's key.
593 key realId = llGetOwnerKey(llGetKey());
594 if (osIsNpc(realId))
595 {
596 // TODO - Instead we should go into a "only control this NPC" mode.
597 llSay(0, "Deleting onefang's NPC scripts from this NPC.");
598 llRemoveInventory(UTILITIES_SCRIPT_NAME);
599 llRemoveInventory(llGetScriptName());
600 }
601 else
602 {
603 // Only listen to the attachment wearer if attached.
604 OUR_COMMANDS = llListReplaceList(OUR_COMMANDS, [realId], 1, 1);
605 }
606 }
607 IS_NPC = ACTIVE;
608 IS_BOTH = SCRIPTED;
609 OBJECT = PASSIVE;
610 for (i = 0; i < 16; ++i)
611 chatters += ["", 0, 0];
612 scriptKey = llGetInventoryKey(llGetScriptName());
613 llMessageLinked(LINK_SET, UTILITIES_RESET, "reset", scriptKey);
614 llSetTimerEvent(scriptTick);
615}
616
617default
618{
619 state_entry()
620 {
621 init();
622 }
623
624 on_rez(integer param)
625 {
626 init();
627 }
628
629 attach(key attached)
630 {
631 init();
632 }
633
634 changed(integer change)
635 {
636 // Restore NPCs if the sim restarted, after a delay to let the sim settle.
637 if (change & CHANGED_REGION_START)
638 restartTimer = llGetTime() + 60.0;
639 }
640
641 link_message(integer sender_num, integer num, string value, key id)
642 {
643 list keys = llParseStringKeepNulls((string) id, ["|"], []);
644 string extra = llList2String(keys, 1);
645
646 id = (key) llList2String(keys, 0);
647//llSay(0, "id = " + (string) id + " extra = " + extra + " VALUE " + value);
648 if ((NPC_RECORD == num) && (scriptKey == id))
649 {
650 record = [];
651 recording = TRUE;
652 }
653 else if ((NPC_RECORD_STOP == num) && (scriptKey == id))
654 {
655 recording = FALSE;
656 lastTick = -1.0;
657 llRemoveInventory(NPC_RECORD_CARD + NPC_SCRIPT_EXT);
658 osMakeNotecard(NPC_RECORD_CARD + NPC_SCRIPT_EXT, record);
659 }
660 else if ((UTILITIES_RESET_DONE == num) && (llGetInventoryKey(UTILITIES_SCRIPT_NAME) == id))
661 {
662 // Set our commands.
663 OUR_COMMANDS = llListReplaceList(OUR_COMMANDS, [commandChannel], 0, 0);
664 llMessageLinked(LINK_SET, UTILITIES_CHAT, llDumpList2String(OUR_COMMANDS, LIST_SEP), scriptKey);
665 }
666 else if ((UTILITIES_READ_DONE == num) && (scriptKey == id))
667 {
668 // The script is done, remove the reading flag.
669 list command = llParseStringKeepNulls(value, [LIST_SEP], []);
670 string card = llList2String(command, 0);
671 // extra was pre strided when it was sent off.
672 integer i = ((integer) extra);
673
674 if ("" != llList2String(scripts, i))
675 {
676 integer flags = llList2Integer(scripts, i + SCRIPTS_FLAGS);
677
678 scripts = llListReplaceList(scripts, [flags & (~SCRIPT_READING)], i + SCRIPTS_FLAGS, i + SCRIPTS_FLAGS);
679//llSay(0, "Done reading " + (string) i + " " + card + "\n" + llDumpList2String(scripts, "~"));
680 }
681 }
682 else if (-1000 >= num) // SettingsReader telling us to change a setting.
683 {
684 // num is the line number : -1000 - settingsLine
685 // Not sure if llMessageLinked is a FIFO, but lets hope so.
686 // Otherwise we may have to resort to OpenSim card reading, and drop SL compatibility.
687 list command = llParseStringKeepNulls(value, [LIST_SEP], []);
688 list result = [];
689 string card = llList2String(command, 0);
690 integer length = llGetListLength(command);
691 // extra was pre strided when it was sent off.
692 integer i = (integer) extra;
693 integer j;
694 string commands = llList2String(scripts, i + SCRIPTS_COMMANDS);
695
696 if ("" != commands)
697 result = [commands];
698//llSay(0, " reading " + (string) i + " " + card);
699//if (0 == (num % 100)) {llSay(0, "read line " + (string) num); llSleep(0.1);}
700 for (j = 1; j < length; j += 2)
701 result += [llList2String(command, j) + LIST_SEP + llList2String(command, j + 1)];
702 scripts = llListReplaceList(scripts, [llDumpList2String(result, "|")], i + SCRIPTS_COMMANDS, i + SCRIPTS_COMMANDS);
703 }
704 else if ((UTILITIES_CHAT_DONE == num) && (scriptKey == id))
705 {
706 integer executed = FALSE;
707 // incoming channel | incoming name | incoming key | incoming message | prefix | command | list of arguments | rest of message
708 list result = llParseStringKeepNulls(value, [LIST_SEP], []);
709 //integer inchannel = llList2Integer(result, 0);
710 //string inName = llList2String (result, 1);
711 key user = llList2Key (result, 2);
712 //string inMessage = llList2String (result, 3);
713 //string prefix = llList2String (result, 4);
714 string command = llList2String (result, 5);
715 list arguments = llList2List (result, 6, -1); // Includes "rest of message" as the last one.
716//llSay(0, "COMMAND " + value);
717
718 // WARNING - Don't return out of this "if" chain, unless you don't want the command recorded.
719 // TODO - Maybe just do that instead of using the executed flag.
720 if (NPC_ATTENTION == command)
721 addAttention(user, string2key(llList2String(arguments, 0), IS_NPC));
722 if (NPC_DISMISSED == command)
723 delAttention(user, string2key(llList2String(arguments, 0), IS_NPC), NULL_KEY);
724 else if (NPC_CLONE == command)
725 {
726 string name = llList2String(arguments, 0);
727 key uuid = string2key(name, AGENT);
728
729 if (osIsUUID(name))
730 name = llKey2Name(name);
731 if (NULL_KEY != uuid)
732 {
733 osAgentSaveAppearance(uuid, name + NPC_NPC_EXT);
734 executed = TRUE;
735 }
736 }
737 else if (NPC_CREATE == command)
738 {
739 string name = llList2String(arguments, 0);
740 list names = llParseString2List(name, [" "], []);
741 string last = llList2String(names, 1);
742 vector pos = llList2String(arguments, 1);
743 key npc;
744 integer fromMenu = FALSE;
745
746 // Either strip off the extension, or add it.
747 if (llSubStringIndex(last, NPC_NPC_EXT) != -1)
748 {
749 last = llGetSubString(last, 0, -1 - llStringLength(NPC_NPC_EXT));
750 // This is an evil hack, the menu system will hand us the full name of the notecard.
751 // Other methods are likely to not do so. But only likely.
752 // TODO - I think this idea fails with the new argument parsing. DOH!
753 fromMenu = TRUE;
754 }
755 else
756 name += NPC_NPC_EXT;
757 if (ZERO_VECTOR == pos)
758 pos = llGetPos() + (<1.0, 0.0, 1.5> * llGetRot());
759 npc = osNpcCreate(llList2String(names, 0), last, pos, name, OS_NPC_SENSE_AS_AGENT | OS_NPC_NOT_OWNED);
760 executed = TRUE;
761 if (fromMenu)
762 llMessageLinked(LINK_SET, NPC_NEW_NPC, user + "|" + npc, scriptKey);
763 }
764 else if (NPC_CHANGE == command)
765 {
766 string card = llList2String(arguments, 1);
767
768 if (llSubStringIndex(card, NPC_NPC_EXT) == -1)
769 card += NPC_NPC_EXT;
770 osNpcLoadAppearance(string2key(llList2String(arguments, 0), IS_NPC), card);
771 executed = TRUE;
772 }
773 else if (NPC_COME == command)
774 executed = goThere(user, llList2String(arguments, 0), user, NPC_GO);
775 else if (NPC_FOLLOW == command)
776 {
777 key npc = string2key(llList2String(arguments, 0), IS_NPC);
778
779 if (NULL_KEY != npc)
780 {
781 integer found = llListFindList(followers, [npc]);
782 vector pos = (vector) llList2String(arguments, 1);
783
784 if (ZERO_VECTOR == pos)
785 {
786 float distance = llList2Float(arguments, 1);
787
788 if (0.0 == distance)
789 pos = STALK_DISTANCE;
790 else
791 pos = <distance, 0.0, 0.0>;
792 }
793 if ((0 <= found) && ((found % FOLLOW_STRIDE) == 0))
794 llListReplaceList(followers, [npc, user, pos], found, found + FOLLOW_STRIDE - 1);
795 else
796 followers += [npc, user, pos];
797 executed = TRUE;
798 }
799 }
800 else if (NPC_STALK == command)
801 {
802 key npc = string2key(llList2String(arguments, 0), IS_NPC);
803 key avatar = string2key(llList2String(arguments, 1), IS_BOTH);
804
805 // No stalking yourself, that's just creepy.
806 if ((NULL_KEY != avatar) && (NULL_KEY != npc) && (avatar != npc))
807 {
808 integer found = llListFindList(followers, [npc]);
809 vector pos = (vector) llList2String(arguments, 2);
810
811 if (ZERO_VECTOR == pos)
812 {
813 float distance = llList2Float(arguments, 2);
814
815 if (0.0 == distance)
816 pos = STALK_DISTANCE;
817 else
818 pos = <distance, 0.0, 0.0>;
819 }
820 if ((0 <= found) && ((found % FOLLOW_STRIDE) == 0))
821 llListReplaceList(followers, [npc, avatar, pos], found, found + FOLLOW_STRIDE - 1);
822 else
823 followers += [npc, avatar, pos];
824 executed = TRUE;
825 }
826 }
827 else if (NPC_GO == command)
828 executed = goThere(user, llList2String(arguments, 0), llList2String(arguments, 1), NPC_GO);
829 else if (NPC_FLY == command)
830 executed = goThere(user, llList2String(arguments, 0), llList2String(arguments, 1), NPC_FLY);
831 else if (NPC_LAND == command)
832 executed = goThere(user, llList2String(arguments, 0), llList2String(arguments, 1), NPC_LAND);
833 else if (NPC_LINK == command)
834 {
835 llMessageLinked(llList2Integer(arguments, 0), llList2Integer(arguments, 1),
836 llList2String(arguments, 2), llList2String(arguments, 3));
837 executed = TRUE;
838 }
839 else if (NPC_LISTEN == command)
840 {
841 // Remove our chat commands from whatever channel they where on before.
842 llMessageLinked(LINK_SET, UTILITIES_CHAT, llDumpList2String(llList2List(OUR_COMMANDS, 0, 0) + ["", "", ""], LIST_SEP), scriptKey);
843 // Set them on the new channel.
844 commandChannel = llList2Integer(arguments, 0);
845 OUR_COMMANDS = llListReplaceList(OUR_COMMANDS, [commandChannel], 0, 0);
846 llMessageLinked(LINK_SET, UTILITIES_CHAT, llDumpList2String(OUR_COMMANDS, LIST_SEP), scriptKey);
847 executed = TRUE;
848 }
849 else if (NPC_LOCATE == command)
850 {
851 key npc = string2key(llList2String(arguments, 0), IS_NPC);
852 vector pos = string2pos(llList2String(arguments, 0));
853 vector size = llGetAgentSize(npc);
854
855 // This wont work, it HAS to be in a touch event. Silly LL and their hobbled thinking.
856 //llMapDestination(llGetRegionName(), pos, pos);
857
858 // Offset by halfish the NPCs size, so it should end up above them.
859 pos.z += size.z / 1.8;
860 // First destroy any existing beacons. Simplifies things.
861 llRegionSay(SIM_CHANNEL, "nobeacon");
862 llRezObject("locator beacon", llGetPos(), ZERO_VECTOR, llEuler2Rot(<180.0 * DEG_TO_RAD, 0.0, 0.0>), SIM_CHANNEL);
863 // Wait for it to finish starting up. A hack I know, avoids making things more complex.
864 // Avoids complications with object_rez(key uuid) events and having to track what we rezzed.
865 llSleep(1.0);
866 llRegionSay(SIM_CHANNEL, "beacon " + (string) pos);
867 }
868 else if (NPC_ANIMATE == command)
869 {
870 osNpcPlayAnimation(string2key(llList2String(arguments, 0), IS_NPC), llList2String(arguments, 1));
871 executed = TRUE;
872 }
873 else if (NPC_STOPANIM == command)
874 {
875 osNpcStopAnimation(string2key(llList2String(arguments, 0), IS_NPC), llList2String(arguments, 1));
876 executed = TRUE;
877 }
878 else if (NPC_ROTATE == command)
879 {
880 rotation rot = llEuler2Rot(<0.0, 0.0, llList2Float(arguments, 1) * DEG_TO_RAD>);
881
882 osNpcSetRot(string2key(llList2String(arguments, 0), IS_NPC), rot);
883 executed = TRUE;
884 }
885 else if (NPC_SCRIPT == command)
886 {
887 newScript(user, llList2String(arguments, 0));
888 // Don't actually record this, since it's commands will be recorded.
889 executed = FALSE;
890 }
891 else if (NPC_SAY == command)
892 executed = checkChat(string2key(llList2String(arguments, 0), IS_NPC), llList2String(arguments, 1), 1, NULL_KEY);
893 else if (NPC_SHOUT == command)
894 executed = checkChat(string2key(llList2String(arguments, 0), IS_NPC), llList2String(arguments, 1), 2, NULL_KEY);
895 else if (NPC_STOP == command)
896 {
897 key npc = string2key(llList2String(arguments, 0), IS_NPC);
898 integer found = llListFindList(followers, [npc]);
899
900 if ((0 <= found) && ((found % FOLLOW_STRIDE) == 0))
901 followers = llDeleteSubList(followers, found, found + FOLLOW_STRIDE - 1);
902 found = llListFindList(movers, [npc]);
903 if ((0 <= found) && ((found % MOVE_STRIDE) == 0))
904 movers = llDeleteSubList(movers, found, found + MOVE_STRIDE - 1);
905 osNpcStopMoveToTarget(npc);
906 executed = TRUE;
907 }
908 else if (NPC_COMPLETE == command) // Do nothing, it's a script only command handled completely in the script.
909 executed = TRUE;
910 else if (NPC_WHISPER == command)
911 executed = checkChat(string2key(llList2String(arguments, 0), IS_NPC), llList2String(arguments, 1), 0, NULL_KEY);
912 else if (NPC_SIT == command)
913 {
914 key npc = string2key(llList2String(arguments, 0), IS_NPC);
915 string name = llList2String(arguments, 1);
916
917 if (NULL_KEY != npc)
918 {
919 key that = string2key(name, OBJECT);
920
921 if (NULL_KEY == that)
922 startSensor(user, npc, name, ACTIVE | SCRIPTED, NPC_SIT);
923 else
924 {
925 osNpcSit(npc, that, OS_NPC_SIT_NOW);
926 executed = TRUE;
927 }
928 }
929 }
930 else if (NPC_TOUCH == command)
931 {
932 key npc = string2key(llList2String(arguments, 0), IS_NPC);
933 string name = llList2String(arguments, 1);
934
935 if (NULL_KEY != npc)
936 {
937 key that = string2key(name, OBJECT);
938
939 if (NULL_KEY == that)
940 startSensor(user, npc, name, ACTIVE | SCRIPTED, NPC_TOUCH);
941 else
942 {
943 osNpcTouch(npc, that, LINK_ROOT);
944 executed = TRUE;
945 }
946 }
947 }
948 else if (NPC_STAND == command)
949 {
950 osNpcStand(string2key(llList2String(arguments, 0), IS_NPC));
951 executed = TRUE;
952 }
953 else if (NPC_DELETE == command)
954 {
955 // Since this deletes the NPC, if we are recording we wont be able to do llKey2Name below.
956 string name = llKey2Name(string2key(llList2String(arguments, 0), IS_NPC));
957
958 killNPC(string2key(llList2String(arguments, 0), IS_NPC), NULL_KEY);
959 arguments = [name];
960 executed = TRUE;
961 }
962 else if (NPC_NUKE == command)
963 {
964 list avatars = osGetAvatarList(); // Strided list, UUID, position, name.
965 integer length = llGetListLength(avatars);
966 integer i;
967
968 for (i = 0; i < length; i++)
969 {
970 key this = (key) llList2String(avatars, i * 3);
971
972 if (osIsNpc(this))
973 osNpcRemove(this);
974 }
975 attention = [];
976 chatters = [];
977 followers = [];
978 movers = [];
979 scripts = [];
980 sensorRequests = [];
981 sensorInFlight = FALSE;
982 // Delete the beacons as well.
983 llRegionSay(SIM_CHANNEL, "nobeacon");
984 executed = TRUE;
985 }
986
987 // Record it, but only if it did something.
988 if (recording && executed)
989 recordIt(command, arguments);
990 }
991 }
992
993 listen(integer channel, string name, key id, string message)
994 {
995 key npc = (key) llList2String (chatters, (channel * CHAT_STRIDE) + CHAT_KEY);
996 integer type = llList2Integer(chatters, (channel * CHAT_STRIDE) + CHAT_TYPE);
997 integer handle = llList2Integer(chatters, (channel * CHAT_STRIDE) + CHAT_HANDLE);
998
999 // It's either in the chatters or the attention list.
1000 // Chatters are on their own channel.
1001 // Attentions are on the usual command channel (common for all users).
1002 // Utilities will also be listening on the command channel. And will get a duplicate utterance, sans the name.
1003 // So choose between them based on channel.
1004 // Users that make a chatter channel the same as the command channel deserve what they get.
1005 if (channel == commandChannel)
1006 sendManyCommands(id, message, id);
1007 if (NULL_KEY != npc)
1008 checkChat(npc, message, type, NULL_KEY);
1009 }
1010
1011 no_sensor()
1012 {
1013 sensorInFlight = FALSE;
1014 if (llGetListLength(sensorRequests))
1015 {
1016 sensorRequests = llDeleteSubList(sensorRequests, 0, 4);
1017 nextSensor();
1018 }
1019 }
1020
1021 sensor(integer numberDetected)
1022 {
1023 sensorInFlight = FALSE;
1024 if (llGetListLength(sensorRequests))
1025 {
1026 string name = llList2String(sensorRequests, 0);
1027 integer type = llList2Integer(sensorRequests, 1);
1028 string command = llList2String(sensorRequests, 2);
1029 key user = llList2String(sensorRequests, 3);
1030 key npc = llList2String(sensorRequests, 4);
1031
1032 sensorRequests = llDeleteSubList(sensorRequests, 0, 4);
1033 sendCommand(user, command + " " + npc + " " + llDetectedKey(0));
1034 nextSensor();
1035 }
1036 }
1037
1038 timer()
1039 {
1040 integer i;
1041 integer length = llGetListLength(followers);
1042
1043 // Check for sim restart.
1044 if ((0.0 < restartTimer) && (llGetTime() > restartTimer))
1045 {
1046 restartTimer = -1.0;
1047 // Start with a nuke, just to clear out all the lists.
1048 sendCommand(llGetOwnerKey(llGetKey()), NPC_NUKE);
1049 sendCommand(llGetOwnerKey(llGetKey()), NPC_SCRIPT + " " + NPC_BACKUP_CARD + NPC_SCRIPT_EXT);
1050 // The sim restarted, no need to take care of followers or scripts this time.
1051 return;
1052 }
1053
1054 // First the followers.
1055 for (i = 0; i < length; i += FOLLOW_STRIDE)
1056 {
1057 key npc = (key) llList2String(followers, i + FOLLOW_KEY);
1058 key stalkee = (key) llList2String(followers, i + FOLLOW_STALK);
1059 vector distance = (vector) llList2String(followers, i + FOLLOW_DIST);
1060 float mag = llVecMag(distance);
1061 list details = llGetObjectDetails(stalkee, [OBJECT_POS, OBJECT_VELOCITY, OBJECT_ROT]);
1062 list npcDetails = llGetObjectDetails(npc, [OBJECT_POS, OBJECT_VELOCITY, OBJECT_ROT]);
1063 vector pos = (vector) llList2String(details, 0);
1064 vector speed = (vector) llList2String(details, 1);
1065 rotation rot = (rotation) llList2String(details, 2);
1066 integer npcInfo = llGetAgentInfo(npc);
1067 integer info = llGetAgentInfo(stalkee);
1068 integer isFlying = info & AGENT_FLYING;
1069 integer isWalking = info & AGENT_WALKING;
1070 integer isInAir = info & AGENT_IN_AIR;
1071 integer isRunning = info & AGENT_ALWAYS_RUN;
1072 integer isSitting = info & AGENT_SITTING;
1073 vector newPos = pos + (distance * rot);
1074 float newDist = llVecMag((vector) llList2String(npcDetails, 0) - pos);
1075
1076 if (newDist > mag)
1077 {
1078 if (npcInfo & AGENT_SITTING) // Tell the lazy bum to stand up.
1079 osNpcStand(npc);
1080 if (isRunning)
1081 osNpcMoveToTarget(npc, newPos, OS_NPC_NO_FLY | OS_NPC_RUNNING);
1082 else if (isFlying || isInAir)
1083 osNpcMoveToTarget(npc, newPos, OS_NPC_FLY);
1084 else if (isWalking)
1085 osNpcMoveToTarget(npc, newPos, OS_NPC_NO_FLY);
1086 else
1087 osNpcMoveToTarget(npc, newPos, OS_NPC_NO_FLY);
1088// osNpcMoveToTarget(npc, pos, OS_NPC_FLY | OS_NPC_LAND_AT_TARGET);
1089 }
1090 else
1091 osNpcStopMoveToTarget(npc);
1092 }
1093
1094 // Then movers.
1095 length = llGetListLength(movers);
1096 for (i = 0; i < length; i += MOVE_STRIDE)
1097 {
1098 key npc = (key) llList2String (movers, i + MOVE_KEY);
1099 vector dest = (vector) llList2String (movers, i + MOVE_DEST);
1100 float least = llList2Float (movers, i + MOVE_LEAST);
1101 float time = llList2Float (movers, i + MOVE_TIME);
1102 float left = llVecMag((vector) llList2String(llGetObjectDetails(npc, [OBJECT_POS]), 0) - dest);
1103
1104 if (least > left) // Progress has been made.
1105 movers = llListReplaceList(movers, [left, llGetTime()], i + MOVE_LEAST, i + MOVE_TIME);
1106 else if ((llGetTime() - time) > 5.0) // No progress, they are stuck.
1107 {
1108 key oldNpc = npc;
1109 string name = llKey2Name(npc);
1110 list names = llParseString2List(name, [" "], []);
1111 list anims = llGetAnimationList(npc);
1112 integer animLen = llGetListLength(anims);
1113 integer invLen = llGetInventoryNumber(INVENTORY_ANIMATION);
1114 integer j;
1115
1116 llShout(0, name + " is stuck!");
1117if ("" == name)
1118{
1119 llSay(0, "stuck key " + (string) i + " " + npc + " - " + oldNpc + " -- " + llDumpList2String(movers, "|"));
1120 killNPC(oldNpc, NULL_KEY);
1121 return;
1122}
1123 osAgentSaveAppearance(npc, NPC_TEMP_CARD + NPC_NPC_EXT);
1124 length = llGetListLength(movers);
1125 npc = osNpcCreate(llList2String(names, 0), llList2String(names, 1), dest, NPC_TEMP_CARD + NPC_NPC_EXT, OS_NPC_SENSE_AS_AGENT | OS_NPC_NOT_OWNED);
1126 llRemoveInventory(NPC_TEMP_CARD + NPC_NPC_EXT);
1127 // We want to replace them into the Attention, chatters, and followers lists.
1128 // Though no point adding them back to movers.
1129//llSay(0, "stuck key " + npc + " - " + oldNpc + " -- " + llDumpList2String(movers, "|"));
1130 killNPC(oldNpc, npc);
1131
1132 // Try to re instate the animations.
1133 for (j = 0; j < animLen; ++j)
1134 {
1135 key anim = (key) llList2String(anims, j);
1136 integer k;
1137
1138 // For each of the anims that was playing on the old NPC,
1139 // See if we can find a match in our inventory.
1140 for (k = 0; k < invLen; ++k)
1141 {
1142 string thisName = llGetInventoryName(INVENTORY_ANIMATION, k);
1143
1144 if (llGetInventoryKey(thisName) == anim)
1145 {
1146 osNpcPlayAnimation(npc, thisName);
1147 k = invLen;
1148 }
1149 }
1150 }
1151 }
1152
1153 if (left < 2.5) // They have arrived.
1154 {
1155 movers = llDeleteSubList(movers, i, i + MOVE_STRIDE - 1);
1156 length -= MOVE_STRIDE;
1157 osNpcStopMoveToTarget(npc);
1158 }
1159 }
1160
1161 // Then the scripts.
1162 length = llGetListLength(scripts);
1163 for (i = 0; i < length; i += SCRIPTS_STRIDE)
1164 {
1165 string user = llList2String(scripts, i + SCRIPTS_KEY);
1166
1167 if ("" != user)
1168 {
1169 string name = llList2String(scripts, i + SCRIPTS_NAME);
1170 integer flags = llList2Integer(scripts, i + SCRIPTS_FLAGS);
1171 list commands = llParseStringKeepNulls(llList2String(scripts, i + SCRIPTS_COMMANDS), ["|"], []);
1172 list statement = llParseStringKeepNulls(llList2String(commands, 0), [LIST_SEP], []);
1173 string command = llList2String(statement, 0);
1174 string value = llList2Key(statement, 1);
1175
1176 commands = llDeleteSubList(commands, 0, 0);
1177
1178 if ("" == value) // A command with no =. Run it as a pretend chat command from the user.
1179 {
1180 if ("sleep " == llGetSubString(command, 0, 5))
1181 {
1182 float time = (float) llGetSubString(command, 6, -1);
1183
1184 commands = llListInsertList(commands, ["until " + (string)(llGetTime() + time)], 0);
1185 }
1186 else if ("until " == llGetSubString(command, 0, 5))
1187 {
1188 float time = (float) llGetSubString(command, 6, -1);
1189
1190 if (llGetTime() < time)
1191 commands = llListInsertList(commands, [command], 0);
1192 }
1193 else if ("script " == llGetSubString(command, 0, 6))
1194 {
1195 string new = llGetSubString(command, 7, -1);
1196 integer index = newScript(user, new);
1197
1198 if (0 == llGetListLength(commands))
1199 ;//llSay(0, "tail recursion detected - " + name + " -> " + new + " " + (string) i + " -> " + (string) index);
1200 else if (-1 != index)
1201 commands = llListInsertList(commands, ["wait " + (string) index + " " + new], 0);
1202 }
1203 else if ("wait " == llGetSubString(command, 0, 4))
1204 {
1205 list parts = llParseStringKeepNulls(command, [" "], []);
1206 integer index = llList2Integer(parts, 1);
1207 string card = llList2String(parts, 2);
1208
1209 // Check if the script this user started is still in the same slot we created above.
1210 // Note, still possible to get a, hopefully rare, race condition here.
1211 if ((llList2String(scripts, index + SCRIPTS_KEY) == user) && (llList2String(scripts, index + SCRIPTS_NAME) == card))
1212 commands = llListInsertList(commands, [command], 0);
1213 }
1214 else if ("complete" == llGetSubString(command, 0, 7) && (8 == llStringLength(command)))
1215 {
1216 // Deal with attention seekers, we have to wait for all of them to get there.
1217 integer found = llListFindList(attention, [(string) i]);
1218
1219 if (0 != (found % ATTENT_STRIDE))
1220 found = -1;
1221
1222 if (-1 != found)
1223 {
1224 list npcs = llParseString2List(llList2String(attention, found + ATTENT_NPCS), ["|"], []);
1225 integer nLength = llGetListLength(npcs);
1226 integer j;
1227
1228 for (j = 0; j < nLength; ++j)
1229 {
1230 key npc = llList2String(npcs, j);
1231 found = llListFindList(movers, [npc]);
1232
1233 // If any are still a mover, keep waiting for the move to complete.
1234 if ((0 <= found) && ((found % MOVE_STRIDE) == 0))
1235 {
1236 commands = llListInsertList(commands, [command], 0);
1237 j = nLength;
1238 }
1239 }
1240 }
1241 }
1242 else if ("complete " == llGetSubString(command, 0, 8))
1243 {
1244 key npc = string2key(llGetSubString(command, 9, -1), IS_NPC);
1245 integer found = llListFindList(movers, [npc]);
1246
1247 // If they are still a mover, keep waiting for the move to complete.
1248 if ((0 <= found) && ((found % MOVE_STRIDE) == 0))
1249 commands = llListInsertList(commands, [command], 0);
1250 }
1251 else if ("attention " == llGetSubString(command, 0, 9))
1252 {
1253 addAttention((string) i, string2key(llGetSubString(command, 10, -1), IS_NPC));
1254 scripts = llListReplaceList(scripts, [flags | SCRIPT_ATTENTION], i + SCRIPTS_FLAGS, i + SCRIPTS_FLAGS);
1255 }
1256 else if ("dismissed " == llGetSubString(command, 0, 9))
1257 {
1258 scripts = llListReplaceList(scripts, [flags & (~SCRIPT_ATTENTION)], i + SCRIPTS_FLAGS, i + SCRIPTS_FLAGS);
1259 delAttention((string) i, string2key(llGetSubString(command, 10, -1), IS_NPC), NULL_KEY);
1260 }
1261 else if ("" != command)
1262 {
1263//llSay(0, "DOING " + (string) (flags & SCRIPT_ATTENTION) + " " + command);
1264 if (flags & SCRIPT_ATTENTION)
1265 sendManyCommands(user, command, (string) i);
1266 else
1267 sendCommand(user, command);
1268 }
1269 }
1270 else // A variable assignment.
1271 {
1272// if ("DEBUG" == command)
1273// DEBUG = ("TRUE" == value);
1274 }
1275
1276 if ((flags & SCRIPT_READING) || (0 < llGetListLength(commands)))
1277 scripts = llListReplaceList(scripts, [llDumpList2String(commands, "|")], i + SCRIPTS_COMMANDS, i + SCRIPTS_COMMANDS);
1278 else
1279 {
1280 scripts = llListReplaceList(scripts, ["", "", 0, ""], i, i + SCRIPTS_STRIDE - 1);
1281 // Remove all our attention seekers.
1282 delAttention((string) i, NULL_KEY, NULL_KEY);
1283 while ((llGetListLength(scripts) > 0) && ("" == llList2String(scripts, 0 - SCRIPTS_STRIDE)))
1284 scripts = llDeleteSubList(scripts, 0 - SCRIPTS_STRIDE, -1);
1285//llSay(0, "Finished script " + (string) i + " " + llDumpList2String(commands, "^") + "\n" + llDumpList2String(scripts, "~"));
1286 }
1287 }
1288 }
1289 }
1290
1291}