diff options
Diffstat (limited to 'onefang's_utilities.lsl')
-rw-r--r-- | onefang's_utilities.lsl | 1189 |
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 | |||
41 | float MENU_TIMEOUT = 45.0; | ||
42 | |||
43 | list channelHandles = []; // channel | listener | ||
44 | list chatChannels = []; // channel | script list | ||
45 | list chatOwners = []; // owner+channel | script list | ||
46 | list chatPrefixes = []; // channel+" "+prefix | script list (PREFIXES WITH SPACE) | ||
47 | list chatPrefixes2 = []; // channel+" "+prefix | script list (PREFIXES WITH NO SPACE) | ||
48 | list 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. | ||
54 | string LIST_SEP = "$!#"; // Used to seperate lists when sending them as strings. | ||
55 | integer UTILITIES_RESET = -1; | ||
56 | integer UTILITIES_RESET_DONE = -2; | ||
57 | integer UTILITIES_READ = -3; | ||
58 | integer UTILITIES_READ_DONE = -4; | ||
59 | integer UTILITIES_SUBSTITUTE = -5; | ||
60 | integer UTILITIES_SUBSTITUTE_DONE = -6; | ||
61 | integer UTILITIES_NEXT_WORD = -7; | ||
62 | integer UTILITIES_NEXT_WORD_DONE = -8; | ||
63 | integer UTILITIES_PAYMENT_MADE = -9; // Not used by this script | ||
64 | integer UTILITIES_PAYMENT_MADE_DONE = -10; // Not used by this script | ||
65 | integer UTILITIES_MENU = -11; | ||
66 | integer UTILITIES_MENU_DONE = -12; | ||
67 | integer UTILITIES_WARP = -13; | ||
68 | integer UTILITIES_WARP_DONE = -14; | ||
69 | integer UTILITIES_AVATAR_KEY = -15; // Redundant in OpenSim. | ||
70 | integer UTILITIES_AVATAR_KEY_DONE = -16; // Redundant in OpenSim. | ||
71 | integer UTILITIES_CHAT = -17; | ||
72 | integer UTILITIES_CHAT_DONE = -18; | ||
73 | integer UTILITIES_CHAT_FAKE = -19; | ||
74 | integer 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. | ||
79 | integer KEYS_SCRIPT = 0; | ||
80 | integer KEYS_TYPE = 1; | ||
81 | integer KEYS_NAME = 2; | ||
82 | integer KEYS_EXTRA = 3; | ||
83 | integer KEYS_ID = 4; | ||
84 | integer KEYS_STRIDE = 5; | ||
85 | |||
86 | string REQUEST_KEY = "1"; | ||
87 | string REQUEST_IM = "2"; | ||
88 | string REQUEST_ONLINE = "3"; | ||
89 | string REQUEST_AVATAR = "4"; | ||
90 | |||
91 | integer MENU_USER = 0; // Key of user the big menu is for. | ||
92 | integer MENU_CHANNEL = 1; // Listen channel for big menu dialogs. | ||
93 | integer MENU_SCRIPT = 2; // ScriptKey of the script calling the menu. | ||
94 | integer MENU_HANDLE = 3; // Listen ID for big menu dialogs. | ||
95 | integer MENU_TYPE = 4; // Type of menu. | ||
96 | integer MENU_MESSAGE = 5; // Message for the top of big menu dialog. | ||
97 | integer MENU_MENU = 6; // The big menu itself. LIST_SEP separated when they come in. | ||
98 | integer MENU_FILTER = 7; // Regex filter for inventory menus. | separated when they come in. | ||
99 | integer MENU_POS = 8; // Current position within big menu. | ||
100 | integer MENU_MAXPOS = 9; // Maximum position within big menu. | ||
101 | integer MENU_NAMES = 10; // Long names to get around stupid SL menu limitations. | ||
102 | integer MENU_TIME = 11; // Otherwise this list is ever growing. Damn Ignore button. sigh | ||
103 | integer MENU_STRIDE = 12; | ||
104 | |||
105 | list settingsCards = []; // Queue of cards to read. | ||
106 | string settingsName = ".settings"; // Name of a notecard in the object's inventory. | ||
107 | key settingsKey = NULL_KEY; // ScriptKey of the script reading the card. | ||
108 | integer settingsLine = 0; // Current line number. | ||
109 | key settingsQueryID = NULL_KEY; // ID used to identify dataserver queries. | ||
110 | |||
111 | list menus = []; // The menus. | ||
112 | list registeredMenus = []; // Any scripts that have registered as needing a touch menu. | ||
113 | list keyRequests = []; // A list of avatar key requests. | ||
114 | |||
115 | list 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 | |||
142 | list 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 | |||
178 | list 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 | |||
215 | resetPrimShit() | ||
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. | ||
230 | list 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. | ||
257 | string 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 | |||
301 | startMenu(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 | |||
381 | showMenu(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 | |||
466 | myListen(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 | |||
868 | startNextRead() | ||
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 | |||
886 | list 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 | |||
944 | init() | ||
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 | |||
953 | default | ||
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 | } | ||