diff options
Diffstat (limited to '')
-rw-r--r-- | NPC_tool.lsl | 1291 |
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. | ||
34 | integer commandChannel = 0; | ||
35 | |||
36 | list attention = []; // npc key or script number, listen handle, list of attentive NPCs | ||
37 | integer ATTENT_KEY = 0; | ||
38 | integer ATTENT_HANDLE = 1; | ||
39 | integer ATTENT_NPCS = 2; | ||
40 | integer ATTENT_STRIDE = 3; | ||
41 | |||
42 | list chatters = []; // npc key, chat type, listen handle. In channel order. | ||
43 | integer CHAT_KEY = 0; | ||
44 | integer CHAT_TYPE = 1; | ||
45 | integer CHAT_HANDLE = 2; | ||
46 | integer CHAT_STRIDE = 3; | ||
47 | |||
48 | list followers = []; // npc key, stalkee key, distance vector | ||
49 | integer FOLLOW_KEY = 0; | ||
50 | integer FOLLOW_STALK = 1; | ||
51 | integer FOLLOW_DIST = 2; | ||
52 | integer FOLLOW_STRIDE = 3; | ||
53 | |||
54 | list movers = []; // npc key, destination position, least distance, timestamp | ||
55 | integer MOVE_KEY = 0; | ||
56 | integer MOVE_DEST = 1; | ||
57 | integer MOVE_LEAST = 2; | ||
58 | integer MOVE_TIME = 3; | ||
59 | integer MOVE_STRIDE = 4; | ||
60 | |||
61 | float scriptTick = 0.5; | ||
62 | float lastTick = -1.0; | ||
63 | list scripts = []; // user key, script card name, flags, command list LIST_SEP separated | ||
64 | integer SCRIPTS_KEY = 0; | ||
65 | integer SCRIPTS_NAME = 1; | ||
66 | integer SCRIPTS_FLAGS = 2; | ||
67 | integer SCRIPTS_COMMANDS = 3; | ||
68 | integer SCRIPTS_STRIDE = 4; | ||
69 | |||
70 | // Script flags. | ||
71 | integer SCRIPT_READING = 1; | ||
72 | integer SCRIPT_ATTENTION = 2; | ||
73 | |||
74 | list sensorRequests = []; // name to search for, type of search, command, user key, npc key | ||
75 | integer sensorInFlight = FALSE; | ||
76 | |||
77 | integer recording = FALSE; | ||
78 | list record = []; // recorded commands | ||
79 | |||
80 | float restartTimer = -1.0; | ||
81 | |||
82 | // NPC commands. | ||
83 | string NPC_ANIMATE = "animate"; // NPC name, animation name | ||
84 | string NPC_ATTENTION= "attention"; // NPC name. | ||
85 | string NPC_CHANGE = "change"; // NPC name, NPC (notecard) name. | ||
86 | string NPC_CLONE = "clone"; // Avatar's name. | ||
87 | string NPC_COME = "come"; // NPC name. | ||
88 | string NPC_COMPLETE = "complete"; // NPC name. | ||
89 | string NPC_CREATE = "create"; // NPC (notecard) name, (optional) position vector. | ||
90 | string NPC_DELETE = "delete"; // NPC name. | ||
91 | string NPC_DISMISSED= "dismissed"; // NPC name. | ||
92 | string NPC_FOLLOW = "follow"; // NPC name, (optional) distance (float or vector). | ||
93 | string NPC_FLY = "fly"; // NPC name, position vector or object/agent name/key. | ||
94 | string NPC_GO = "go"; // NPC name, position vector or object/agent name/key. | ||
95 | string NPC_LAND = "land"; // NPC name, position vector or object/agent name/key. | ||
96 | string NPC_LINK = "link"; // Link number, number, string, (optional) key | ||
97 | string NPC_LISTEN = "listen"; // new command channel | ||
98 | string NPC_LOCATE = "locate"; // NPC name | ||
99 | string NPC_NUKE = "nuke"; // No arguments. | ||
100 | string NPC_ROTATE = "rotate"; // NPC name, Z rotation in degrees. | ||
101 | string NPC_SAY = "say"; // NPC name, thing to say, or relay channel. | ||
102 | string NPC_SCRIPT = "script"; // Script notecard name. | ||
103 | string NPC_SHOUT = "shout"; // NPC name, thing to shout, or relay channel. | ||
104 | string NPC_SIT = "sit"; // NPC name, object to sit on. | ||
105 | string NPC_SLEEP = "sleep"; // Seconds. | ||
106 | string NPC_STALK = "stalk"; // NPC name, avatar name, (optional) distance (float or vector). | ||
107 | string NPC_STAND = "stand"; // NPC name. | ||
108 | string NPC_STOP = "stop"; // NPC name. | ||
109 | string NPC_STOPANIM = "stopanim"; // NPC name, animation name | ||
110 | string NPC_TOUCH = "touch"; // NPC name, object to touch. | ||
111 | string NPC_WHISPER = "whisper"; // NPC name, thing to whisper, or relay channel. | ||
112 | |||
113 | string NPC_MAIN = "Main"; | ||
114 | string NPC_PICK = "Pick"; | ||
115 | string NPC_NPC = "NPC"; | ||
116 | string 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. | ||
121 | list 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. | ||
154 | list OUR_COMMANDS_NONAMES = ["attention", "create", "link", "listen", "nuke", "script", "sleep"]; | ||
155 | |||
156 | integer IS_NPC; | ||
157 | integer IS_BOTH; | ||
158 | integer OBJECT; | ||
159 | |||
160 | vector STALK_DISTANCE = <-3.0, 0.0, 0.0>; | ||
161 | integer SIM_CHANNEL = -65767365; | ||
162 | |||
163 | string NPC_NPC_EXT = ".avatar"; | ||
164 | string NPC_SCRIPT_EXT = ".npc"; | ||
165 | string NPC_BACKUP_CARD = "Restore"; | ||
166 | string NPC_RECORD_CARD = "Recorded"; | ||
167 | string NPC_TEMP_CARD = "Temporary"; | ||
168 | |||
169 | integer NPC_RECORD = -100; | ||
170 | integer NPC_RECORD_STOP = -101; | ||
171 | integer NPC_NEW_NPC = -102; | ||
172 | integer NPC_ADD_CHATTER = -103; | ||
173 | integer NPC_DEL_CHATTER = -104; | ||
174 | |||
175 | |||
176 | // Stuff for onefangs common utilities. | ||
177 | key scriptKey; | ||
178 | |||
179 | string UTILITIES_SCRIPT_NAME = "onefang's utilities"; | ||
180 | string LIST_SEP = "$!#"; // Used to seperate lists when sending them as strings. | ||
181 | integer UTILITIES_RESET = -1; | ||
182 | integer UTILITIES_RESET_DONE = -2; | ||
183 | integer UTILITIES_READ = -3; | ||
184 | integer UTILITIES_READ_DONE = -4; | ||
185 | integer UTILITIES_SUBSTITUTE = -5; | ||
186 | integer UTILITIES_SUBSTITUTE_DONE = -6; | ||
187 | integer UTILITIES_NEXT_WORD = -7; | ||
188 | integer UTILITIES_NEXT_WORD_DONE = -8; | ||
189 | integer UTILITIES_PAYMENT_MADE = -9; | ||
190 | integer UTILITIES_PAYMENT_MADE_DONE = -10; | ||
191 | integer UTILITIES_MENU = -11; | ||
192 | integer UTILITIES_MENU_DONE = -12; | ||
193 | integer UTILITIES_WARP = -13; | ||
194 | integer UTILITIES_WARP_DONE = -14; | ||
195 | integer UTILITIES_AVATAR_KEY = -15; | ||
196 | integer UTILITIES_AVATAR_KEY_DONE = -16; | ||
197 | integer UTILITIES_CHAT = -17; | ||
198 | integer UTILITIES_CHAT_DONE = -18; | ||
199 | integer UTILITIES_CHAT_FAKE = -19; | ||
200 | integer UTILITIES_CHAT_FAKE_DONE = -20; | ||
201 | |||
202 | |||
203 | integer 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 | |||
259 | startSensor(key user, key npc, string name, integer type, string command) | ||
260 | { | ||
261 | sensorRequests += [name, type, command, user, npc]; | ||
262 | nextSensor(); | ||
263 | } | ||
264 | |||
265 | nextSensor() | ||
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 | |||
282 | integer 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 | |||
307 | recordIt(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 | |||
328 | key 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 | |||
361 | vector 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 | |||
378 | integer 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 | |||
415 | killNPC(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 | |||
457 | addAttention(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. | ||
495 | delAttention(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 | |||
534 | sendManyCommands(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 | |||
582 | sendCommand(key user, string command) | ||
583 | { | ||
584 | llMessageLinked(LINK_SET, UTILITIES_CHAT_FAKE, llDumpList2String([0, llKey2Name(user), user, command], LIST_SEP), scriptKey); | ||
585 | } | ||
586 | |||
587 | init() | ||
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 | |||
617 | default | ||
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!"); | ||
1117 | if ("" == 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 | } | ||