aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/onefang's_utilities.lsl
diff options
context:
space:
mode:
authorDavid Walter Seikel2014-03-02 22:34:54 +1000
committerDavid Walter Seikel2014-03-02 22:34:54 +1000
commit9c9a333860debf85b403a79bd6ee93b4f6bc5ded (patch)
treea04fe89e2467782129dc6aba7b82d7e8cf57e97a /onefang's_utilities.lsl
parentInitial commit (diff)
downloadNPC-tool-9c9a333860debf85b403a79bd6ee93b4f6bc5ded.zip
NPC-tool-9c9a333860debf85b403a79bd6ee93b4f6bc5ded.tar.gz
NPC-tool-9c9a333860debf85b403a79bd6ee93b4f6bc5ded.tar.bz2
NPC-tool-9c9a333860debf85b403a79bd6ee93b4f6bc5ded.tar.xz
Initial import of everything.
Diffstat (limited to 'onefang's_utilities.lsl')
-rw-r--r--onefang's_utilities.lsl1189
1 files changed, 1189 insertions, 0 deletions
diff --git a/onefang's_utilities.lsl b/onefang's_utilities.lsl
new file mode 100644
index 0000000..e088141
--- /dev/null
+++ b/onefang's_utilities.lsl
@@ -0,0 +1,1189 @@
1// onefang's utilites version 3.0
2// Read a complete settings notecard and send settings to other scripts.
3// Also other useful functions.
4
5// Copyright (C) 2007 David Seikel (onefang rejected).
6//
7// Permission is hereby granted, free of charge, to any person obtaining a copy
8// of this software and associated documentation files (the "Software"), to
9// deal in the Software without restriction, including without limitation the
10// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
11// sell copies of the Software, and to permit persons to whom the Software is
12// furnished to do so, subject to the following conditions:
13//
14// The above copyright notice and this permission notice shall be included in
15// all copies of the Software and its Copyright notices. In addition publicly
16// documented acknowledgment must be given that this software has been used if no
17// source code of this software is made available publicly. This includes
18// acknowledgments in either Copyright notices, Manuals, Publicity and Marketing
19// documents or any documentation provided with any product containing this
20// software. This License does not apply to any software that links to the
21// libraries provided by this software (statically or dynamically), but only to
22// the software provided.
23//
24// Please see the COPYING-PLAIN for a plain-english explanation of this notice
25// and it's intent.
26//
27// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
28// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
29// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
30// THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
31// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
32// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33//
34// As a special exception to the above conditions, the Second Life user known
35// as Winter Ventura may ignore all permissions and conditions and provide her
36// own.
37
38// See the notecard "onefang's utilities manual".
39
40
41float MENU_TIMEOUT = 45.0;
42
43list channelHandles = []; // channel | listener
44list chatChannels = []; // channel | script list
45list chatOwners = []; // owner+channel | script list
46list chatPrefixes = []; // channel+" "+prefix | script list (PREFIXES WITH SPACE)
47list chatPrefixes2 = []; // channel+" "+prefix | script list (PREFIXES WITH NO SPACE)
48list chatCommands = []; // script+channel | command list { command | argument defining stuff }
49// TODO might be better to do - script+channel+" "+prefix | command list { command | argument defining stuff }
50// and do away with the chatPrefix lists. Makes no space prefix checks harder I think.
51// or add a prefix field to the list.
52
53// SettingsReaderAndUtilities constants for copying into other code.
54string LIST_SEP = "$!#"; // Used to seperate lists when sending them as strings.
55integer UTILITIES_RESET = -1;
56integer UTILITIES_RESET_DONE = -2;
57integer UTILITIES_READ = -3;
58integer UTILITIES_READ_DONE = -4;
59integer UTILITIES_SUBSTITUTE = -5;
60integer UTILITIES_SUBSTITUTE_DONE = -6;
61integer UTILITIES_NEXT_WORD = -7;
62integer UTILITIES_NEXT_WORD_DONE = -8;
63integer UTILITIES_PAYMENT_MADE = -9; // Not used by this script
64integer UTILITIES_PAYMENT_MADE_DONE = -10; // Not used by this script
65integer UTILITIES_MENU = -11;
66integer UTILITIES_MENU_DONE = -12;
67integer UTILITIES_WARP = -13;
68integer UTILITIES_WARP_DONE = -14;
69integer UTILITIES_AVATAR_KEY = -15; // Redundant in OpenSim.
70integer UTILITIES_AVATAR_KEY_DONE = -16; // Redundant in OpenSim.
71integer UTILITIES_CHAT = -17;
72integer UTILITIES_CHAT_DONE = -18;
73integer UTILITIES_CHAT_FAKE = -19;
74integer UTILITIES_CHAT_FAKE_DONE = -20; // Never used anywhere.
75// TODO reuse -20 for CHAT_PURGE - which removes this scriptKey from all chat lists, usually before replacing them.
76// Which was done some other way if I remember.
77
78// internal constant and variable declarations.
79integer KEYS_SCRIPT = 0;
80integer KEYS_TYPE = 1;
81integer KEYS_NAME = 2;
82integer KEYS_EXTRA = 3;
83integer KEYS_ID = 4;
84integer KEYS_STRIDE = 5;
85
86string REQUEST_KEY = "1";
87string REQUEST_IM = "2";
88string REQUEST_ONLINE = "3";
89string REQUEST_AVATAR = "4";
90
91integer MENU_USER = 0; // Key of user the big menu is for.
92integer MENU_CHANNEL = 1; // Listen channel for big menu dialogs.
93integer MENU_SCRIPT = 2; // ScriptKey of the script calling the menu.
94integer MENU_HANDLE = 3; // Listen ID for big menu dialogs.
95integer MENU_TYPE = 4; // Type of menu.
96integer MENU_MESSAGE = 5; // Message for the top of big menu dialog.
97integer MENU_MENU = 6; // The big menu itself. LIST_SEP separated when they come in.
98integer MENU_FILTER = 7; // Regex filter for inventory menus. | separated when they come in.
99integer MENU_POS = 8; // Current position within big menu.
100integer MENU_MAXPOS = 9; // Maximum position within big menu.
101integer MENU_NAMES = 10; // Long names to get around stupid SL menu limitations.
102integer MENU_TIME = 11; // Otherwise this list is ever growing. Damn Ignore button. sigh
103integer MENU_STRIDE = 12;
104
105list settingsCards = []; // Queue of cards to read.
106string settingsName = ".settings"; // Name of a notecard in the object's inventory.
107key settingsKey = NULL_KEY; // ScriptKey of the script reading the card.
108integer settingsLine = 0; // Current line number.
109key settingsQueryID = NULL_KEY; // ID used to identify dataserver queries.
110
111list menus = []; // The menus.
112list registeredMenus = []; // Any scripts that have registered as needing a touch menu.
113list keyRequests = []; // A list of avatar key requests.
114
115list ANIMATIONS =
116[
117 "aim_L_bow", "aim_R_bazooka", "aim_R_handgun", "aim_R_rifle", "angry_fingerwag",
118 "angry_tantrum", "away", "backflip", "blowkiss", "bow", "brush", "clap",
119 "courtbow", "cross_arms", "crouch", "crouchwalk", "curtsy",
120 "dance1", "dance2", "dance3", "dance4", "dance5", "dance6", "dance7", "dance8",
121 "dead", "drink", "express_afraid", "express_anger", "express_bored",
122 "express_cry", "express_embarrased", "express_laugh", "express_repulsed",
123 "express_sad", "express_shrug", "express_surprise", "express_wink",
124 "express_worry", "falldown", "female_walk", "fist_pump", "fly", "flyslow",
125 "hello", "hold_R_bow", "hold_R_bazooka", "hold_R_handgun", "hold_R_rifle",
126 "hold_throw_R", "hover", "hover_down", "hover_up", "impatient",
127 "jump", "jumpforjoy", "kick_roundhouse_R", "kissmybutt", "kneel_left",
128 "kneel_right", "land", "laugh_short", "motorcycle_sit", "musclebeach", "no_head",
129 "no_unhappy", "nyanya", "peace", "point_me", "point_you",
130 "prejump", "punch_L", "punch_onetwo", "punch_R",
131 "RPS_countdown", "RPS_paper", "RPS_rock", "RPS_scissors",
132 "run", "salute", "shoot_L_bow", "shout", "sit", "sit_female", "sit_ground",
133 "sit_to_stand", "sleep", "slowwalk", "smoke_idle", "smoke_inhale",
134 "smoke_throw_down", "snapshot", "soft_land", "stand", "standup", "stand_1",
135 "stand_2", "stand_3", "stand_4", "stretch", "stride", "surf", "sword_strike_R",
136 "talk", "throw_R", "tryon_shirt", "turnback_180", "turnleft", "turnright",
137 "turn_180", "type", "uphillwalk", "walk", "whisper", "whistle", "wink_hollywood",
138 "yell", "yes_happy", "yes_head", "yoga_float"
139];
140
141
142list addChatScripts(list thisList, string name, string value, integer stride)
143{
144 integer found = llListFindList(thisList, [name]);
145
146 if (0 <= found)
147 {
148 list values = llParseString2List(llList2String(thisList, found + 1), ["|"], []);
149
150 if (1 == stride)
151 {
152 integer subFound = llListFindList(values, [value]);
153
154 if (0 > subFound)
155 values += [value];
156 }
157 else
158 {
159 integer length = llGetListLength(values);
160 integer i;
161
162 for (i = 0; i < length; i += stride)
163 {
164 list sub = llList2List(values, i, i + stride - 1);
165 integer subFound = llListFindList(values, [llList2String(sub, 0)]);
166
167 if (0 > subFound)
168 values += sub;
169 }
170 }
171 thisList = llListReplaceList(thisList, [name, llDumpList2String(values, "|")], found, found + 1);
172 }
173 else
174 thisList += [name, value];
175 return thisList;
176}
177
178list delChatScripts(list thisList, string name, string value, integer stride)
179{
180 integer found = llListFindList(thisList, [name]);
181
182 if (0 <= found)
183 {
184 list values = llParseString2List(llList2String(thisList, found + 1), ["|"], []);
185
186 if (1 == stride)
187 {
188 integer subFound = llListFindList(values, [value]);
189
190 if (0 <= subFound)
191 values = llDeleteSubList(values, subFound, subFound);
192 }
193 else
194 {
195 integer length = llGetListLength(values);
196 integer i;
197
198 for (i = 0; i < length; i += stride)
199 {
200 list sub = llList2List(values, i, i + stride - 1);
201 integer subFound = llListFindList(values, [llList2String(sub, 0)]);
202
203 if (0 <= subFound)
204 values = llDeleteSubList(values, subFound, subFound + stride - 1);
205 }
206 }
207 if (llGetListLength(values))
208 thisList = llListReplaceList(thisList, [name, llDumpList2String(values, "|")], found, found + 1);
209 else
210 thisList = llDeleteSubList(thisList, found, found + 1);
211 }
212 return thisList;
213}
214
215resetPrimShit()
216{
217 llParticleSystem([]);
218// Comment this out for now. Causes problems with updates, and is probably not needed.
219// llSetRemoteScriptAccessPin(0);
220 llSetText("", <1, 1, 1 >, 1.0);
221 llSetTextureAnim(FALSE | SMOOTH | LOOP, ALL_SIDES, 1, 1, 0, 0, 0.0);
222 llTargetOmega(<0, 0, 0 >, 0, 0);
223}
224
225// Get the next word.
226// The last parameter is the last word returned from the previous call.
227// The list results are -
228// 0 = The rest of the text.
229// 1 = The next word.
230list nextWord(string separator, string message, string last)
231{
232 list result = [];
233 integer index;
234
235 index = llSubStringIndex(message, separator);
236 if (0 <= index)
237 {
238 if (0 != index) // Check for a corner case.
239 last += llGetSubString(message, 0, index - 1);
240 if ((index + 1) < llStringLength(message))
241 message = llGetSubString(message, index + 1, -1);
242 else
243 message = "";
244 result += message;
245 result += last;
246 }
247 else
248 {
249 result += "";
250 result += last + message;
251 }
252
253 return (result);
254}
255
256// Substitute params from a list.
257string substitute(list values, string separator)
258{
259 string result = "";
260 string original = llList2String(values, 0);
261 integer length = llGetListLength(values);
262
263// integer length = (values != []); // Speed hack.
264 integer index = 1;
265
266 while ((0 <= index) && ("" != original))
267 {
268 index = llSubStringIndex(original, separator);
269 if (0 <= index)
270 {
271 string last = separator;
272 integer i;
273
274 if (0 != index) // Check for a corner case.
275 result += llGetSubString(original, 0, index - 1);
276 if ((index + 2) < llStringLength(original))
277 {
278 last = llGetSubString(original, index + 1, index + 1);
279 original = llGetSubString(original, index + 2, -1);
280 }
281 else
282 original = "";
283
284 for (i = 1; i < length; ++i)
285 {
286 string pattern = llList2String(values, i);
287
288 if (llGetSubString(pattern, 0, 0) == last)
289 {
290 last = llGetSubString(pattern, 1, -1);
291 i = length; // A break statement would be nice.
292 }
293 }
294 result += last;
295 }
296 }
297
298 return result + original;
299}
300
301startMenu(key id, list input)
302{
303 key menuUser = NULL_KEY;
304 integer menuChannel = -1;
305 key menuScript = NULL_KEY;
306 integer menuHandle = 0;
307 integer menuType = INVENTORY_NONE;
308 string menuMessage = "";
309 list menu = [];
310 list menuFilter = [];
311 integer menuPos = 0;
312 integer menuMaxPos = 1;
313// list menuNames = [];
314 integer menuIndex = llGetListLength(menus);
315
316 menuPos = 0;
317 menuScript = id;
318 menuUser = llList2Key(input, 0);
319 menuFilter = llParseStringKeepNulls(llList2String(input, 1), ["|"], []);
320 menuType = llList2Integer(menuFilter, 0);
321 if (llGetListLength(menuFilter) > 1)
322 menuFilter = llList2List(menuFilter, 1, -1);
323 else
324 menuFilter = [];
325 menuMessage = llList2String(input, 2);
326 menu = llList2List(input, 3, -1);
327 if (NULL_KEY == menuUser)
328 {
329 // Only add it if it's not there.
330 if (-1 == llListFindList(registeredMenus, [menuMessage + "|" + menuScript]))
331 registeredMenus += [menuMessage + "|" + menuScript];
332 registeredMenus = llListSort(registeredMenus, 1, TRUE);
333 return;
334 }
335 else if (INVENTORY_NONE == menuType)
336 menuMaxPos = llGetListLength(menu);
337 else if (INVENTORY_ALL == menuType) // TODO - NONE and ALL are both -1. lol
338 menuMaxPos = llGetListLength(ANIMATIONS);
339 else
340 {
341 integer i;
342 integer j;
343 integer number = llGetInventoryNumber(menuType); // NOTE this may change while we are in the menus.
344 integer length = llGetListLength(menuFilter);
345
346 menuMaxPos = 0;
347 menu= [];
348 for (i = 0; i < number; ++i)
349 {
350 string name = llGetInventoryName(menuType, i);
351
352 if (length)
353 {
354 for (j = 0; j < length; ++j)
355 {
356 if (osRegexIsMatch(name, llList2String(menuFilter, j)))
357 {
358 ++menuMaxPos;
359 menu += name;
360 }
361 }
362 }
363 else
364 {
365 ++menuMaxPos;
366 menu += name;
367 }
368 }
369 }
370 menuChannel = (integer) (llFrand(10000) + 1000);
371 menuHandle = llListen(menuChannel, "", menuUser, "");
372 menuMaxPos /= 9;
373 menuMaxPos *= 9;
374 ++menuMaxPos;
375 menus += [menuUser, menuChannel, menuScript, menuHandle, menuType, menuMessage,
376 llDumpList2String(menu, LIST_SEP), llDumpList2String(menuFilter, "|"),
377 menuPos, menuMaxPos, "", llGetTime()];
378 showMenu(menuIndex);
379}
380
381showMenu(integer menuIndex)
382{
383 key menuUser = llList2String (menus, menuIndex + MENU_USER);
384 integer menuChannel = llList2Integer(menus, menuIndex + MENU_CHANNEL);
385 key menuScript = llList2String (menus, menuIndex + MENU_SCRIPT);
386 integer menuHandle = llList2Integer(menus, menuIndex + MENU_HANDLE);
387 integer menuType = llList2Integer(menus, menuIndex + MENU_TYPE);
388 string menuMessage = llList2String (menus, menuIndex + MENU_MESSAGE);
389 list menu = llParseString2List(llList2String (menus, menuIndex + MENU_MENU), [LIST_SEP], []);
390 list menuFilter = llParseString2List(llList2String (menus, menuIndex + MENU_FILTER), ["|"], []);
391 integer menuPos = llList2Integer(menus, menuIndex + MENU_POS);
392 integer menuMaxPos = llList2Integer(menus, menuIndex + MENU_MAXPOS);
393 list menuNames = [];
394
395 list thisMenu = [];
396 integer length;
397 integer i;
398
399 for (i = 0; i < 12; ++i)
400 {
401 string name;
402 integer index;
403
404 if (INVENTORY_NONE == menuType)
405 name = llList2String(menu, menuPos + i);
406 else if (INVENTORY_ALL == menuType)
407 name = llList2String(ANIMATIONS, menuPos + i);
408 else
409 name = llList2String(menu, menuPos + i);
410// name = llGetInventoryName(menuType, menuPos + i);
411
412 index = llSubStringIndex(name, "|");
413 if (index != -1)
414 {
415 list parts = llParseStringKeepNulls(name, ["|"], []);
416 name = llGetSubString(llList2String(parts, 0) + " ", 0, 15)
417 + "|" + llList2String(parts, 1)+ "|" + llList2String(parts, 2);
418 }
419 if (llStringLength(name) > 24)
420 {
421 menuNames += [name];
422 name = llGetSubString(name, 0, 23);
423 }
424 // TODO - Only allow blank ones for arbitrary menus, but screws with the showMenu() code.
425// if ((INVENTORY_NONE == menuType) && ("" == name))
426// name = ".";
427 if ("" != name)
428 thisMenu += [name];
429 }
430
431 length = llGetListLength(thisMenu);
432 if ((12 > length) && (0 == menuPos))
433 {
434 integer j = length % 3;
435 if (0 == j)
436 thisMenu += [".", "Exit", "."];
437 else if (1 == j)
438 {
439 string last = llList2String(thisMenu, -1);
440 thisMenu = llDeleteSubList(thisMenu, -1, -1) + [last, "Exit", "."];
441 }
442 else if (2 == j)
443 {
444 string penultimate = llList2String(thisMenu, -2);
445 string last = llList2String(thisMenu, -1);
446 thisMenu = llDeleteSubList(thisMenu, -2, -1) + [penultimate, "Exit", last];
447 }
448 }
449 else if (9 >= length)
450 thisMenu += ["<<", "Exit", ">>"];
451 else
452 thisMenu = llList2List(thisMenu, 0, 8) + ["<<", "Exit", ">>"];
453
454 // Re order them to make LSL happy.
455 for (i = 0; i < length; i += 3)
456 thisMenu = llListInsertList(llDeleteSubList(thisMenu, -3, -1), llList2List(thisMenu, -3, -1), i);
457 llDialog(menuUser, menuMessage, thisMenu, menuChannel);
458 menus = llListReplaceList(menus,
459 [
460 menuUser, menuChannel, menuScript, menuHandle, menuType, menuMessage,
461 llDumpList2String(menu, LIST_SEP), llDumpList2String(menuFilter, "|"),
462 menuPos, menuMaxPos, llDumpList2String(menuNames, LIST_SEP), llGetTime()
463 ], menuIndex, menuIndex + MENU_STRIDE - 1);
464}
465
466myListen(integer channel, string name, key id, string message)
467{
468 // check if this message came from an object or avatar,
469 // use the objects owner if it came from an object.
470 // Avatars own themselves. B-)
471 key realId = llGetOwnerKey(id);
472 integer menuIndex = llListFindList(menus, [realId, channel]);
473
474 if (0 <= menuIndex) // Menu response
475 {
476 key menuUser = llList2String (menus, menuIndex + MENU_USER);
477 integer menuChannel = llList2Integer(menus, menuIndex + MENU_CHANNEL);
478 key menuScript = llList2String (menus, menuIndex + MENU_SCRIPT);
479 integer menuHandle = llList2Integer(menus, menuIndex + MENU_HANDLE);
480 integer menuType = llList2Integer(menus, menuIndex + MENU_TYPE);
481 string menuMessage = llList2String (menus, menuIndex + MENU_MESSAGE);
482 list menu = llParseString2List(llList2String (menus, menuIndex + MENU_MENU), [LIST_SEP], []);
483 list menuFilter = llParseString2List(llList2String (menus, menuIndex + MENU_FILTER), ["|"], []);
484 integer menuPos = llList2Integer(menus, menuIndex + MENU_POS);
485 integer menuMaxPos = llList2Integer(menus, menuIndex + MENU_MAXPOS);
486 list menuNames = llParseString2List(llList2String (menus, menuIndex + MENU_NAMES), [LIST_SEP], []);
487 integer delete = FALSE;
488
489 if ("<<" == message)
490 {
491 menuPos -= 9;
492 if (menuPos < 0)
493 menuPos = menuMaxPos - 1;
494 }
495 else if (">>" == message)
496 {
497 menuPos += 9;
498 if (menuPos > (menuMaxPos - 1))
499 menuPos = 0;
500 }
501 else if ("." == message)
502 delete = TRUE;
503 else
504 {
505 delete = TRUE;
506 if (menuHandle)
507 llListenRemove(menuHandle);
508 if (llStringLength(message) == 24)
509 {
510 integer i;
511 integer length = llGetListLength(menuNames);
512
513 for (i = 0; i < length; ++i)
514 {
515 string lName = llList2String(menuNames, i);
516
517 if (message == llGetSubString(lName, 0, 23))
518 message = lName;
519 }
520 }
521
522 if (NULL_KEY == menuScript)
523 {
524 menuScript = (key) llList2String(llParseStringKeepNulls(message, ["|"], []), 2);
525 message = "";
526 }
527 llMessageLinked(LINK_SET, UTILITIES_MENU_DONE, llDumpList2String([menuUser, message], LIST_SEP), menuScript);
528 }
529 if (delete)
530 menus = llDeleteSubList(menus, menuIndex, menuIndex + MENU_STRIDE - 1);
531 else
532 {
533 menus = llListReplaceList(menus,
534 [
535 menuUser, menuChannel, menuScript, menuHandle, menuType, menuMessage,
536 llDumpList2String(menu, LIST_SEP), llDumpList2String(menuFilter, "|"),
537 menuPos, menuMaxPos, llDumpList2String(menuNames, LIST_SEP), llGetTime()
538 ], menuIndex, menuIndex + MENU_STRIDE - 1);
539 showMenu(menuIndex);
540 }
541 }
542 else // Chat command.
543 {
544 list scripts = [];
545 list words = [];
546 string prefix = "";
547 string command = "";
548 string idChannel = (string) realId + (string) channel;
549 integer thisOwner = llListFindList(chatOwners, [idChannel]);
550
551//llOwnerSay("->>" + (string) channel + " " + message);
552
553//llOwnerSay("owners " + llDumpList2String(chatOwners, "^"));
554//llOwnerSay("channels " + llDumpList2String(chatChannels, "^"));
555//llOwnerSay("prefixes " + llDumpList2String(chatPrefixes, "^"));
556//llOwnerSay("prefixes2 " + llDumpList2String(chatPrefixes2, "^"));
557//llOwnerSay("commands " + llDumpList2String(chatCommands, "^"));
558 if (0 <= thisOwner)
559 scripts = llParseString2List(llList2String(chatOwners, thisOwner + 1), ["|"], []);
560 else
561 {
562 integer thisChannel = llListFindList(chatChannels, [(string) channel]);
563
564 if (0 <= thisChannel)
565 scripts = llParseString2List(llList2String(chatChannels, thisChannel + 1), ["|"], []);
566 }
567//llOwnerSay(llDumpList2String(scripts, "|"));
568 if ([] != scripts)
569 {
570 integer thisPrefix;
571 string candidate;
572
573 words = llParseString2List(message, [" "], []);
574 candidate = llList2String(words, 0);
575 thisPrefix = llListFindList(chatPrefixes, [(string) channel + " " + candidate]);
576//llOwnerSay(llDumpList2String(words, "~"));
577//llSay(0, candidate);
578//llSay(0, (string) thisPrefix);
579 if (0 <= thisPrefix)
580 {
581 prefix = candidate;
582 scripts = llParseString2List(llList2String(chatPrefixes, thisPrefix + 1), ["|"], []);
583 words = llList2List(words, 1, -1);
584 }
585 else
586 {
587 integer length = llGetListLength(chatPrefixes2);
588 integer i;
589
590 for (i = 0; i < length; i += 2)
591 {
592 string pName = llList2String(chatPrefixes2, i);
593 integer pLength = llStringLength(pName);
594
595 if (pName == ((string) channel + " " + llGetSubString(candidate, 0, pLength)))
596 {
597// prefix = pName;
598 prefix = candidate;
599 scripts = llParseString2List(llList2String(chatPrefixes2, i + 1), ["|"], []);
600// words = [llGetSubString(candidate, pLength + 1, -1)] + llList2List(words, 1, -1);
601 words = llList2List(words, 1, -1);
602 i = length;
603 }
604 }
605 }
606 }
607//llOwnerSay(llDumpList2String(scripts, "|"));
608//llSay(0, prefix);
609
610 // Finally found it, process it.
611 if ([] != scripts)
612 {
613 integer length = llGetListLength(scripts);
614 integer wordLength = llGetListLength(words);
615 integer i;
616
617 // Wont put up with laggy scripts. Use a prefix if you insist on using local chat.
618// if ((0 == channel) && ("" == prefix))
619// return;
620
621 // The problem with this loop is that if a bunch of scripts are wanting the same commands,
622 // then it's not efficient. In the expected use cases, that should not me much of a problem.
623 for (i = 0; i < length; ++i)
624 {
625 string script = llList2String(scripts, i);
626 integer theseCommands = llListFindList(chatCommands, [script + (string) channel]);
627
628 if (0 <= theseCommands)
629 {
630 list commands = llParseStringKeepNulls(llList2String(chatCommands, theseCommands + 1), ["|"], []);
631 integer thisCommand = llListFindList(commands, [llList2String(words, 0)]);
632//llOwnerSay(llDumpList2String(words, "~"));
633//llOwnerSay(llDumpList2String(commands, "~"));
634//llSay(0, (string) thisCommand);
635
636 if (0 != (thisCommand % 2))
637 thisCommand = -1;
638 if (0 <= thisCommand)
639 {
640 list result = [channel, name, realId, message, prefix, llList2String(commands, thisCommand)];
641 string argsTypes = llList2String(commands, thisCommand + 1);
642 integer argsLength = llStringLength(argsTypes);
643 integer required = 0;
644 integer optional = 0;
645 integer multiple = -1;
646// integer oldStyle = FALSE;
647
648 if (0 < argsLength) // Arguments expected.
649 {
650 required = (integer) llGetSubString(argsTypes, 0, 0);
651 optional = (integer) llGetSubString(argsTypes, 1, 1);
652
653 {
654 list arguments = []; // type, required, extra
655 integer a;
656 integer argsCount = 0;
657 integer w = 1;
658
659 for (a = 0; a < argsLength; a++)
660 {
661 string type = llGetSubString(argsTypes, a, a);
662 string TYPE = llToUpper(type);
663 integer isNeeded = (TYPE == type);
664 string extra = "";
665
666 if (isNeeded)
667 ++required;
668 else
669 ++optional;
670 // Sort out the extra string for those that support it.
671 if (("C" == TYPE) || ("X" == TYPE))
672 {
673 string subbie = llGetSubString(argsTypes, a + 1, -1);
674 integer comma = llSubStringIndex(subbie, ",");
675
676 if (-1 == comma)
677 {
678 extra = llGetSubString(argsTypes, a + 1, -1);
679 a = argsLength;
680 }
681 else
682 {
683 extra = llGetSubString(argsTypes, a + 1, a + 1 + comma - 1);
684 a += comma + 1;
685 }
686 }
687 arguments += [TYPE, isNeeded, extra];
688 ++argsCount;
689 }
690 for (a = 0; a < argsCount; ++a)
691 {
692 string TYPE = llList2String (arguments, (a * 3) + 0);
693 integer isNeeded = llList2Integer(arguments, (a * 3) + 1);
694 string extra = llList2String (arguments, (a * 3) + 2);
695 string value = "";
696
697 // a animation, b bodypart, clothing, g gesture, m landmark, o object, sound, t texture.
698 // c notecard, x script, Extension is what follows up to the next comma, or end of line.
699 // f float, i integer, k key, r rotation, s rest of string, v vector.
700 // n name of avatar or NPC. Full two word variety, no other checking done.
701 // l local name, could be just first or last name, but they need to be in the sim.
702 if ("F" == TYPE)
703 value = (string) llList2Float(words, w);
704 else if ("I" == TYPE)
705 value = (string) llList2Integer(words, w);
706 else if ("K" == TYPE)
707 value = (key) llList2String(words, w);
708 else if (("R" == TYPE) || ("V" == TYPE))
709 {
710 string next = llList2String(words, w);
711
712 if ("<" == llGetSubString(next, 0, 0))
713 {
714 integer l;
715
716 for (l = 0; (w + l) < wordLength; ++l)
717 {
718 value += next;
719 if (">" == llGetSubString(value, -1, -1))
720 {
721 w += l;
722 l = wordLength; // BREAK!
723 }
724 if (("V" == TYPE) && (3 <= l))
725 l = wordLength; // BREAK!
726 else if (("R" == TYPE) && (4 <= l))
727 l = wordLength; // BREAK!
728 next = llList2String(words, w + 1 + l);
729 }
730 if (">" != llGetSubString(value, -1, -1)) // Seems OpenSim at least can cast partial vectors.
731 value = "";
732 if ("V" == TYPE)
733 {
734 vector v = (vector) value;
735 value = (string) v;
736 }
737 else if ("R" == TYPE)
738 {
739 rotation r = (rotation) value;
740 value = (string) r;
741 }
742 }
743 }
744 else if ("L" == TYPE)
745 {
746 string first = llList2String(words, w);
747 string last = llList2String(words, w + 1);
748
749 if (osIsUUID(first))
750 value = first;
751 else if ("" != last)
752 {
753 list avatars = [llGetOwner(), ZERO_VECTOR, llKey2Name(llGetOwner())] + osGetAvatarList(); // Strided list, UUID, position, name.
754 integer avaLength = llGetListLength(avatars);
755 string aName = llToLower(first + " " + last);
756 integer n;
757
758 for (n = 0; n < avaLength; n +=3)
759 {
760 if (llToLower(llList2String(avatars, n + 2)) == aName)
761 {
762 value = llKey2Name(llList2String(avatars, n));
763 ++w;
764 n = avaLength; // BREAK!
765 }
766 }
767 }
768 if (("" == value) && ("" != first)) // Try scanning the sim for a matching first or last name.
769 {
770 list candidates = [];
771 list avatars = [llGetOwner(), ZERO_VECTOR, llKey2Name(llGetOwner())] + osGetAvatarList(); // Strided list, UUID, position, name.
772 integer avaLength = llGetListLength(avatars);
773 integer n = llSubStringIndex(first, "'");
774
775 // Check if we are searching for multiples.
776 // There can be only one multiple, and it should be the first,
777 // so skip multiples checking if multiple is set already.
778 if ((-1 != n) && (-1 == multiple))
779 {
780 multiple = a;
781 first = llGetSubString(first, 0, n - 1);
782 }
783 last = llToLower(first);
784 for (n = 0; n < avaLength; n +=3)
785 {
786 list names = llParseString2List(llToLower(llList2String(avatars, n + 2)), [" "], []);
787
788 if ((llList2String(names, 0) == last) || (llList2String(names, 1) == last))
789 candidates += [llList2String(avatars, n)];
790 }
791 avaLength = llGetListLength(candidates);
792 if (0 == avaLength)
793 llSay(0, "No one matching the name " + first + " here.");
794 else if ((1 < avaLength) && (-1 == multiple))
795 llSay(0, "More than one matching the name " + first + " here.");
796 else if (-1 != multiple)
797 value = llDumpList2String(candidates, "|");
798 else
799 value = llKey2Name(llList2String(candidates, 0));
800 }
801 }
802 else if ("N" == TYPE)
803 {
804 string first = llList2String(words, w);
805 string last = llList2String(words, w + 1);
806
807 if (osIsUUID(first))
808 value = first;
809 else if ("" != last)
810 {
811 value = first + " " + last;
812 ++w;
813 }
814 }
815 else // The rest are "rest of string".
816 {
817 if (w < wordLength)
818 result += [llDumpList2String(llList2List(words, w, -1), " ")];
819 w = wordLength;
820 }
821 result += [value];
822 ++w;
823 if (w > wordLength)
824 a = argsCount; // BREAK!
825 }
826
827 // Put the rest of the words back together as "rest of message".
828 if (w < wordLength)
829 result += [llDumpList2String(llList2List(words, w, -1), " ")];
830//llSay(0, "ARGUMENTS for " + llList2String(words, 0) + " = " + llDumpList2String(arguments, "|"));
831//llSay(0, "RESULTS " + llDumpList2String(result, "~"));
832 }
833 }
834
835 if ((1 + required) > wordLength)
836 {
837 // bitch
838 if (id != realId)
839 llInstantMessage(realId, "Not enough required arguments in a command from your object " + llKey2Name(id) + " The command was - " + message);
840 else
841 llInstantMessage(realId, "Not enough required arguments in your command. The command was - " + message);
842 }
843 else
844 {
845 // RETURNS incoming channel | incoming name | incoming key | incoming message | prefix | command | list of arguments | rest of message
846 if (-1 != multiple)
847 {
848 list candidates = llParseString2List(llList2String(result, 6 + multiple), ["|"], []);
849 integer candiLength = llGetListLength(candidates);
850 integer c;
851
852 for (c = 0; c < candiLength; ++c)
853 {
854 result = llListReplaceList(result, [llList2String(candidates, c)], 6 + multiple, 6 + multiple);
855 llMessageLinked(LINK_SET, UTILITIES_CHAT_DONE, llDumpList2String(result, LIST_SEP), (key) script);
856 }
857 }
858 else
859 llMessageLinked(LINK_SET, UTILITIES_CHAT_DONE, llDumpList2String(result, LIST_SEP), (key) script);
860 }
861 }
862 }
863 }
864 }
865 }
866}
867
868startNextRead()
869{
870 if (0 < llGetListLength(settingsCards))
871 {
872 settingsName = llList2String(settingsCards, 0);
873 settingsKey = llList2Key(settingsCards, 1);
874 settingsLine = 0;
875 settingsQueryID = llGetNotecardLine(settingsName, settingsLine); // request first line
876 settingsCards = llDeleteSubList(settingsCards, 0, 1);
877 }
878 else
879 {
880 settingsName = ".settings";
881 settingsKey = NULL_KEY;
882 settingsQueryID = NULL_KEY;
883 }
884}
885
886list readThisLine(string data)
887{
888 list result = [];
889
890 data = llStringTrim(data, STRING_TRIM);
891 if ((0 < llStringLength(data)) && ("#" != llGetSubString(data, 0, 0)))
892 {
893 list commands = llParseStringKeepNulls(data, [";"], []);
894 list new = [];
895 string newCommand = "";
896 integer length = llGetListLength(commands);
897 integer i;
898
899 for (i = 0; i < length; ++i)
900 {
901 string command = llList2String(commands, i);
902
903 // Check for line continuation. I think. lol
904 if ("\\" == llGetSubString(command, -1, -1))
905 newCommand += llGetSubString(command, 0, -2) + ";";
906 else
907 {
908 command = llStringTrim(newCommand + command, STRING_TRIM);
909 if (0 < llStringLength(command))
910 new += [command];
911 newCommand = "";
912 }
913//llOwnerSay("|" + newCommand + "|" + command + "|");
914 }
915
916 length = llGetListLength(new);
917 for (i = 0; i < length; ++i)
918 {
919 string name;
920 string value = llList2String(new, i);
921 integer equals = llSubStringIndex(value, "=");
922
923 name = "";
924 if (0 <= equals)
925 {
926 name = llStringTrim(llGetSubString(value, 0, equals - 1), STRING_TRIM_TAIL);
927 if ((equals + 1) < llStringLength(value))
928 value = llStringTrim(llGetSubString(value, equals + 1, -1), STRING_TRIM_HEAD);
929 else
930 value = "";
931 }
932 else
933 {
934 name = value;
935 value = "";
936 }
937 result += [name, value];
938 }
939 }
940 ++settingsLine;
941 return result;
942}
943
944init()
945{
946 llMessageLinked(LINK_SET, UTILITIES_RESET_DONE, "", llGetInventoryKey(llGetScriptName()));
947 // Pointless in OpenSim, always reports 16384. Pffft
948 //llOwnerSay("Free memory " + (string) llGetFreeMemory() + " in " + llGetScriptName());
949 llSetTimerEvent(MENU_TIMEOUT);
950}
951
952
953default
954{
955 state_entry()
956 {
957 init();
958 }
959
960 on_rez(integer param)
961 {
962 init();
963 }
964
965 attach(key attached)
966 {
967 init();
968 }
969
970 listen(integer channel, string name, key id, string message)
971 {
972 myListen(channel, name, id, message);
973 }
974
975 // Handle commands from other scripts.
976 // Negative odd values of num are the commands, return num - 1 as the result.
977 link_message(integer sender_num, integer num, string value, key id)
978 {
979 list input = llParseStringKeepNulls(value, [LIST_SEP], []);
980
981 if (UTILITIES_RESET == num) // Request stuff to be reset
982 {
983 if ("reset" == value) // Request us to be reset.
984 llResetScript();
985 resetPrimShit();
986 llMessageLinked(LINK_SET, num - 1, value, id);
987 }
988 else if (UTILITIES_READ == num) // Request a notecard to be read.
989 {
990 list result = [];
991 integer length = osGetNumberOfNotecardLines(value);
992
993 settingsLine = 0;
994 while (settingsLine <= length)
995 {
996 result += readThisLine(osGetNotecardLine(value, settingsLine));
997 if (0 == (settingsLine % 500)) // Don't send too many at once.
998 {
999 integer percent = (integer) ((((float) settingsLine) / ((float) length)) * 100.0);
1000
1001 llSay(0, "Reading '" + value + "' " + (string) percent + "% done.");
1002 // Sending a negative line to try to avoid triggering foreign scripts.
1003 // Sending from -1000 downwards to avoid triggering our scripts.
1004 llMessageLinked(LINK_SET, -1000 - settingsLine, llDumpList2String([value] + result, LIST_SEP), id);
1005 result = [];
1006 }
1007 }
1008 // Send the last batch.
1009 if (0 != llGetListLength(result))
1010 llMessageLinked(LINK_SET, -1000 - settingsLine, llDumpList2String([value] + result, LIST_SEP), id);
1011 llMessageLinked(LINK_SET, UTILITIES_READ_DONE, value, id);
1012 }
1013 else if (UTILITIES_SUBSTITUTE == num) // Request a param substitution.
1014 {
1015 llMessageLinked(LINK_SET, num - 1, substitute([substitute(input, "%"), "n\n", "t\t", "\\\\", "\"\""], "\\"), id);
1016 }
1017 else if (UTILITIES_NEXT_WORD == num) // Get the next word
1018 {
1019 llMessageLinked(LINK_SET, num - 1, llDumpList2String(nextWord(llList2String(input, 0), llList2String(input, 1), llList2String(input, 2)), LIST_SEP), id);
1020 }
1021 else if (UTILITIES_MENU == num) // Request big menu to be displayed
1022 {
1023 startMenu(id, input);
1024 }
1025 else if (UTILITIES_CHAT == num)
1026 {
1027 // channel list | owner list | prefix list | command list
1028 list channels = llParseString2List(llList2String(input, 0), ["|"], []);
1029 list owners = llParseString2List(llList2String(input, 1), ["|"], []);
1030 list prefixes = llParseString2List(llList2String(input, 2), ["|"], []);
1031 string commands = llList2String(input, 3);
1032 integer chLength = llGetListLength(channels);
1033 integer i;
1034
1035 for (i = 0; i < chLength; ++i)
1036 {
1037 string channel = llList2String(channels, i);
1038 integer j;
1039
1040 if ("" != commands)
1041 {
1042//llSay(0, "ADDING " + channel + "= " + commands);
1043 integer oLength = llGetListLength(owners);
1044 integer pLength = llGetListLength(prefixes);
1045 integer found = llListFindList(channelHandles, [channel]);
1046
1047 if ("0" == channel)
1048 llOwnerSay("WARNING: Script using local chat for commands may cause lag - " + llKey2Name(id));
1049 chatChannels = addChatScripts(chatChannels, channel, (string) id, 1);
1050 chatCommands = addChatScripts(chatCommands, (string) id + channel, commands, 2);
1051 for (j = 0; j < oLength; ++j)
1052 chatOwners = addChatScripts(chatOwners, llList2String(owners, j) + channel, (string) id, 1);
1053
1054 for (j = 0; j < pLength; ++j)
1055 {
1056 string prefix = llList2String(prefixes, j);
1057
1058 if (" " == llGetSubString(prefix, -1, -1))
1059 chatPrefixes = addChatScripts(chatPrefixes, channel + " " + llGetSubString(prefix, 0, -2), (string) id, 1);
1060 else
1061 chatPrefixes2 = addChatScripts(chatPrefixes2, channel + " " + prefix, (string) id, 1);
1062 }
1063
1064 // Do this last, so all the rest is setup already.
1065 if (0 > found)
1066 {
1067//llOwnerSay("LISTEN " + channel + " " + llList2String(owners, j));
1068// TODO - Closing and reopening that channel if details change.
1069 // NOTE - only the FIRST owner is supported.
1070 // TODO - this is not right anyway.
1071 // If a channel gets more than one owner from different invocations of this command,
1072 // then it should re open the listener.
1073 if (1 == oLength)
1074 channelHandles += [channel, llListen((integer) channel, "", llList2Key(owners, 0), "")];
1075 else
1076 channelHandles += [channel, llListen((integer) channel, "", NULL_KEY, "")];
1077 }
1078 }
1079 else // if ("" != commands)
1080 {
1081 integer index = llListFindList(chatCommands, [(string) id + channel]);
1082 // Yes, I know, UUIDs are a fixed length, but there's talk of using SHA1 hashes instead.
1083 integer keyLength = llStringLength((string) id);
1084 integer length;
1085
1086 chatChannels = delChatScripts(chatChannels, channel, (string) id, 1);
1087 if (0 <= index)
1088 chatCommands = llDeleteSubList(chatCommands, index, index + 1);
1089
1090 length = llGetListLength(chatOwners);
1091 for (j = 0; j < length; j += 2)
1092 {
1093 string this = llList2String(chatOwners, j);
1094
1095 if (llGetSubString(this, keyLength, -1) == channel)
1096 chatOwners = delChatScripts(chatOwners, this, (string) id, 1);
1097 }
1098
1099// TODO - go through chatPrefixes/2 removing script from any on this channel
1100
1101 if (0 > llListFindList(chatChannels, [channel]))
1102 {
1103 length = llGetListLength(channelHandles);
1104 for (j = 0; j < length; j += 2)
1105 {
1106 if (llList2String(channelHandles, j) == channel)
1107 {
1108 llListenRemove(llList2Integer(channelHandles, j + 1));
1109 channelHandles = llDeleteSubList(channelHandles, j, j + 1);
1110 length -= 2;
1111 j -= 2;
1112 }
1113 }
1114 }
1115//llOwnerSay("owners " + llDumpList2String(chatOwners, "^"));
1116//llOwnerSay("channels " + llDumpList2String(chatChannels, "^"));
1117//llOwnerSay("prefixes " + llDumpList2String(chatPrefixes, "^"));
1118//llOwnerSay("prefixes2 " + llDumpList2String(chatPrefixes2, "^"));
1119//llOwnerSay("commands " + llDumpList2String(chatCommands, "^"));
1120 } // if ("" != commands)
1121 } // for (i = 0; i < chLength; ++i)
1122
1123 }
1124 else if (UTILITIES_CHAT_FAKE == num)
1125 {
1126 myListen(llList2Integer(input, 0), llList2String(input, 1), llList2Key(input, 2), llList2String(input, 3));
1127 }
1128 }
1129
1130 touch_start(integer num)
1131 {
1132 integer length = llGetListLength(registeredMenus);
1133 integer i;
1134
1135 // Scan through the list, checking if those scripts still exist in inventory.
1136 for (i = length - 1; i >= 0; --i)
1137 {
1138 string this = llList2String(llParseStringKeepNulls(llList2String(registeredMenus, 0), ["|"], []), 1);
1139
1140 if (INVENTORY_NONE == llGetInventoryType(this))
1141 {
1142 registeredMenus = llDeleteSubList(registeredMenus, i, i);
1143 --length;
1144 }
1145 }
1146 for (i = 0; i < num; ++i)
1147 {
1148 key id = llDetectedKey(i);
1149 string desc = llList2String(llGetObjectDetails(llGetLinkKey(llDetectedLinkNumber(i)), [OBJECT_DESC]), 0);
1150
1151 // If there's a description, then it's likely a scriptlet.
1152 if ("" != desc)
1153 myListen(0, llKey2Name(id), id, desc); // TODO - the problem here is that the first argument is a channel,
1154 // and we don't know which channel to fake.
1155 // Maybe use the debug channel as a wildcard?
1156 else if (1 == length) // Only one registered, select it directly.
1157 {
1158 llMessageLinked(LINK_SET, UTILITIES_MENU_DONE, llDumpList2String([id, ""], LIST_SEP),
1159 llList2String(llParseStringKeepNulls(llList2String(registeredMenus, 0), ["|"], []), 2));
1160 }
1161 else if (0 != length) // More than one, put up a menu of them.
1162 startMenu(NULL_KEY, [id, INVENTORY_NONE, "Choose a function :"] + registeredMenus);
1163 // If there's zero registered menus, then do nothing.
1164 }
1165 }
1166
1167 timer()
1168 {
1169 integer length = llGetListLength(menus);
1170 float time = llGetTime();
1171 integer i;
1172
1173 // Run through menus, removing any that timed out.
1174 for (i = 0; i < length; i += MENU_STRIDE)
1175 {
1176 if (time > (llList2Float(menus, i + MENU_TIME) + MENU_TIMEOUT))
1177 {
1178 integer menuHandle = llList2Integer(menus, i + MENU_HANDLE);
1179
1180 llSay(0, "Menu for " + llKey2Name(llList2String(menus, i + MENU_USER)) + " timed out.");
1181 if (menuHandle)
1182 llListenRemove(menuHandle);
1183 menus = llDeleteSubList(menus, i, i + MENU_STRIDE - 1);
1184 length = llGetListLength(menus);
1185 i -= MENU_STRIDE;
1186 }
1187 }
1188 }
1189}