diff options
Diffstat (limited to 'NPC_tool.lsl')
| -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 | } | ||
