diff options
-rw-r--r-- | 1AOor2.lsl | 1835 | ||||
-rw-r--r-- | 1chatter.lsl | 1597 | ||||
-rw-r--r-- | 1ring.lsl | 1424 | ||||
-rw-r--r-- | OhSillyThreatDetector.lsl | 59 | ||||
-rw-r--r-- | onefang's leash holder.lsl | 44 |
5 files changed, 4959 insertions, 0 deletions
diff --git a/1AOor2.lsl b/1AOor2.lsl new file mode 100644 index 0000000..7b460b6 --- /dev/null +++ b/1AOor2.lsl | |||
@@ -0,0 +1,1835 @@ | |||
1 | |||
2 | // AO and couples interactions. An Animation Overrider with a swimmer and a smiler, which are also AO type things. | ||
3 | // evry byt cnts | ||
4 | |||
5 | string Version = "1AOor2 v0.1 test version"; | ||
6 | |||
7 | // BEGIN boilerplate. | ||
8 | integer DEBUG = FALSE; | ||
9 | float Start; | ||
10 | string ScriptName; | ||
11 | key ScriptKey; | ||
12 | key LibraryKey; | ||
13 | key Owner; | ||
14 | string URL; | ||
15 | |||
16 | // Settings. | ||
17 | list Aliases; | ||
18 | list Settings; | ||
19 | integer sNAME = 0; | ||
20 | integer sTYPE = 1; | ||
21 | integer sVALUE = 2; | ||
22 | integer sAUTH = 3; | ||
23 | integer sSTRIDE = 4; | ||
24 | |||
25 | // Access "f"rom some source - | ||
26 | integer fINT = 0; | ||
27 | integer fCARD = 1; | ||
28 | integer fCHAT = 2; | ||
29 | integer fLINK = 3; | ||
30 | integer fMENU = 4; | ||
31 | integer fRLV = 5; | ||
32 | integer fRELAY = 6; | ||
33 | integer fOSM = 7; | ||
34 | integer fHTTP = 8; | ||
35 | |||
36 | // utilities commands, "l"ibrary. | ||
37 | string lSEP = "$!#"; | ||
38 | integer lRESET = -1; | ||
39 | integer lRESET_DONE = -2; | ||
40 | integer lALIAS = -3; | ||
41 | integer lALIAS_DONE = -4; | ||
42 | integer lSETTING = -5; | ||
43 | integer lSETTING_DONE = -6; | ||
44 | integer lSUBSTITUTE = -7; | ||
45 | integer lSUBSTITUTE_DONE = -8; | ||
46 | integer lNEXT_WORD = -9; | ||
47 | integer lNEXT_WORD_DONE = -10; | ||
48 | integer lCONTROL = -13; | ||
49 | integer lCONTROL_DONE = -14; | ||
50 | integer lCMD = -15; | ||
51 | integer lCMD_DONE = -16; | ||
52 | integer lSETTINGS = -17; | ||
53 | integer lSETTINGS_DONE = -18; | ||
54 | integer lSCAN = -19; | ||
55 | integer lDYNAMIC = -20; | ||
56 | integer lMENU = -21; | ||
57 | |||
58 | // OhSillyThreat detector | ||
59 | list OhSillyThreats = []; | ||
60 | list OhSillyTreats = // OpenSim threat level. | ||
61 | [ | ||
62 | // "osKey2Name", // low | ||
63 | // "osGetAvatarList", // none | ||
64 | "osGetNotecard", // very high (describes what they where when making this decision) | ||
65 | // "osMakeNotecard", // high (describes what they where when making this decision) | ||
66 | // "osGetRezzingObject", // none | ||
67 | "osMessageObject", // low | ||
68 | "osAvatarPlayAnimation", // very high | ||
69 | "osAvatarStopAnimation", // very high | ||
70 | "osForceOtherSit", // very high | ||
71 | "osSetSpeed" // moderate | ||
72 | ]; | ||
73 | |||
74 | d(string m) {if (DEBUG) llInstantMessage(Owner, llGetScriptName() + ": " + m);} | ||
75 | D(string m) {llRegionSay(DEBUG_CHANNEL, llGetScriptName() + ": " + m);} | ||
76 | s(string m) {s(Owner, m);} | ||
77 | s(key id, string m) {if (id == Owner) llOwnerSay(m); else llInstantMessage(id, m);} | ||
78 | sendScript(integer cmd, list args) {sendScript(LibraryKey, cmd, ScriptName, args);} | ||
79 | sendScript(key them, integer cmd, list args) {sendScript(them, cmd, inKey2Name(them), args);} | ||
80 | sendScript(integer cmd, string name, list args) {sendScript(LibraryKey, cmd, name, args);} | ||
81 | sendScript(key them, integer cmd, string name, list args) | ||
82 | { | ||
83 | llMessageLinked(LINK_SET, cmd, llDumpList2String([ScriptKey, name] + args, lSEP), them); | ||
84 | } | ||
85 | sendPrim(key them, string cmd, list args) | ||
86 | { | ||
87 | osMessageObject(them, llDumpList2String([ScriptName, cmd] + args, lSEP)); | ||
88 | } | ||
89 | addEvent(float delay, string cmds) | ||
90 | { | ||
91 | sendScript(lCMD, [fINT, ScriptKey, "TimerEvent", "TIMER", ((string) delay) + " " + cmds]); | ||
92 | } | ||
93 | string inKey2Name(key k) | ||
94 | { | ||
95 | if (k == LibraryKey) return "1chatter"; | ||
96 | integer i = llGetInventoryNumber(INVENTORY_SCRIPT); | ||
97 | while (i-- > 0) | ||
98 | { | ||
99 | string n = llGetInventoryName(INVENTORY_SCRIPT, i); | ||
100 | if (llGetInventoryKey(n) == k) return n; | ||
101 | } | ||
102 | return k; | ||
103 | } | ||
104 | |||
105 | integer listFindString(list lst, string name, integer stride) | ||
106 | { | ||
107 | integer f = llListFindList(lst, [name]); | ||
108 | integer ix = f / stride; | ||
109 | ix = ix * stride; | ||
110 | if ((-1 != f) && (ix != f)) | ||
111 | { | ||
112 | integer l = llGetListLength(lst); | ||
113 | integer i; | ||
114 | f = -1; | ||
115 | for (i = 0; i < l; i += stride) | ||
116 | { | ||
117 | if (llList2String(lst, i) == name) {f = i; i = l;} | ||
118 | } | ||
119 | } | ||
120 | return f; | ||
121 | } | ||
122 | |||
123 | // Note these next two are different from the ones in 1chatter. | ||
124 | string alias(string n) | ||
125 | { | ||
126 | list v = validateName(n, "alias"); | ||
127 | n = llList2String(v, 1); | ||
128 | integer a = listFindString(Aliases, llList2String(v, 0) + llToLower(n), 2); | ||
129 | if (-1 != a) n = llList2String(Aliases, a + 1); | ||
130 | return n; | ||
131 | } | ||
132 | |||
133 | list validateName(string var, string type) | ||
134 | { | ||
135 | list v = llParseStringKeepNulls(var, ["."], []); | ||
136 | if (2 != llGetListLength(v)) v = ScriptName + v; | ||
137 | var = llList2String(v, 1); | ||
138 | if ("setting" == type) var = llToUpper(var); | ||
139 | return [llList2String(v, 0) + ".", var]; | ||
140 | } | ||
141 | |||
142 | string getSetting(string var) | ||
143 | { | ||
144 | list v = validateName(var, "setting"); | ||
145 | var = llList2String(v, 0) + llList2String(v, 1); | ||
146 | string result = ""; | ||
147 | integer f = listFindString(Settings, var, sSTRIDE); | ||
148 | if (-1 != f) result = llList2String(Settings, f + sVALUE); | ||
149 | return result; | ||
150 | } | ||
151 | |||
152 | setSetting(key id, string var, string val, integer source) | ||
153 | { | ||
154 | list v = validateName(var, "setting"); | ||
155 | string fr = llList2String(v, 0); | ||
156 | var = fr + llList2String(v, 1); | ||
157 | integer f = listFindString(Settings, var, sSTRIDE); | ||
158 | if (-1 != f) | ||
159 | { | ||
160 | Settings = llListReplaceList(Settings, [val], f + sVALUE, f + sVALUE); | ||
161 | if (llGetSubString(fr, 0, -2) == ScriptName) | ||
162 | { | ||
163 | if (fINT != source) | ||
164 | { | ||
165 | doThing(id, "SET " + var + "=" + val, fr, llList2String(v, 1), val, fINT); | ||
166 | sendScript(lSETTING, [source, id, llList2String(v, 1), val]); | ||
167 | } | ||
168 | } | ||
169 | } | ||
170 | } | ||
171 | |||
172 | doSettings(key id, list settings) | ||
173 | { | ||
174 | integer l = llGetListLength(settings); | ||
175 | integer i; | ||
176 | for (i = 0; i < l; i += sSTRIDE) | ||
177 | { | ||
178 | string var = llList2String(settings, i + sNAME); | ||
179 | list v = validateName(var, "setting"); | ||
180 | string fr = llList2String(v, 0); | ||
181 | var = llList2String(v, 1); | ||
182 | string val = llList2String(settings, i + sVALUE); | ||
183 | doThing(id, "SET " + var + "=" + val, fr, var, val, fINT); | ||
184 | } | ||
185 | } | ||
186 | |||
187 | list readCard(string card) | ||
188 | { | ||
189 | list r; | ||
190 | card = "~" + card + ".data"; | ||
191 | if (NULL_KEY != llGetInventoryKey(card)) | ||
192 | r = llList2List(llParseStringKeepNulls(osGetNotecard(card), ["\n"], []), 0, -2); | ||
193 | return r; | ||
194 | } | ||
195 | |||
196 | dynamicMenu(key id, string menu, string name, string title, string entries, string command) | ||
197 | { | ||
198 | sendScript(lDYNAMIC, [id, menu, name, title, entries, command]); | ||
199 | } | ||
200 | |||
201 | integer Chosen; | ||
202 | linky(integer num, string message, key id) | ||
203 | { | ||
204 | if ((id != ScriptKey) && (id != NULL_KEY)) return; | ||
205 | list input = llParseStringKeepNulls(message, [lSEP], []); | ||
206 | key them = llList2Key(input, 0); | ||
207 | string fr = llList2Key(input, 1); | ||
208 | //d("linky " + num + " " + message); | ||
209 | if (lCMD == num) | ||
210 | { | ||
211 | //d("link CHAT " + llDumpList2String(input, " ~ ")); | ||
212 | if ((fr == (ScriptName + ".")) || (fr == "*.")) | ||
213 | { | ||
214 | key a = llList2Key(input, 3); | ||
215 | string button = llList2String(input, 4); | ||
216 | integer r = doThing(a, button, fr, | ||
217 | llList2String(input, 5), llList2String(input, 6), llList2Integer(input, 2)); | ||
218 | sendScript(lCMD_DONE, [button, a, r]); | ||
219 | } | ||
220 | } | ||
221 | else if (lRESET_DONE == num) | ||
222 | { | ||
223 | d("linky RESET_DONE"); | ||
224 | LibraryKey = them; | ||
225 | Settings = llList2List(input, 2, -1); | ||
226 | setSetting(ScriptKey, ScriptName + ".VERSION", Version, fCARD); | ||
227 | sendScript(lSETTINGS, []); | ||
228 | } | ||
229 | else if (lALIAS_DONE == num) Aliases = llList2List(input, 2, -1); | ||
230 | else if (lSETTINGS_DONE == num) | ||
231 | { | ||
232 | Settings = llList2List(input, 2, -1); | ||
233 | doSettings(id, Settings); | ||
234 | laterInit(); | ||
235 | s(Owner, "Finished starting up " + getSetting("VERSION") + " in " + (string) (llGetTimeOfDay() - Start)); | ||
236 | } | ||
237 | else if (DEBUG_CHANNEL == num) | ||
238 | { | ||
239 | key root = llList2Key(llGetObjectDetails(id, [OBJECT_ROOT]), 0); | ||
240 | integer f = llListFindList(OhSillyThreats, [message]); | ||
241 | if (-1 == f) OhSillyThreats += [message]; | ||
242 | else d("OhSillyThreats detected the function " + message + "() again!" ); | ||
243 | D("Oh Silly threat system prevented " + message + "()" | ||
244 | + "\n in " + id + " \t" + llKey2Name(id) | ||
245 | + "\n part of " + root + " \t" + llKey2Name(root)); | ||
246 | } | ||
247 | } | ||
248 | // END boilerplate, mostly. | ||
249 | |||
250 | integer doThing(key id, string button, string fr, string cmd, string data, integer source) | ||
251 | { | ||
252 | if ("Set " == llGetSubString(button, 0, 3)) | ||
253 | { | ||
254 | integer set = listFindString(Settings, fr + cmd, sSTRIDE); | ||
255 | if (-1 != set) setSetting(id, fr + cmd, data, fINT); | ||
256 | } | ||
257 | if ((fr != (ScriptName + ".") && ("*." != fr))) return TRUE; | ||
258 | integer st = findSitter(id); | ||
259 | integer f; | ||
260 | string menu; | ||
261 | f = llSubStringIndex(button, "->"); | ||
262 | if (-1 != f) | ||
263 | { | ||
264 | menu = llGetSubString(button, 0, f - 1); | ||
265 | button = llGetSubString(button, f + 2, -1); | ||
266 | } | ||
267 | //d("doThing " + button + " = |" + cmd + "| -> " + data ); | ||
268 | if ("Keys" == cmd) updateControls(); | ||
269 | else if ("CONTROLS" == cmd) | ||
270 | { | ||
271 | list p = llParseStringKeepNulls(data, [","], []); | ||
272 | doControl(llList2Key(p, 0), llList2Integer(p, 1), llList2Integer(p, 2)); | ||
273 | } | ||
274 | else if ("checkAO" == cmd) checkAO(); | ||
275 | else if ("SMILE" == cmd) | ||
276 | { // The built in express_* animations are too short, but live with it. | ||
277 | osAvatarStopAnimation(llGetOwner(), Smile); | ||
278 | Smile = llList2String(Smiles, (integer) llFrand(2.5)); | ||
279 | addEvent(3.0 + llFrand(5.0), cmd); | ||
280 | osAvatarPlayAnimation(llGetOwner(), Smile); | ||
281 | } | ||
282 | else if ("LESS_-" == cmd) | ||
283 | { | ||
284 | integer distance = llList2Integer(Sitters, st + pDIST); | ||
285 | --distance; | ||
286 | if (0 > distance) distance = 0; | ||
287 | Sitters = llListReplaceList(Sitters, [distance], st + pDIST, st + pDIST); | ||
288 | showMenu(id); | ||
289 | return FALSE; | ||
290 | } | ||
291 | else if ("MORE_+" == cmd) | ||
292 | { | ||
293 | integer distance = llList2Integer(Sitters, st + pDIST); | ||
294 | ++distance; | ||
295 | if (llGetListLength(Distances) <= distance) distance = llGetListLength(Distances) - 1; | ||
296 | Sitters = llListReplaceList(Sitters, [distance], st + pDIST, st + pDIST); | ||
297 | showMenu(id); | ||
298 | return FALSE; | ||
299 | } | ||
300 | else if ("NEXT_AVATAR" == cmd) | ||
301 | { | ||
302 | key them = llList2Key(Sitters, st + pADJ); | ||
303 | integer t = findSitter(them); | ||
304 | if (-1 != t) | ||
305 | { | ||
306 | t += pSTRIDE; | ||
307 | if (llGetListLength(Sitters) <= t) | ||
308 | t = -1; | ||
309 | } | ||
310 | else t = 0; | ||
311 | if (-1 != t) | ||
312 | Sitters = llListReplaceList(Sitters, [llList2Key(Sitters, t + pKEY)], st + pADJ, st + pADJ); | ||
313 | else | ||
314 | Sitters = llListReplaceList(Sitters, [ScriptKey], st + pADJ, st + pADJ); | ||
315 | showMenu(id); | ||
316 | return FALSE; | ||
317 | } | ||
318 | else if ( | ||
319 | ("FORWARD" == cmd) || ("BACKWARDS" == cmd) || ("LEFT" == cmd) || ("RIGHT" == cmd) | ||
320 | || ("UP" == cmd) || ("DOWN" == cmd) || ("TURN_LEFT" == cmd) || ("TURN_RIGHT" == cmd)) | ||
321 | { | ||
322 | integer i = llSubStringIndex(cmd, "_"); | ||
323 | if (-1 != i) cmd = llGetSubString(cmd, 0, i - 1) + " " + llGetSubString(cmd, i + 1, -1); | ||
324 | adjust(id, llList2Float(Distances, llList2Integer(Sitters, st + pDIST)), llToLower(cmd)); | ||
325 | } | ||
326 | else if ("AO" == cmd) | ||
327 | { | ||
328 | if ("0" != data) | ||
329 | { | ||
330 | integer l = llGetListLength(Sitters); | ||
331 | Pose = ""; | ||
332 | for (f = 0; f < l; f += pSTRIDE) | ||
333 | updateSitter(llList2Key(Sitters, f + pKEY)); | ||
334 | checkAO(); | ||
335 | } | ||
336 | } | ||
337 | else if ("SYNC" == cmd) | ||
338 | { | ||
339 | integer l = llGetListLength(Sitters); | ||
340 | for (f = 0; f < l; f += pSTRIDE) | ||
341 | { | ||
342 | if (("R" != data) || (0 == llGetListLength(isPoseAO(f)))) | ||
343 | Sitters = llListReplaceList(Sitters, [""], f + pSTATE, f + pSTATE); | ||
344 | } | ||
345 | if ("R" != data) | ||
346 | vAnim = data; | ||
347 | checkAO(); | ||
348 | } | ||
349 | else if ("POSE" == cmd) | ||
350 | { | ||
351 | list p = llParseStringKeepNulls(data, [","], []); | ||
352 | if (Attached) | ||
353 | { | ||
354 | What = llList2String(p, 0); | ||
355 | Whats = llList2List(p, 1, -1); | ||
356 | Chosen = 0; | ||
357 | list b = osGetAvatarList(); | ||
358 | integer l = llGetListLength(b); | ||
359 | integer i; | ||
360 | string keys; | ||
361 | string names; | ||
362 | for (i = 0; i < l; i += 3) | ||
363 | { | ||
364 | keys += "|COUPLE_WITH " + llList2String(b, i); | ||
365 | names += "|" + llList2String(b, i + 2); | ||
366 | } | ||
367 | dynamicMenu(Owner, "couples", What, " Pick someone to '" + What + "' with -", | ||
368 | llGetSubString(names, 1, -1), llGetSubString(keys, 1, -1)); | ||
369 | PIN = (integer) llFrand(DEBUG_CHANNEL - 2) + 2; | ||
370 | vector RefPos = llGetPos(); | ||
371 | rotation RefRot = llGetRot(); | ||
372 | llRezObject("1AOor2 prim", ZERO_VECTOR * RefRot + RefPos, ZERO_VECTOR, (ZERO_ROTATION) * RefRot, PIN); | ||
373 | return FALSE; | ||
374 | } | ||
375 | else | ||
376 | newPose(id, llList2String(p, 0), llList2List(p, 1, -1)); | ||
377 | } | ||
378 | else if ("COUPLE_WITH" == cmd) | ||
379 | { | ||
380 | Stalkee = data; | ||
381 | if (osIsNpc(Stalkee)) | ||
382 | { | ||
383 | Chosen = 1; | ||
384 | s(Owner, llKey2Name(data) + " is an NPC, forcing them to '" + What + "' with you."); | ||
385 | } | ||
386 | else | ||
387 | { | ||
388 | s(Owner, "Asking " + llKey2Name(Stalkee) + " if they would like to do '" + What + "' with you."); | ||
389 | dynamicMenu(Stalkee, "", What, | ||
390 | llKey2Name(Owner) + " would like to do '" + What + "' with you.", | ||
391 | "Yes|No", "COUPLE_YES " + Stalkee + "|COUPLE_NO " + Stalkee); | ||
392 | sendScript(lCMD, "1ring", [fINT, id, Stalkee, "GOTO", Stalkee]); | ||
393 | addEvent(120.0, "COUPLE_TIMEOUT"); | ||
394 | } | ||
395 | return FALSE; | ||
396 | } | ||
397 | else if ("PIN" == cmd) | ||
398 | { | ||
399 | list p = llParseStringKeepNulls(data, [","], []); | ||
400 | integer i = llGetInventoryNumber(INVENTORY_NOTECARD); | ||
401 | TheirKey = id; | ||
402 | while (i-- > 0) | ||
403 | llGiveInventory(id, llGetInventoryName(INVENTORY_NOTECARD, i)); | ||
404 | i = llGetInventoryNumber(INVENTORY_ANIMATION); | ||
405 | while (i-- > 0) | ||
406 | llGiveInventory(id, llGetInventoryName(INVENTORY_ANIMATION, i)); | ||
407 | llGiveInventory(id, "soap-bubble"); | ||
408 | llRemoteLoadScriptPin(id, "1chatter", llList2Integer(p, 0), TRUE, bREZ); | ||
409 | llRemoteLoadScriptPin(id, ScriptName, llList2Integer(p, 0), TRUE, bREZ); | ||
410 | } | ||
411 | else if ("REZ_DONE" == cmd) | ||
412 | { | ||
413 | if (1 == Chosen) | ||
414 | { | ||
415 | sendPrim(TheirKey, "SIT", [Stalkee, What, llDumpList2String(Whats, "|")]); | ||
416 | sendPrim(TheirKey, "COUPLES", [Owner]); | ||
417 | Chosen = 0; | ||
418 | } | ||
419 | else if (-1 == Chosen) killPrim(); | ||
420 | else | ||
421 | addEvent(0.5, cmd); | ||
422 | } | ||
423 | else if ("COUPLES" == cmd) | ||
424 | { | ||
425 | if (NULL_KEY != TheirKey) | ||
426 | sendPrim(TheirKey, "COUPLES", [id]); | ||
427 | else | ||
428 | { | ||
429 | if ("" != data) id = data; | ||
430 | sendScript(lMENU, [id, "couples", ""]); | ||
431 | } | ||
432 | return FALSE; | ||
433 | } | ||
434 | else if ("COUPLE_NO" == cmd) | ||
435 | { | ||
436 | Chosen = -1; | ||
437 | addEvent(0.0, "COUPLE_TIMEOUT"); | ||
438 | sendScript(lCMD, "1ring", [fINT, id, Stalkee, "STOP", ""]); | ||
439 | s(Owner, llKey2Name(id) + " said no to your offer of '" + What + "'."); | ||
440 | } | ||
441 | else if ("COUPLE_YES" == cmd) | ||
442 | { | ||
443 | Chosen = 1; | ||
444 | addEvent(0.0, "COUPLE_TIMEOUT"); | ||
445 | sendScript(lCMD, "1ring", [fINT, id, Stalkee, "STOP", ""]); | ||
446 | s(Owner, llKey2Name(id) + " said yes to your offer of '" + What + "."); | ||
447 | } | ||
448 | else if ("COUPLE_TIMEOUT" == cmd) | ||
449 | { | ||
450 | if (NULL_KEY != Stalkee) | ||
451 | { | ||
452 | Chosen = -1; | ||
453 | s(Owner, llKey2Name(Stalkee) + " didn't answer your offer of '" + What + "."); | ||
454 | s(Stalkee, "You failed to answer " + llKey2Name(Owner) + "'s offer to '" + What + "' with you."); | ||
455 | } | ||
456 | } | ||
457 | else if ("SIT" == cmd) | ||
458 | { | ||
459 | if (!Attached) | ||
460 | { | ||
461 | list det = llGetObjectDetails(Owner, [OBJECT_POS, OBJECT_ROT]); | ||
462 | list p = llParseStringKeepNulls(data, [","], []); | ||
463 | llSetPrimitiveParams([PRIM_POSITION, llList2Vector(det, 0), PRIM_ROTATION, llList2Rot(det, 1)]); | ||
464 | Stalkee = llList2Key(p, 0); | ||
465 | d("Forcing sit of " + llKey2Name(Stalkee) + " (" + Stalkee + ") for " + llList2Key(p, 1)); | ||
466 | // This function only works if no one else is sitting on the object, which is why we sit them first. | ||
467 | // RLV has no such limitation. | ||
468 | if (osIsNpc(Stalkee)) | ||
469 | osNpcSit(Stalkee, llGetKey(), OS_NPC_SIT_NOW); | ||
470 | else | ||
471 | { | ||
472 | llInstantMessage(Stalkee, "Please sit on the big gold heart."); | ||
473 | // osForceOtherSit(Stalkee/*, llGetKey()*/); | ||
474 | } | ||
475 | newPose(id, llList2Key(p, 1), llList2List(p, 2, -1)); | ||
476 | } | ||
477 | } | ||
478 | else if ("SIT_DONE" == cmd) | ||
479 | { | ||
480 | d("SIT_DONE for " + llKey2Name(data)); | ||
481 | if(data == Stalkee) | ||
482 | { | ||
483 | llOwnerSay("@sit:" + TheirKey + "=force"); | ||
484 | s(Owner, "Switching AO to 1AOor2 couples object."); | ||
485 | Controller = Owner; | ||
486 | d("SIT_DONE is requesting camera and controls from " + llKey2Name(Controller)); | ||
487 | llRequestPermissions(Controller, PERMISSION_CONTROL_CAMERA | PERMISSION_TAKE_CONTROLS); | ||
488 | } | ||
489 | } | ||
490 | else if ("DIE" == cmd) die(); | ||
491 | else if ("DIE_DONE" == cmd) | ||
492 | { | ||
493 | TheirKey = NULL_KEY; | ||
494 | if (99.0 <= TPangle) | ||
495 | { | ||
496 | s(Owner, "Switching AO to 1ring object."); | ||
497 | oldController(Owner); | ||
498 | } | ||
499 | } | ||
500 | else if ("ADJUST" == cmd) | ||
501 | { | ||
502 | if (Attached) s("You need to be in a couples interaction with someone to use the ADJUST function."); | ||
503 | else {showMenu(id); return FALSE;} | ||
504 | } | ||
505 | else if ("SAVE" == cmd) | ||
506 | { | ||
507 | if (Attached) s(Owner, "You need to be in a couples interaction with someone to use the SAVE function."); | ||
508 | else sendPrim(BossKey, "SAVE_POSES", [llDumpList2String(reportPose(), "\n")]); | ||
509 | } | ||
510 | else if ("SAVE_POSES" == cmd) | ||
511 | { | ||
512 | integer i = llGetInventoryNumber(INVENTORY_NOTECARD); | ||
513 | list cards = []; | ||
514 | s(Owner, "Backing up old cards."); | ||
515 | while (i-- > 0) | ||
516 | { | ||
517 | string item = llGetInventoryName(INVENTORY_NOTECARD, i); | ||
518 | |||
519 | if ((llSubStringIndex(item, ".POSITIONS") == 0) ) cards += [llGetInventoryName(INVENTORY_NOTECARD, i)]; | ||
520 | } | ||
521 | i = llGetListLength(cards); | ||
522 | while (i-- > 0) | ||
523 | { | ||
524 | string item = llList2String(cards, i); | ||
525 | osMakeNotecard(".backup" + item, llParseStringKeepNulls(osGetNotecard(item), ["\n"], [])); | ||
526 | llRemoveInventory(item); | ||
527 | } | ||
528 | osMakeNotecard(".POSITIONS", data); | ||
529 | s(Owner, "Current positions saved to the .POSITIONS notecard."); | ||
530 | } | ||
531 | else if ("TPRIM" == cmd) | ||
532 | { | ||
533 | if (Attached) | ||
534 | { | ||
535 | vector cr = <1.0, 0.0 ,0.0> * llGetRootRotation(); | ||
536 | TPangle = llAtan2(cr.x, cr.y); | ||
537 | llOwnerSay("@tpto:" + data + "=force"); | ||
538 | } | ||
539 | else | ||
540 | { | ||
541 | list crl = llParseString2List(data, ["/"], []); | ||
542 | vector cr = <llList2Float(crl, 0), llList2Float(crl, 1), llList2Float(crl, 2)>; | ||
543 | integer x = (integer)(cr.x / 256); | ||
544 | integer y = (integer)(cr.y / 256); | ||
545 | cr.x = cr.x % 256; | ||
546 | cr.y = cr.y % 256; | ||
547 | osTeleportAgent(Stalkee, x, y, cr, <1.0,1.0,1.0>); | ||
548 | } | ||
549 | } | ||
550 | else if ("RLV" == cmd) | ||
551 | { | ||
552 | list dt = llParseString2List(data, ["|"], []); | ||
553 | d("RLV command requested " + llList2String(dt, 0)); | ||
554 | llOwnerSay("@" + llList2String(dt, 0)); | ||
555 | if (2 < llGetListLength(dt)) | ||
556 | addEvent(llList2Float(dt, 1), "RLV " + llDumpList2String(llList2List(dt, 2, -1), "|")); | ||
557 | } | ||
558 | else if ("URL" == cmd) | ||
559 | { | ||
560 | URL = data; | ||
561 | d("New URL " + URL); | ||
562 | } | ||
563 | else if ("▲" == cmd) | ||
564 | { | ||
565 | if (Attached) | ||
566 | Chosen = -1; | ||
567 | else | ||
568 | { | ||
569 | if (-1 != st) | ||
570 | { | ||
571 | if ("" != llList2Key(Sitters, st + pADJ)) | ||
572 | { | ||
573 | Sitters = llListReplaceList(Sitters, [""], st + pADJ, st + pADJ); | ||
574 | s(id, "Switched out of adjusting mode."); | ||
575 | addEvent(Tick * Smooth, "Keys"); | ||
576 | } | ||
577 | } | ||
578 | } | ||
579 | } | ||
580 | else if (-1 == listFindString(Settings, fr + cmd, sSTRIDE)) | ||
581 | { | ||
582 | if (fMENU == source) d("Unknown menu command '" + cmd + "' from - " + button); | ||
583 | else d("Unknown command '" + cmd + "' from - " + button); | ||
584 | } | ||
585 | return (source == fMENU); | ||
586 | } | ||
587 | |||
588 | float TPangle = 999.0; | ||
589 | |||
590 | killPrim() | ||
591 | { | ||
592 | d("killPrim"); | ||
593 | if (NULL_KEY != TheirKey) | ||
594 | sendPrim(TheirKey, "DIE", []); | ||
595 | Stalkee = NULL_KEY; | ||
596 | What = ""; | ||
597 | Chosen = 0; | ||
598 | } | ||
599 | |||
600 | // General variables | ||
601 | integer Attached; | ||
602 | integer Link; | ||
603 | list Distances = [0.01, 0.05, 0.1, 0.5, 1.0, 2.0]; | ||
604 | vector position = <0.0, 0.0, -0.0001>; | ||
605 | vector rotat = ZERO_VECTOR; | ||
606 | |||
607 | // Rezonater | ||
608 | key BossKey = NULL_KEY; | ||
609 | key TheirKey = NULL_KEY; | ||
610 | integer bREZ = -501; | ||
611 | string HoverText = "loveness"; | ||
612 | string Sit0Text = "carry them"; | ||
613 | string Sit1Text = "be carried"; | ||
614 | |||
615 | // DrivableBox | ||
616 | list Sitters = []; | ||
617 | integer pKEY = 0; // Key of the sitter. | ||
618 | integer pLINK = 1; // The "prim" link the sitter is. | ||
619 | integer pSTATE = 2; // The current AOstate for this person. | ||
620 | integer pNEW = 3; // New state for this person. | ||
621 | integer pANIMS = 4; // Current set of animations for this person. | ||
622 | integer pADJ = 5; // Key of who they are adjusting, "" means not adjusting, NULL_KEY will mean adjust all. | ||
623 | integer pDIST = 6; // For the adjusting this person is doing, not for who they are adjusting. | ||
624 | integer pKEYS = 7; // KeysLevel for this sitter. | ||
625 | integer pSTRIDE = 8; | ||
626 | list Poses; // List of poses. | ||
627 | integer psNAME = 0; // Name of pose. | ||
628 | integer psANIM = 1; // | separated animations. | ||
629 | integer psEMOTE = 2; // | separated emotions and timers list. | ||
630 | integer psPOSROT= 3; // | separated position and rotation pairs. | ||
631 | integer psSTRIDE= 4; | ||
632 | |||
633 | // Smiler | ||
634 | integer SmileCounter = 0; | ||
635 | string Smile = "express_toothsmile"; | ||
636 | list Smiles = | ||
637 | [ | ||
638 | "express_smile", | ||
639 | "express_toothsmile", | ||
640 | "express_wink_emote", | ||
641 | "express_tongue_out" | ||
642 | ]; | ||
643 | |||
644 | // Couples | ||
645 | list Whats; | ||
646 | string What; | ||
647 | string Pose; | ||
648 | key Stalkee; | ||
649 | integer PIN; | ||
650 | key Leader = NULL_KEY; | ||
651 | float LeaderOffset = 0.0; | ||
652 | key Controller = NULL_KEY; | ||
653 | |||
654 | // AO | ||
655 | integer Lag = 100; | ||
656 | integer Swimming; | ||
657 | integer Bobbing; | ||
658 | float Tick = 0.2; | ||
659 | float AOspeed = 1.0; | ||
660 | list flyStates; | ||
661 | list initialStates; // "Taking Off", "hover_up", | ||
662 | list ANIMATIONS; | ||
663 | |||
664 | integer checkAnim(string a) | ||
665 | { | ||
666 | if (("" != a) && (NULL_KEY == llGetInventoryKey(a)) && (-1 == llListFindList(ANIMATIONS, [a]))) | ||
667 | { | ||
668 | s(Owner, "Missing animation - " + a + "!"); | ||
669 | return FALSE; | ||
670 | } | ||
671 | return TRUE; | ||
672 | } | ||
673 | |||
674 | newPose(key id, string p, list ps) | ||
675 | { | ||
676 | integer f = findPose(p); | ||
677 | integer l = llGetListLength(Sitters); | ||
678 | if (-1 != f) | ||
679 | { | ||
680 | setSetting(id, "AO", "0", fINT); | ||
681 | Pose = p; | ||
682 | Poses = llListReplaceList(Poses, llDumpList2String(ps, "|"), f + psANIM, f + psANIM); | ||
683 | for (f = 0; f < l; f += pSTRIDE) | ||
684 | updateSitter(llList2Key(Sitters, f + pKEY)); | ||
685 | checkAO(); | ||
686 | } | ||
687 | } | ||
688 | |||
689 | integer findSitter(key id) {return listFindString(Sitters, id, pSTRIDE);} | ||
690 | |||
691 | string prStr(string str) | ||
692 | { | ||
693 | integer ix = llSubStringIndex(str, ">"); | ||
694 | vector p = (vector) llGetSubString(str, 0, ix); | ||
695 | vector r = (vector) llGetSubString(str, ix + 1, -1); | ||
696 | return vround(p, r); | ||
697 | } | ||
698 | |||
699 | string vround(vector p, vector r) | ||
700 | { // OpenSim likes to swap these around, which triggers the ball movement saving. | ||
701 | if (-179.9 >= r.x) r.x += 360.0; if (-179.9 >= r.y) r.y += 360.0; if (-179.9 >= r.z) r.z += 360.0; | ||
702 | if ( 179.9 <= r.x) r.x -= 360.0; if ( 179.9 <= r.y) r.y -= 360.0; if ( 179.9 <= r.z) r.z -= 360.0; | ||
703 | return "<" + round(p.x, 3) + "," + round(p.y, 3) + "," + round(p.z, 3) + | ||
704 | "><" + round(r.x, 1) + "," + round(r.y, 1) + "," + round(r.z, 1) + ">"; | ||
705 | } | ||
706 | |||
707 | string round(float number, integer places) | ||
708 | { | ||
709 | float shifted; | ||
710 | integer rounded; | ||
711 | string s; | ||
712 | shifted = number * llPow(10.0, (float) places); | ||
713 | rounded = llRound(shifted); | ||
714 | s = (string) ((float) rounded / llPow(10.0, (float)places)); | ||
715 | rounded = llSubStringIndex(s, "."); | ||
716 | if (-1 != rounded) s = llGetSubString(s, 0, llSubStringIndex(s, ".") + places); | ||
717 | else | ||
718 | { | ||
719 | s += ".00000000"; | ||
720 | s = llGetSubString(s,0,llSubStringIndex(s, ".") + places); | ||
721 | } | ||
722 | return s; | ||
723 | } | ||
724 | |||
725 | readPos(list cards) | ||
726 | { | ||
727 | integer i; | ||
728 | integer l = llGetListLength(cards); | ||
729 | cards = llListSort(cards, 1, TRUE); | ||
730 | for (i = 0; i < l; i++) | ||
731 | { | ||
732 | string card = llList2String(cards, i); | ||
733 | list crd = llParseStringKeepNulls(osGetNotecard(card), ["\n"], []); | ||
734 | integer m = llGetListLength(crd); | ||
735 | integer j; | ||
736 | d("Reading '" + card + "'."); | ||
737 | for (j = 0; j < m; j++) | ||
738 | { | ||
739 | string data = llList2String(crd, j); | ||
740 | if (llGetSubString(data, 0, 0) != "/") | ||
741 | { // skip comments | ||
742 | data = llStringTrim(data, STRING_TRIM); | ||
743 | integer ix = llSubStringIndex(data, "{"); | ||
744 | integer jx = llSubStringIndex(data, "} <"); | ||
745 | if (ix != -1 && jx != -1) | ||
746 | { | ||
747 | string name = llStringTrim(llGetSubString(data, ix + 1, jx - 1), STRING_TRIM); | ||
748 | string ldata = llGetSubString(data, jx + 2, -1); | ||
749 | list posrots = llParseString2List(ldata, ["<"], []); | ||
750 | string pr = ""; | ||
751 | jx = llGetListLength(posrots); | ||
752 | for (ix = 0; ix < jx; ix += 2) | ||
753 | pr += "|" + prStr("<" + llStringTrim(llList2String(posrots, ix), STRING_TRIM) | ||
754 | + "<" + llStringTrim(llList2String(posrots, ix + 1), STRING_TRIM)); | ||
755 | savePose(name, "", "", llGetSubString(pr, 1, -1)); | ||
756 | } | ||
757 | } | ||
758 | } | ||
759 | } | ||
760 | } | ||
761 | |||
762 | integer findPose(string name) {return listFindString(Poses, name, psSTRIDE);} | ||
763 | |||
764 | savePose(string name, string anim, string exp, string posRot) | ||
765 | { | ||
766 | integer f = findPose(name); | ||
767 | posRot = normPose(posRot); | ||
768 | if (-1 != f) | ||
769 | { | ||
770 | if ("" == anim) | ||
771 | { | ||
772 | anim = llList2String(Poses, f + psANIM); | ||
773 | exp = llList2String(Poses, f + psEMOTE); | ||
774 | } | ||
775 | else if ("" == posRot) posRot = llList2String(Poses, f + psPOSROT); | ||
776 | Poses = llListReplaceList(Poses, [name, anim, exp, posRot], f, f + psSTRIDE - 1); | ||
777 | } | ||
778 | else | ||
779 | Poses += [name, anim, exp, posRot]; | ||
780 | } | ||
781 | |||
782 | string normPose(string posRot) | ||
783 | { | ||
784 | // Subtract the x,y of the first from both to normalise it around the prim. | ||
785 | // NOTE - only handles the first two. | ||
786 | list p = llParseStringKeepNulls(posRot, ["|"], []); | ||
787 | string pr0 = llList2String(p, 0); | ||
788 | integer jx0 = llSubStringIndex(pr0, "><"); | ||
789 | vector minp = (vector) llGetSubString(pr0, 0, jx0); | ||
790 | vector minr = (vector) llGetSubString(pr0, jx0 + 1, -1); | ||
791 | string pr1 = llList2String(p, 1); | ||
792 | integer jx1 = llSubStringIndex(pr1, "><"); | ||
793 | vector maxp = (vector) llGetSubString(pr1, 0, jx1); | ||
794 | vector maxr = (vector) llGetSubString(pr1, jx1 + 1, -1); | ||
795 | maxp.x = maxp.x - minp.x; | ||
796 | maxp.y = maxp.y - minp.y; | ||
797 | minp.x = 0.0; minp.y = 0.0; | ||
798 | maxr.z = maxr.z - minr.z; | ||
799 | minr.z = 0.0; | ||
800 | return prStr(((string) minp) + ((string) minr)) + "|" + prStr(((string) maxp) + ((string) maxr)); | ||
801 | } | ||
802 | |||
803 | list reportPose() | ||
804 | { | ||
805 | list result = []; | ||
806 | integer l = llGetListLength(Poses); | ||
807 | integer i; | ||
808 | for (i = 0; i < l; i += psSTRIDE) | ||
809 | { | ||
810 | list prs = llParseString2List(llList2String(Poses, i + psPOSROT), ["|"], []); | ||
811 | string r = "{" + llList2String(Poses, i) + "} "; | ||
812 | integer m = llGetListLength(prs); | ||
813 | integer j; | ||
814 | |||
815 | for (j = 0; j < m; j++) | ||
816 | r += llList2String(prs, j); | ||
817 | result += [r]; | ||
818 | } | ||
819 | return result; | ||
820 | } | ||
821 | |||
822 | lookAtMe(key id) | ||
823 | { | ||
824 | if ((llGetPermissionsKey() == id) && (llGetPermissions() & PERMISSION_CONTROL_CAMERA)) | ||
825 | { | ||
826 | d("setting camera for " + llKey2Name(id)); | ||
827 | llClearCameraParams(); | ||
828 | llSetCameraParams( | ||
829 | [ CAMERA_ACTIVE, 1, CAMERA_FOCUS_OFFSET, <0.0, 0.0, 1.0>, CAMERA_PITCH, 12.5, | ||
830 | CAMERA_BEHINDNESS_ANGLE, 0.1, CAMERA_BEHINDNESS_LAG, 0.0, CAMERA_DISTANCE, 2.75, | ||
831 | CAMERA_FOCUS_LAG, 0.0 , CAMERA_FOCUS_THRESHOLD, 0.0, | ||
832 | CAMERA_POSITION_LAG, 0.0, CAMERA_POSITION_THRESHOLD, 0.0 | ||
833 | ]); | ||
834 | } | ||
835 | else | ||
836 | { | ||
837 | if (Controller == id) | ||
838 | { | ||
839 | d("lookAtMe() requesting camera and controls from " + llKey2Name(id)); | ||
840 | llRequestPermissions(id, PERMISSION_CONTROL_CAMERA | PERMISSION_TAKE_CONTROLS); | ||
841 | } | ||
842 | else | ||
843 | { | ||
844 | d("lookAtMe() requesting camera from " + llKey2Name(id)); | ||
845 | llRequestPermissions(id, PERMISSION_CONTROL_CAMERA); | ||
846 | } | ||
847 | } | ||
848 | } | ||
849 | |||
850 | float ControlTime = 0.0; | ||
851 | integer HeldKeys; | ||
852 | integer LevelKeys; | ||
853 | doControl(key id, integer level, integer edge) | ||
854 | { | ||
855 | if (99.0 > TPangle) return; | ||
856 | if (NULL_KEY != TheirKey) | ||
857 | { | ||
858 | sendPrim(TheirKey, "CONTROLS", [id, level, edge]); | ||
859 | if ("Sitting" != llGetAnimation(id)) | ||
860 | oldController(id); | ||
861 | return; | ||
862 | } | ||
863 | // integer start = level & edge; | ||
864 | // integer end = ~level & edge; | ||
865 | integer held = level & ~edge; | ||
866 | // integer untouched = ~(level | edge); | ||
867 | // llSay(0, "doControl " + llKey2Name(id) + " " + llList2CSV([level, edge, start, end, held, untouched])); | ||
868 | integer f = findSitter(id); | ||
869 | if (-1 != f) | ||
870 | { | ||
871 | integer a = ("" != llList2Key(Sitters, f + pADJ)); | ||
872 | if ((held == (CONTROL_BACK | CONTROL_FWD)) || | ||
873 | (held == (CONTROL_DOWN | CONTROL_UP)) || | ||
874 | (held == (CONTROL_LEFT | CONTROL_RIGHT)) || | ||
875 | (held == (CONTROL_ROT_LEFT | CONTROL_ROT_RIGHT))) | ||
876 | { | ||
877 | if ((llGetTimeOfDay() - ControlTime) >= 1.0) | ||
878 | { | ||
879 | if (held != HeldKeys) | ||
880 | { | ||
881 | if (a) | ||
882 | { | ||
883 | Sitters = llListReplaceList(Sitters, [""], f + pADJ, f + pADJ); | ||
884 | s(id, "Switched out of adjusting mode."); | ||
885 | addEvent(Tick * Smooth, "Keys"); | ||
886 | } | ||
887 | else | ||
888 | showMenu(id); | ||
889 | } | ||
890 | HeldKeys = held; | ||
891 | } | ||
892 | level = 0; | ||
893 | } | ||
894 | else if (0 != HeldKeys) | ||
895 | { | ||
896 | HeldKeys = 0; | ||
897 | level = 0; | ||
898 | } | ||
899 | Sitters = llListReplaceList(Sitters, [level], f + pKEYS, f + pKEYS); | ||
900 | if (0 != edge) ControlTime = llGetTimeOfDay(); | ||
901 | if (level != LevelKeys) | ||
902 | updateControls(); | ||
903 | LevelKeys = level; | ||
904 | } | ||
905 | } | ||
906 | |||
907 | float SPEED = 3.8; | ||
908 | float ROTAT = 8.0; | ||
909 | float Smooth = 0.2; // 0.2 is good. | ||
910 | string vAnim; | ||
911 | updateControls() | ||
912 | { | ||
913 | if (Attached) return; | ||
914 | integer mode = vNONE; integer reverse = 1; float speed = 0.0; float rot = 0.0; | ||
915 | integer l = llGetListLength(Sitters); integer i; | ||
916 | integer keys; | ||
917 | for (i = l - pSTRIDE; i >= 0; i -= pSTRIDE) | ||
918 | { | ||
919 | key k = llList2Key(Sitters, i + pKEY); | ||
920 | keys = llList2Integer(Sitters, i + pKEYS); | ||
921 | if ("" == llList2Key(Sitters, i + pADJ)) | ||
922 | { | ||
923 | if (keys & (CONTROL_DOWN |CONTROL_UP)) mode = vUP; | ||
924 | if (keys & (CONTROL_BACK | CONTROL_FWD)) mode = vFWD; | ||
925 | if (keys & (CONTROL_LEFT | CONTROL_RIGHT)) mode = vLEFT; | ||
926 | if (keys & (CONTROL_ROT_LEFT | CONTROL_ROT_RIGHT)) mode = vFWD; | ||
927 | if (keys & (CONTROL_BACK | CONTROL_DOWN | CONTROL_RIGHT)) | ||
928 | { | ||
929 | speed = 0.0 - (SPEED * Smooth); | ||
930 | reverse = -1; | ||
931 | } | ||
932 | if (keys & (CONTROL_FWD | CONTROL_UP | CONTROL_LEFT)) speed = SPEED * Smooth; | ||
933 | if (keys & CONTROL_ROT_LEFT) rot = (reverse * PI) / (ROTAT / Smooth); | ||
934 | if (keys & CONTROL_ROT_RIGHT) rot = ((0 - reverse) * PI) / (ROTAT / Smooth); | ||
935 | } | ||
936 | else | ||
937 | { | ||
938 | float d = llList2Float(Distances, llList2Integer(Sitters, i + pDIST)); | ||
939 | if (keys & CONTROL_FWD) adjust(k, d, "forward"); | ||
940 | if (keys & CONTROL_BACK) adjust(k, d, "backwards"); | ||
941 | if (keys & CONTROL_LEFT) adjust(k, d, "left"); | ||
942 | if (keys & CONTROL_RIGHT) adjust(k, d, "right"); | ||
943 | if (keys & CONTROL_ROT_LEFT) adjust(k, d, "turn left"); | ||
944 | if (keys & CONTROL_ROT_RIGHT) adjust(k, d, "turn right"); | ||
945 | if (keys & CONTROL_UP) adjust(k, d, "up"); | ||
946 | if (keys & CONTROL_DOWN) adjust(k, d, "down"); | ||
947 | } | ||
948 | } | ||
949 | updateVehicle(keys, mode, speed * AOspeed, rot); | ||
950 | checkLag(); | ||
951 | float t = Tick; | ||
952 | if (vNONE != mode) addEvent(t * Smooth, "Keys"); | ||
953 | else if (vFALL == vMODE) addEvent(t * Smooth * 1.5, "Keys"); | ||
954 | } | ||
955 | |||
956 | adjust(key id, float dist, string direction) | ||
957 | { | ||
958 | integer f = findSitter(id); | ||
959 | integer i = -1; | ||
960 | list p; | ||
961 | i = findPose(Pose); | ||
962 | if (-1 != i) | ||
963 | p = llParseStringKeepNulls(llList2String(Poses, i + psPOSROT), ["|"], []); | ||
964 | else | ||
965 | return; | ||
966 | if (-1 != f) | ||
967 | { | ||
968 | key them = llList2Key(Sitters, f + pADJ); | ||
969 | integer l; integer t; | ||
970 | if (ScriptKey == them) | ||
971 | { | ||
972 | t = 0; | ||
973 | l = llGetListLength(Sitters); | ||
974 | } | ||
975 | else | ||
976 | { | ||
977 | t = findSitter(them); | ||
978 | l = t + pSTRIDE; | ||
979 | } | ||
980 | if (-1 != t) | ||
981 | { | ||
982 | if (("turn left" == direction) || ("turn right" == direction)) dist = dist * 90.0; | ||
983 | for (; t < l; t += pSTRIDE) | ||
984 | { | ||
985 | them = llList2Key(Sitters, t + pKEY); | ||
986 | integer lnk = llList2Integer(Sitters, t + pLINK); | ||
987 | if (-1 == lnk) return; | ||
988 | string prn = llList2String(p, t / pSTRIDE); | ||
989 | integer ix = llSubStringIndex(prn, ">"); | ||
990 | vector pos = (vector) llGetSubString(prn, 0, ix); | ||
991 | vector rot = (vector) llGetSubString(prn, ix + 1, -1); | ||
992 | if ("forward" == direction) pos.x = pos.x + dist; | ||
993 | else if ("backwards" == direction) pos.x = pos.x - dist; | ||
994 | else if ("left" == direction) pos.y = pos.y + dist; | ||
995 | else if ("right" == direction) pos.y = pos.y - dist; | ||
996 | else if ("up" == direction) pos.z = pos.z + dist; | ||
997 | else if ("down" == direction) pos.z = pos.z - dist; | ||
998 | else if ("turn left" == direction) rot.z = rot.z + (dist); | ||
999 | else if ("turn right" == direction) rot.z = rot.z - (dist); | ||
1000 | p = llListReplaceList(p, [vround(pos, rot)], t / pSTRIDE, t / pSTRIDE); | ||
1001 | Poses = llListReplaceList(Poses, [llDumpList2String(p, "|")], i + psPOSROT, i + psPOSROT); | ||
1002 | updateSitter(them); | ||
1003 | s(id, llKey2Name(id) + " adjusts " + llKey2Name(them) + " by " + (string) dist + " " + direction); | ||
1004 | if (id != them) | ||
1005 | s(them, llKey2Name(id) + " adjusts " + llKey2Name(them) + " by " + (string)dist + " " + direction); | ||
1006 | } | ||
1007 | } | ||
1008 | } | ||
1009 | } | ||
1010 | |||
1011 | updateSitter(key id) | ||
1012 | { // Written by Strife Onizuka, size adjustment provided by Talarus Luan | ||
1013 | // Using this while the object is moving may give unpredictable results. | ||
1014 | integer f = findSitter(id); | ||
1015 | if (-1 != f) | ||
1016 | { | ||
1017 | integer lnk = llList2Integer(Sitters, f + pLINK); | ||
1018 | if (-1 == lnk) return; | ||
1019 | vector pos = position; | ||
1020 | rotation rot = llEuler2Rot(rotat * DEG_TO_RAD); | ||
1021 | integer i = findPose(Pose); | ||
1022 | if (-1 != i) | ||
1023 | { | ||
1024 | string prn = llList2String(llParseStringKeepNulls(llList2String(Poses, i + psPOSROT), ["|"], []), f / pSTRIDE); | ||
1025 | integer ix = llSubStringIndex(prn, ">"); | ||
1026 | // TODO - might be wrong, coz rotations are hard, m'kay. | ||
1027 | pos = (vector) llGetSubString(prn, 0, ix); | ||
1028 | rot = llEuler2Rot((vector) llGetSubString(prn, ix + 1, -1) * DEG_TO_RAD); | ||
1029 | } | ||
1030 | vector size = llGetAgentSize(id); | ||
1031 | integer prim = llGetLinkNumber(); | ||
1032 | // We need to make the position and rotation local to the current prim. | ||
1033 | vector localpos = ZERO_VECTOR; | ||
1034 | rotation localrot = ZERO_ROTATION; | ||
1035 | |||
1036 | if (1 < prim) // Only need the local rot if it's not the root. | ||
1037 | { | ||
1038 | list local = llGetLinkPrimitiveParams(prim, [PRIM_POS_LOCAL, PRIM_ROT_LOCAL]); | ||
1039 | localpos = llList2Vector(local, 0); | ||
1040 | localrot = llList2Rot(local, 1); | ||
1041 | } | ||
1042 | pos += <0.0, 0.0, 0.2>; // Fudge it. Pffft | ||
1043 | // <0.008906, -0.049831, 0.088967> are the coefficients for a parabolic curve that | ||
1044 | // best fits real avatars. It is not a perfect fit. | ||
1045 | float fAdjust = ((((0.008906 * size.z) + -0.049831) * size.z) + 0.088967) * size.z; | ||
1046 | vector fa = llRot2Up(rot) * fAdjust; | ||
1047 | llSetLinkPrimitiveParamsFast(lnk, | ||
1048 | [ | ||
1049 | PRIM_POS_LOCAL, ((pos - fa) * localrot) + localpos, | ||
1050 | PRIM_ROT_LOCAL, rot * localrot // This does rotate around the avatar's centre, not the prims centre. | ||
1051 | ]); | ||
1052 | } | ||
1053 | } | ||
1054 | |||
1055 | showMenu(key id) | ||
1056 | { | ||
1057 | integer f = findSitter(id); | ||
1058 | if (-1 != f) | ||
1059 | { | ||
1060 | key them = llList2Key(Sitters, f + pADJ); | ||
1061 | string name; | ||
1062 | integer distance = llList2Integer(Sitters, f + pDIST); | ||
1063 | if ("" == them) | ||
1064 | { | ||
1065 | them = id; | ||
1066 | Sitters = llListReplaceList(Sitters, [them], f + pADJ, f + pADJ); | ||
1067 | s(id, "Switched to adjusting mode. Click '▲ Exit' on the adjusting menu to switch back to moving."); | ||
1068 | } | ||
1069 | if (ScriptKey == them) | ||
1070 | name = "all"; | ||
1071 | else | ||
1072 | name = llKey2Name(them); | ||
1073 | sendScript(lMENU, [id, "adjust", "Adjusting position of " + name | ||
1074 | + ". \nAdjust by " + llList2String(Distances, distance) | ||
1075 | + ", \nusing menu or movement keys."]); | ||
1076 | } | ||
1077 | } | ||
1078 | |||
1079 | integer vMODE; | ||
1080 | integer vNONE = 0; | ||
1081 | integer vFWD = 1; | ||
1082 | integer vLEFT = 2; | ||
1083 | integer vUP = 3; | ||
1084 | integer vCROUCH = 4; | ||
1085 | integer vGROUND = 5; | ||
1086 | integer vFALL = 6; | ||
1087 | float vTHEN; | ||
1088 | |||
1089 | float LastCast; | ||
1090 | integer CastOut; | ||
1091 | float cast(vector gp, vector pos, float dist) | ||
1092 | { | ||
1093 | vector start = gp + <0.0, 0.0, 0.0>; | ||
1094 | vector end = pos + <0.0, 0.0, -llFabs(dist * 1.1)>; | ||
1095 | gp = pos - gp; | ||
1096 | float g = llGround(<gp.x, gp.y, 0.0 - (LeaderOffset * 3.0)>); | ||
1097 | if (0 <= CastOut) | ||
1098 | { | ||
1099 | list results = llCastRay(start, end, [ | ||
1100 | RC_REJECT_TYPES, RC_REJECT_AGENTS | RC_REJECT_PHYSICAL | RC_REJECT_LAND, | ||
1101 | RC_DATA_FLAGS, RC_GET_ROOT_KEY, | ||
1102 | RC_MAX_HITS, 2] ); | ||
1103 | CastOut = llList2Integer(results, -1); | ||
1104 | if (0 < CastOut) | ||
1105 | { | ||
1106 | vector p = llList2Vector(results, 1); | ||
1107 | if (llGetKey() == llList2Key(results, 0)) | ||
1108 | { | ||
1109 | if (1 < CastOut) | ||
1110 | { | ||
1111 | p = llList2Vector(results, 3); | ||
1112 | LastCast = p.z + 0.1; | ||
1113 | } | ||
1114 | else | ||
1115 | LastCast = g + 0.1; | ||
1116 | } | ||
1117 | else | ||
1118 | LastCast = p.z + 0.1; | ||
1119 | } | ||
1120 | else if (0 == CastOut) | ||
1121 | LastCast = g + 0.1; | ||
1122 | /* | ||
1123 | else | ||
1124 | { | ||
1125 | if (RCERR_UNKNOWN == CastOut) d("llCastRay() failed for an unspecified reason."); | ||
1126 | else if (RCERR_SIM_PERF_LOW == CastOut) d("llCastRay() failed coz sim performance is low."); | ||
1127 | else if (RCERR_CAST_TIME_EXCEEDED == CastOut) d("llCastRay() failed coz too many raycasts."); | ||
1128 | else d("llCastRay() returned unknown error code " + CastOut); | ||
1129 | } | ||
1130 | */ | ||
1131 | } | ||
1132 | else CastOut += 1; | ||
1133 | if (g > LastCast) LastCast = g + 0.1; | ||
1134 | return LastCast - 0.1; | ||
1135 | } | ||
1136 | |||
1137 | updateVehicle(integer keys, integer mode, float move, float rotate) | ||
1138 | { | ||
1139 | vector pos = llGetPos(); | ||
1140 | vector gp = pos; | ||
1141 | vector rotvec = llRot2Euler(llGetRot()); | ||
1142 | if (Attached || (0.0 == LeaderOffset)) return; | ||
1143 | vAnim = "Standing"; | ||
1144 | if (0.0 < vTHEN) | ||
1145 | { | ||
1146 | if (3.0 > (llGetTimeOfDay() - vTHEN)) | ||
1147 | { | ||
1148 | pos.z = cast(gp, pos, 4.0) + LeaderOffset; | ||
1149 | llSetPrimitiveParams([PRIM_POSITION, pos]); | ||
1150 | return; | ||
1151 | } | ||
1152 | } | ||
1153 | if (vUP == vMODE) | ||
1154 | { | ||
1155 | if (vNONE == mode) vAnim = "Hovering"; | ||
1156 | else if (vFWD == mode) | ||
1157 | { | ||
1158 | vAnim = "Flying"; | ||
1159 | pos += (<(llCos(rotvec.z)) * move, (llSin(rotvec.z)) * move, 0.0>); | ||
1160 | } | ||
1161 | else if (vUP == mode) | ||
1162 | { | ||
1163 | pos += (<0.0, 0.0, move>); | ||
1164 | if (0.0 < move) vAnim = "Hovering Up"; | ||
1165 | else vAnim = "Hovering Down"; | ||
1166 | } | ||
1167 | if (cast(gp, pos, move / Smooth) > (pos.z - LeaderOffset)) | ||
1168 | { | ||
1169 | vAnim = "Soft Landing"; vMODE = vFWD; | ||
1170 | vTHEN = llGetTimeOfDay(); addEvent(3.0, "SYNC Standing"); | ||
1171 | } | ||
1172 | } | ||
1173 | else if (vFALL == vMODE) | ||
1174 | { | ||
1175 | vAnim = "Falling"; pos.z -= 1.0; | ||
1176 | if (cast(gp, pos, move / Smooth) > (pos.z - LeaderOffset)) | ||
1177 | { | ||
1178 | vAnim = "Standing Up"; vMODE = vFWD; | ||
1179 | vTHEN = llGetTimeOfDay(); addEvent(3.0, "SYNC Standing"); | ||
1180 | } | ||
1181 | } | ||
1182 | else if ((vFWD == mode) || (vLEFT == mode) || (vNONE == mode)) | ||
1183 | { | ||
1184 | if (vLEFT == mode) | ||
1185 | { | ||
1186 | if (0.0 < move) rotvec.z += 89.0 * DEG_TO_RAD; | ||
1187 | else rotvec.z += 91.0 * DEG_TO_RAD; | ||
1188 | } | ||
1189 | pos += (<(llCos(rotvec.z)) * move, (llSin(rotvec.z)) * move, 0.0>); | ||
1190 | if (0.0 != move) | ||
1191 | { | ||
1192 | float ground = cast(gp, pos, move / Smooth); | ||
1193 | if (pos.z > (ground + LeaderOffset * 3.0)) | ||
1194 | { | ||
1195 | vMODE = vFALL; | ||
1196 | pos.z -= 1.0; | ||
1197 | } | ||
1198 | else pos.z = ground + LeaderOffset; | ||
1199 | } | ||
1200 | if (vMODE == vFALL) vAnim = "Falling"; | ||
1201 | else if (vCROUCH == vMODE) | ||
1202 | { | ||
1203 | if (0.0 == move) vAnim = "Crouching"; | ||
1204 | else vAnim = "CrouchWalking"; | ||
1205 | } | ||
1206 | else if (vGROUND == vMODE) | ||
1207 | vAnim = "Sitting on Ground"; | ||
1208 | else | ||
1209 | { | ||
1210 | if (0.0 != rotate) | ||
1211 | { | ||
1212 | if (0.0 < rotate) vAnim = "Turning Left"; | ||
1213 | else vAnim = "Turning Right"; | ||
1214 | } | ||
1215 | if (0.0 == move) vAnim = "Standing"; | ||
1216 | else vAnim = "Walking"; | ||
1217 | } | ||
1218 | } | ||
1219 | else if (vUP == mode) | ||
1220 | { | ||
1221 | if (keys & CONTROL_UP) | ||
1222 | { | ||
1223 | if (vGROUND == vMODE) {vAnim = "Crouching"; vMODE = vCROUCH;} | ||
1224 | else if (vCROUCH == vMODE) {vAnim = "Standing"; vMODE = vFWD;} | ||
1225 | else {pos += (<0.0, 0.0, move>); vMODE = vUP;} | ||
1226 | } | ||
1227 | else | ||
1228 | { | ||
1229 | if (vCROUCH == vMODE) {vAnim = "Sitting on Ground"; vMODE = vGROUND;} | ||
1230 | else if (0.0 > move) {vAnim = "Crouching"; vMODE = vCROUCH;} | ||
1231 | else {pos += (<0.0, 0.0, move>); vMODE = vUP;} | ||
1232 | } | ||
1233 | } | ||
1234 | // This is useful at least, not tested on a var region yet, but should work, that'll be why it exists. | ||
1235 | vector rs = osGetRegionSize(); | ||
1236 | vector cr = pos - gp; | ||
1237 | integer isBorder; | ||
1238 | integer isEdge; | ||
1239 | if ((pos.x < 0.0) || (pos.x > rs.x) ||(pos.y < 0.0) || (pos.y > rs.y)) | ||
1240 | { | ||
1241 | isBorder = TRUE; | ||
1242 | if (llFabs(cr.x) > llFabs(cr.y)) | ||
1243 | { | ||
1244 | cr.y = 0.0; | ||
1245 | if (0.0 < cr.x) {if (0.3 < cr.x) cr.x = 1.0; else cr.x = 0.0;} | ||
1246 | else if (0.0 > cr.x) {if (-0.3 > cr.x) cr.x = -1.0; else cr.x = 0.0;} | ||
1247 | } | ||
1248 | else | ||
1249 | { | ||
1250 | cr.x = 0.0; | ||
1251 | if (0.0 < cr.y) {if (0.3 < cr.y) cr.y = 1.0; else cr.y = 0.0;} | ||
1252 | else if (0.0 > cr.y) {if (-0.3 > cr.y) cr.y = -1.0; else cr.y = 0.0;} | ||
1253 | } | ||
1254 | isEdge = llEdgeOfWorld(gp, cr); | ||
1255 | integer l = llGetListLength(Sitters); | ||
1256 | integer i; | ||
1257 | for (i = 0; i < l; i += pSTRIDE) | ||
1258 | { | ||
1259 | key a = llList2Key(Sitters, i); | ||
1260 | key t = llList2Key(Sitters, i + pADJ); | ||
1261 | if (isEdge) | ||
1262 | { | ||
1263 | Sitters = llListReplaceList(Sitters, [a], i + pADJ, i + pADJ); | ||
1264 | adjust(a, move, "forward"); | ||
1265 | Sitters = llListReplaceList(Sitters, [t], i + pADJ, i + pADJ); | ||
1266 | } | ||
1267 | else | ||
1268 | llSetLinkPrimitiveParamsFast(llList2Integer(Sitters, i + pLINK), | ||
1269 | [PRIM_POS_LOCAL, <-1.0, 0.0, -0.0001>]); | ||
1270 | } | ||
1271 | } | ||
1272 | if (isEdge) | ||
1273 | d("Can't go there, you'll fall off the edge!"); | ||
1274 | else | ||
1275 | { | ||
1276 | if (isBorder) | ||
1277 | { | ||
1278 | llWhisper(0, "Can't walk into next sim, teleporting slowly instead."); | ||
1279 | cr = llGetRegionCorner() + pos + ((pos - gp) * 2.0); | ||
1280 | string data = (string)cr.x + "/" + (string)cr.y + "/" + (string)cr.z; | ||
1281 | sendPrim(BossKey, "TPRIM", [data]); | ||
1282 | LeaderOffset = 0.0; | ||
1283 | addEvent(1.5, "TPRIM " + (string)cr.x + "/" + (string)cr.y + "/" + (string)cr.z); | ||
1284 | } | ||
1285 | else | ||
1286 | { | ||
1287 | // This is useful at least for parcel crossing. | ||
1288 | integer f = llGetParcelFlags(pos); | ||
1289 | if (!f & PARCEL_FLAG_ALLOW_FLY) d("No fly."); | ||
1290 | if (!f & PARCEL_FLAG_ALLOW_SCRIPTS) d("No scripts."); | ||
1291 | if (!f & PARCEL_FLAG_ALLOW_CREATE_OBJECTS) d("No create object."); | ||
1292 | if (!f & PARCEL_FLAG_ALLOW_ALL_OBJECT_ENTRY) d("No object entry."); | ||
1293 | if (!f & PARCEL_FLAG_ALLOW_GROUP_SCRIPTS) d("No group scripts."); | ||
1294 | if (!f & PARCEL_FLAG_ALLOW_CREATE_GROUP_OBJECTS) d("No group create objects."); | ||
1295 | if (!f & PARCEL_FLAG_ALLOW_GROUP_OBJECT_ENTRY) d("No group object entry."); | ||
1296 | if ((f & PARCEL_FLAG_ALLOW_SCRIPTS) && (f & PARCEL_FLAG_ALLOW_ALL_OBJECT_ENTRY)) | ||
1297 | llSetLinkPrimitiveParamsFast(LINK_THIS, [PRIM_POSITION, pos, PRIM_ROTATION, | ||
1298 | llEuler2Rot(<0.0, 0.0, rotate>) * llGetRot()]); | ||
1299 | } | ||
1300 | } | ||
1301 | checkAO(); | ||
1302 | } | ||
1303 | |||
1304 | newLeader(key id) | ||
1305 | { | ||
1306 | Leader = id; | ||
1307 | vector bb = llGetAgentSize(Leader); LeaderOffset = (bb.z + 0.3) / 2; | ||
1308 | updateVehicle(0, vNONE, 0.0, 0.0); | ||
1309 | checkAO(); | ||
1310 | d("newLeader " + llKey2Name(Leader) + ", offset " + (string) LeaderOffset); | ||
1311 | } | ||
1312 | |||
1313 | oldController(key t) | ||
1314 | { | ||
1315 | // NOTE - the person just stood up, that automatically revoked the permissions. In theory. | ||
1316 | // And sometimes in practice. | ||
1317 | if (llGetPermissionsKey() == t) | ||
1318 | { | ||
1319 | if (llGetPermissions() & PERMISSION_CONTROL_CAMERA) | ||
1320 | { | ||
1321 | d("releasing camera for " + llKey2Name(t)); | ||
1322 | llClearCameraParams(); | ||
1323 | } | ||
1324 | if (llGetPermissions() & PERMISSION_TAKE_CONTROLS) | ||
1325 | { | ||
1326 | d("releasing controls for " + llKey2Name(t)); | ||
1327 | llReleaseControls(); | ||
1328 | } | ||
1329 | } | ||
1330 | if (t == Controller) | ||
1331 | { | ||
1332 | addEvent(0.0, "Keys"); | ||
1333 | Controller = NULL_KEY; | ||
1334 | } | ||
1335 | } | ||
1336 | |||
1337 | checkSitters(integer del) | ||
1338 | { | ||
1339 | list new = []; | ||
1340 | integer l = llGetNumberOfPrims(); | ||
1341 | integer lnk; | ||
1342 | for (lnk = llGetObjectPrimCount(llGetKey()) + 1; lnk <= l; ++lnk) | ||
1343 | new += [llGetLinkKey(lnk), lnk]; | ||
1344 | l = llGetListLength(Sitters); | ||
1345 | for (lnk = 0; lnk < l; lnk += pSTRIDE) | ||
1346 | { | ||
1347 | key t = llList2Key(Sitters, lnk + pKEY); | ||
1348 | if (NULL_KEY != t) | ||
1349 | { | ||
1350 | integer j = listFindString(new, t, 2); | ||
1351 | |||
1352 | if (-1 == j) // old sitter that left | ||
1353 | { | ||
1354 | d("unsit " + llKey2Name(t)); | ||
1355 | stopMe(t, lnk); | ||
1356 | oldController(t); | ||
1357 | Sitters = llListReplaceList(Sitters, [NULL_KEY], lnk + pKEY, lnk + pKEY); | ||
1358 | } | ||
1359 | else // old sitter that is still here | ||
1360 | new = llListReplaceList(new, [], j, j + 1); | ||
1361 | } | ||
1362 | } | ||
1363 | l = llGetListLength(new) / 2; | ||
1364 | for (lnk = 0; lnk < l; lnk +=2) | ||
1365 | { | ||
1366 | key t = llList2Key(new, lnk); | ||
1367 | list n = [t, llList2Integer(new, lnk + 1), "", "", "", "", llGetListLength(Distances) / 3, 0]; | ||
1368 | list gndr = llGetObjectDetails(t, [OBJECT_BODY_SHAPE_TYPE]); | ||
1369 | stopAnims(t); | ||
1370 | integer f = findSitter(NULL_KEY); | ||
1371 | if (-1 != f) // new sitter replaces old | ||
1372 | Sitters = llListReplaceList(Sitters, n, f, f + pSTRIDE - 1); | ||
1373 | else // new sitter added on end | ||
1374 | Sitters += n; | ||
1375 | if (NULL_KEY != BossKey) sendPrim(BossKey, "SIT_DONE", [t]); | ||
1376 | d("sat " + llKey2Name(t) + ", " + llList2String(gndr, 0) + " male @ " + (f / pSTRIDE)); | ||
1377 | } | ||
1378 | |||
1379 | // Wearer is the default leader, unless anyone is taller. | ||
1380 | l = llGetListLength(Sitters); | ||
1381 | integer m = l / pSTRIDE; | ||
1382 | integer ldr = findSitter(Owner); | ||
1383 | float max = 0.0; | ||
1384 | if (-1 != ldr) | ||
1385 | { | ||
1386 | vector s = llGetAgentSize(Owner); | ||
1387 | max = s.z; | ||
1388 | } | ||
1389 | for (lnk = 0; lnk < l; lnk += pSTRIDE) | ||
1390 | { | ||
1391 | key t = llList2Key(Sitters, lnk + pKEY); | ||
1392 | vector s = llGetAgentSize(t); | ||
1393 | if (s.z > max) | ||
1394 | { | ||
1395 | max = s.z; | ||
1396 | ldr = lnk; | ||
1397 | } | ||
1398 | if (NULL_KEY == t) --m; | ||
1399 | } | ||
1400 | if ((0 != ldr) && (0 != llGetListLength(Sitters))) | ||
1401 | { | ||
1402 | list s = llList2List(Sitters, ldr, ldr + pSTRIDE - 1); | ||
1403 | Sitters = s + llListReplaceList(Sitters, [], ldr, ldr + pSTRIDE - 1); | ||
1404 | } | ||
1405 | newLeader(llList2Key(Sitters, pKEY)); | ||
1406 | llSitTarget(position, llEuler2Rot(rotat * DEG_TO_RAD)); | ||
1407 | Controller = NULL_KEY; | ||
1408 | for (lnk = 0; lnk < l; lnk += pSTRIDE) | ||
1409 | { | ||
1410 | updateSitter(llList2Key(Sitters, lnk + pKEY)); | ||
1411 | key t = llList2Key(Sitters, lnk + pKEY); | ||
1412 | if ((NULL_KEY == Controller) && (Owner != t)) | ||
1413 | Controller = t; | ||
1414 | } | ||
1415 | if (NULL_KEY != Controller) | ||
1416 | { | ||
1417 | d("checkSitters() requesting camera and controls from " + llKey2Name(Controller)); | ||
1418 | llRequestPermissions(Controller, PERMISSION_CONTROL_CAMERA | PERMISSION_TAKE_CONTROLS); | ||
1419 | } | ||
1420 | llSetSitText(Sit0Text); llSetTouchText("menu"); llSetClickAction(CLICK_ACTION_SIT); | ||
1421 | llSetText(HoverText, <1.0, 1.0, 1.0>, 1.0); | ||
1422 | if (0 == m) | ||
1423 | { | ||
1424 | if (del) | ||
1425 | { | ||
1426 | llSleep(5.0); | ||
1427 | d("No one left sitting on me."); | ||
1428 | if (NULL_KEY != BossKey) die(); | ||
1429 | } | ||
1430 | llSetTouchText(""); | ||
1431 | llSetLinkAlpha(Link, 0.5, ALL_SIDES); | ||
1432 | } | ||
1433 | else if (1 == m) llSetLinkAlpha(Link, 0.25, ALL_SIDES); | ||
1434 | else if (2 == m) | ||
1435 | { | ||
1436 | llSetSitText(Sit1Text); llSetClickAction(CLICK_ACTION_TOUCH); | ||
1437 | llSetLinkAlpha(Link, 0.1, ALL_SIDES); llSetText("", ZERO_VECTOR, 0.0); | ||
1438 | } | ||
1439 | return; | ||
1440 | } | ||
1441 | |||
1442 | stopAnims(key avatar) | ||
1443 | { | ||
1444 | if (NULL_KEY != avatar) | ||
1445 | { | ||
1446 | list anims = llGetAnimationList(avatar); | ||
1447 | integer l = llGetListLength(anims); | ||
1448 | integer i; | ||
1449 | for (i = 0; i < l; i++) | ||
1450 | { | ||
1451 | string anim = llList2String(anims, i); | ||
1452 | if (anim != "") osAvatarStopAnimation(avatar, anim); | ||
1453 | } | ||
1454 | } | ||
1455 | } | ||
1456 | |||
1457 | // AO functions. | ||
1458 | list States = []; | ||
1459 | integer loadCard(string card, integer casperMode) | ||
1460 | { | ||
1461 | if (NULL_KEY != llGetInventoryKey(card)) | ||
1462 | { | ||
1463 | float now = llGetTimeOfDay(); | ||
1464 | string section = ""; | ||
1465 | integer l = osGetNumberOfNotecardLines(card); | ||
1466 | integer i; | ||
1467 | States = initialStates; | ||
1468 | for (i = 0; i <= l; ++i) | ||
1469 | { | ||
1470 | string line = osGetNotecardLine(card, i); | ||
1471 | string rest = ""; | ||
1472 | string name = ""; | ||
1473 | integer match = llSubStringIndex(line, "]"); | ||
1474 | integer found = -1; | ||
1475 | // Ignore anything that does not start with [, and has the matching ]. | ||
1476 | // Which is a dirt cheap ZHAO II compatibility. | ||
1477 | if (("[" == llGetSubString(line, 0, 0)) && (-1 != match)) | ||
1478 | { | ||
1479 | name = llStringTrim(llGetSubString(line, 1, match - 1), STRING_TRIM); | ||
1480 | if (casperMode) section = name; | ||
1481 | else | ||
1482 | { | ||
1483 | name = alias(name); | ||
1484 | rest = llGetSubString(line, match + 1, -1); | ||
1485 | // Corner case, no actual anims. | ||
1486 | if ((match + 1) == llStringLength(line)) rest = ""; | ||
1487 | } | ||
1488 | } | ||
1489 | // Casper AO has a different format. AOConfig, "[Walking]" on a line by itself, followed by one animation per line. | ||
1490 | // I vaguely remember that might be the ZHAO I format. | ||
1491 | // Though I doubt if ZHAO I ever made it out of SL, think ZHAO II was too popular at the time. | ||
1492 | else if (casperMode) | ||
1493 | { | ||
1494 | rest = llStringTrim(line, STRING_TRIM); | ||
1495 | if ("" != rest) name = section; | ||
1496 | } | ||
1497 | if ("" != name) | ||
1498 | { | ||
1499 | found = llListFindList(States, [name]); | ||
1500 | if ((0 <= found) && ((found % 2) == 0)) | ||
1501 | { | ||
1502 | string data = llList2String(States, found + 1); | ||
1503 | if ("" != data) data += "|"; | ||
1504 | States = llListReplaceList(States, [name, data + rest], found, found + 1); | ||
1505 | } | ||
1506 | else States += [name, rest]; | ||
1507 | } | ||
1508 | } | ||
1509 | l = llGetListLength(States); | ||
1510 | for (i = 0; i < l; i += 2) | ||
1511 | { | ||
1512 | list nA = llParseStringKeepNulls(llList2String(States, i + 1), ["|"], []); | ||
1513 | integer m = llGetListLength(nA); | ||
1514 | integer j; | ||
1515 | for (j = 0; j < m; ++j) | ||
1516 | { | ||
1517 | list a = llCSV2List(llList2String(nA, j)); | ||
1518 | integer n = llGetListLength(a); | ||
1519 | integer k; | ||
1520 | for (k = 0; k < n; ++k) | ||
1521 | checkAnim(llList2String(a, k)); | ||
1522 | } | ||
1523 | } | ||
1524 | d("Read " + card + " in " + (string) (llGetTimeOfDay() - now) + " seconds."); | ||
1525 | return TRUE; | ||
1526 | } | ||
1527 | return FALSE; | ||
1528 | } | ||
1529 | |||
1530 | list isPoseAO(integer f) | ||
1531 | { | ||
1532 | if ("" != Pose) | ||
1533 | { | ||
1534 | integer p = findPose(Pose); | ||
1535 | if (-1 != p) | ||
1536 | { | ||
1537 | list ps = llParseStringKeepNulls(llList2String(Poses, p + psANIM), ["|"], []); | ||
1538 | if ("~" != llList2String(ps, f / pSTRIDE)) | ||
1539 | return ps; | ||
1540 | } | ||
1541 | } | ||
1542 | return []; | ||
1543 | } | ||
1544 | |||
1545 | checkAO() | ||
1546 | { | ||
1547 | string newAnim; | ||
1548 | integer l = llGetListLength(Sitters); | ||
1549 | integer fast; integer i; integer f; float dpth; | ||
1550 | if (NULL_KEY != TheirKey) return; | ||
1551 | |||
1552 | if (Attached) | ||
1553 | { | ||
1554 | // if (llGetAgentInfo(Owner) & AGENT_ALWAYS_RUN) fast = 1; else fast = 0; | ||
1555 | newAnim = llGetAnimation(Owner); | ||
1556 | vAnim = newAnim; | ||
1557 | } | ||
1558 | else | ||
1559 | { | ||
1560 | // TODO - when updateControls() figures out fast mode, use that. | ||
1561 | newAnim = vAnim; | ||
1562 | } | ||
1563 | if (("" == Pose) && ("0" == getSetting("AO"))) | ||
1564 | { | ||
1565 | stopAnims(Owner); | ||
1566 | AOspeed = 1.0; | ||
1567 | osSetSpeed(Owner, 1.0); | ||
1568 | return; | ||
1569 | } | ||
1570 | string oldAnim = newAnim; | ||
1571 | //d("checkAO() " + newAnim); | ||
1572 | AOspeed = 0.0; | ||
1573 | integer flying = llListFindList(flyStates, [vAnim]); | ||
1574 | if (-1 != flying) | ||
1575 | { | ||
1576 | float water = llWater(ZERO_VECTOR); | ||
1577 | float ground = llGround(ZERO_VECTOR); | ||
1578 | integer fly = ("Flying" == vAnim); | ||
1579 | // if (fly && !fast) newAnim = "FlyingSlow"; | ||
1580 | if (water > ground) // First check if we can even be under water. | ||
1581 | { | ||
1582 | // In Opensim, pos.z is actually a double, and OpenSim can't do equality for doubles & floats. | ||
1583 | vector pos = llGetPos(); float z = pos.z; // Coz OpenSim can't do llGetPos().z | ||
1584 | if (z <= water) | ||
1585 | { | ||
1586 | if (0 > Bobbing) Bobbing++; | ||
1587 | Swimming = TRUE; | ||
1588 | dpth = water - z; | ||
1589 | } | ||
1590 | else | ||
1591 | { | ||
1592 | if (Swimming) | ||
1593 | { // That's metres of water depth before it figures you don't have enough to swim in. | ||
1594 | if ((z > water) && ((water - ground) > 1.5)) | ||
1595 | { // Push you back into the water. | ||
1596 | vector velocity = llGetVel(); | ||
1597 | dpth = 0.1; | ||
1598 | velocity.x = 0.0; velocity.y = 0.0; | ||
1599 | if (10 > velocity.z) velocity.z = 10; | ||
1600 | velocity.z *= -4; | ||
1601 | if ("Swimming Up" == alias("~" +vAnim)) | ||
1602 | { | ||
1603 | Bobbing++; | ||
1604 | if (3 < Bobbing) | ||
1605 | { // Switch to flying. | ||
1606 | Bobbing = 0; | ||
1607 | Swimming = FALSE; | ||
1608 | } | ||
1609 | } | ||
1610 | // TODO - this wont work in vehicle mode. | ||
1611 | if (Swimming) | ||
1612 | llApplyImpulse(llGetMass() * velocity, FALSE); | ||
1613 | } | ||
1614 | else // Switch to "walking" mode. | ||
1615 | Swimming = FALSE; | ||
1616 | } | ||
1617 | } | ||
1618 | } // No water to swim in here. | ||
1619 | else Swimming = FALSE; | ||
1620 | } | ||
1621 | else // Not in fly mode. | ||
1622 | Swimming = FALSE; | ||
1623 | // llParticleSystem([]); | ||
1624 | if (Swimming) | ||
1625 | { | ||
1626 | if (Attached) AOspeed = 0.1; | ||
1627 | else AOspeed = 0.5; | ||
1628 | newAnim = alias("~" + newAnim); | ||
1629 | // updateParticles(); | ||
1630 | } | ||
1631 | else if (-1 != flying) | ||
1632 | { | ||
1633 | if (Attached) AOspeed = 1.0; | ||
1634 | else AOspeed = 4.0; | ||
1635 | } | ||
1636 | else AOspeed = 1.0; | ||
1637 | if (0.0 < AOspeed) osSetSpeed(Owner, AOspeed + ((AOspeed / 2) * (fast + (2 * (integer) getSetting("super"))))); | ||
1638 | else AOspeed = 1.0; | ||
1639 | for (f = 0; f < l; f += pSTRIDE) | ||
1640 | { | ||
1641 | string anim = llList2String(Sitters, f + pSTATE); | ||
1642 | list anims = llCSV2List(llList2String(Sitters, f + pANIMS)); | ||
1643 | integer g; | ||
1644 | list states = States; | ||
1645 | key id = llList2Key(Sitters, f + pKEY); | ||
1646 | list ps = isPoseAO(f); | ||
1647 | if (0 != llGetListLength(ps)) | ||
1648 | { | ||
1649 | states = [Pose, llList2String(ps, f / pSTRIDE)]; | ||
1650 | newAnim = Pose; | ||
1651 | } | ||
1652 | //d("checkAO " + llKey2Name(id) + " " + anim + " -> " + newAnim + " @ " + (string) AOspeed + " " + Pose); | ||
1653 | if (newAnim != anim) | ||
1654 | { | ||
1655 | g = listFindString(states, newAnim, 2); | ||
1656 | if (-1 != g) | ||
1657 | { | ||
1658 | stopMe(id, f); | ||
1659 | // Ignore sits, since 99.99% of the time what they are sitting on supplies an animation. | ||
1660 | if ((("Sitting" == vAnim) && Attached) || ("" == llList2String(states, g + 1))) | ||
1661 | ; | ||
1662 | else | ||
1663 | { | ||
1664 | list newAnims = llParseString2List(llList2String(states, g + 1), ["|"], []); | ||
1665 | // If there's more than one, randomly switch between them at random times. | ||
1666 | i = llGetListLength(newAnims); | ||
1667 | if (1 < i) addEvent(20.0 + llFrand(20.0), "SYNC R"); | ||
1668 | i = (integer) llFrand((float) i); | ||
1669 | // ZHAO II also allows multiple anims in one set, comma separated. Good idea. | ||
1670 | anims = llCSV2List(llList2String(newAnims, i)); | ||
1671 | //d("ANIMS " + llKey2Name(id) + " " + newAnim + " -> " + llList2String(newAnims, i)); | ||
1672 | for (i = llGetListLength(anims) - 1; i >= 0; --i) | ||
1673 | { | ||
1674 | string a = llList2String(anims, i); | ||
1675 | if (checkAnim(a)) osAvatarPlayAnimation(id, a); | ||
1676 | } | ||
1677 | } | ||
1678 | } | ||
1679 | anim = newAnim; | ||
1680 | } | ||
1681 | Sitters = llListReplaceList(Sitters, [anim, anim, llDumpList2String(anims, ",")], f + pSTATE, f + pANIMS); | ||
1682 | newAnim = oldAnim; | ||
1683 | } | ||
1684 | if (("" == Pose) || ("Swimming Up" == vAnim)) addEvent(Tick * dpth, "checkAO"); | ||
1685 | } | ||
1686 | |||
1687 | stopMe(key id, integer f) | ||
1688 | { | ||
1689 | list anims = llCSV2List(llList2String(Sitters, f + pANIMS)); | ||
1690 | integer i; | ||
1691 | //d("stopMe " + llKey2Name(id) + " " + llList2String(Sitters, f + pANIMS)); | ||
1692 | for (i = llGetListLength(anims) - 1; i >= 0; --i) | ||
1693 | osAvatarStopAnimation(id, llList2String(anims, i)); | ||
1694 | } | ||
1695 | |||
1696 | checkLag() | ||
1697 | { | ||
1698 | float dil = llGetRegionTimeDilation(); // Between 0 and 1. | ||
1699 | float fps = llGetRegionFPS(); // Frames per second, up to 50. | ||
1700 | integer newLag = (integer) (dil * fps); | ||
1701 | if (llAbs(Lag - newLag) > 9) | ||
1702 | { | ||
1703 | string l; | ||
1704 | Lag = newLag; | ||
1705 | Tick = ((60 - (dil * fps)) / 30) + 0.15; | ||
1706 | if (45 <= newLag) l = "No"; | ||
1707 | else if (35 <= newLag) l = "A little"; | ||
1708 | else if (25 <= newLag) l = "Medium"; | ||
1709 | else if (15 <= newLag) l = "Lots of"; | ||
1710 | else l = "Way too much"; | ||
1711 | if (!osIsNpc(llGetOwner())) s(Owner, l + " lag, tick is " + (string) Tick); | ||
1712 | } | ||
1713 | } | ||
1714 | |||
1715 | init() | ||
1716 | { | ||
1717 | ANIMATIONS = readCard("animations"); flyStates = readCard("flystates"); initialStates = readCard("states"); | ||
1718 | Attached = (0 != llGetAttached()); | ||
1719 | llSetSitText("NO SIT"); llSetTouchText("menu"); | ||
1720 | llSetClickAction(CLICK_ACTION_TOUCH); llSetText("", ZERO_VECTOR, 0.0); | ||
1721 | Link = llGetLinkNumber(); | ||
1722 | // None of this works in OpenSim? | ||
1723 | // llSetBuoyancy(-2.0); | ||
1724 | // llSetHoverHeight(10.0, TRUE, 2.0); | ||
1725 | // llGroundRepel(10.0, TRUE, 2.0); | ||
1726 | checkLag(); | ||
1727 | } | ||
1728 | |||
1729 | laterInit() | ||
1730 | { | ||
1731 | if (!loadCard("ZHAO II", FALSE)) // In an object full of random scripts, "Default" is a lousy name. | ||
1732 | if (!loadCard("Default", FALSE)) | ||
1733 | loadCard("AOConfig", TRUE); // Casper AOs are less common. | ||
1734 | if (Attached) | ||
1735 | { | ||
1736 | d("AO mode."); | ||
1737 | Sitters = [Owner, -1, "", "", "", "", llGetListLength(Distances) / 3, 0]; | ||
1738 | checkAO(); | ||
1739 | } | ||
1740 | else | ||
1741 | { | ||
1742 | if (NULL_KEY == BossKey) | ||
1743 | d("Vehicle mode."); | ||
1744 | else | ||
1745 | d("Vehicle mode, slaved to " + llKey2Name(BossKey) + "."); | ||
1746 | integer i = llGetInventoryNumber(INVENTORY_NOTECARD); | ||
1747 | list posCards = []; | ||
1748 | string item; | ||
1749 | while (i-- > 0) | ||
1750 | { | ||
1751 | item = llGetInventoryName(INVENTORY_NOTECARD, i); | ||
1752 | if (llSubStringIndex(item, ".POSITIONS") == 0) posCards += (list) item; | ||
1753 | } | ||
1754 | readPos(posCards); | ||
1755 | d("Loaded " + (string) (llGetListLength(Poses) / psSTRIDE) + " positions in " | ||
1756 | + (string) (llGetTimeOfDay() - Start) + " seconds."); | ||
1757 | checkSitters(FALSE); | ||
1758 | if (NULL_KEY != BossKey) sendPrim(BossKey, "REZ_DONE", []); | ||
1759 | } | ||
1760 | addEvent(6.0 + llFrand(10.0), "SMILE"); | ||
1761 | } | ||
1762 | |||
1763 | die() | ||
1764 | { | ||
1765 | integer l = llGetListLength(Sitters); | ||
1766 | integer f; | ||
1767 | for (f = 0; f < l; f += pSTRIDE) | ||
1768 | stopMe(llList2Key(Sitters, f + pKEY), f); | ||
1769 | d("Deleting myself."); | ||
1770 | sendPrim(BossKey, "DIE_DONE", []); | ||
1771 | if (PERM_COPY & llGetObjectPermMask(MASK_OWNER)) llDie(); | ||
1772 | else s("This no copy object wont delete itself, please delete manually, or take into inventory."); | ||
1773 | } | ||
1774 | |||
1775 | default | ||
1776 | { | ||
1777 | state_entry() | ||
1778 | { | ||
1779 | llSleep(0.2); | ||
1780 | Start = llGetTimeOfDay(); | ||
1781 | Owner = llGetOwner(); | ||
1782 | d("\n\n1AOor2 sending RESET @ " + (string) Start + "\n"); | ||
1783 | ScriptName = llGetScriptName(); ScriptKey = llGetInventoryKey(ScriptName); | ||
1784 | LibraryKey = NULL_KEY; | ||
1785 | sendScript(lRESET, []); | ||
1786 | init(); | ||
1787 | if (bREZ == llGetStartParameter()) BossKey = osGetRezzingObject(); | ||
1788 | } | ||
1789 | |||
1790 | control(key id, integer level, integer edge) {doControl(id, level, edge);} | ||
1791 | link_message(integer sender, integer num, string message, key id) {linky(num, message, id);} | ||
1792 | |||
1793 | changed(integer change) | ||
1794 | { | ||
1795 | if (change & CHANGED_LINK) checkSitters(TRUE); | ||
1796 | if (change & CHANGED_ANIMATION) checkAO(); | ||
1797 | if ((change & CHANGED_TELEPORT) || (change & CHANGED_REGION)) | ||
1798 | { | ||
1799 | if (99.0 > TPangle) | ||
1800 | { | ||
1801 | PIN = (integer) llFrand(DEBUG_CHANNEL - 2) + 2; | ||
1802 | vector RefPos = llGetPos(); | ||
1803 | rotation RefRot = llGetRot(); | ||
1804 | llRezObject("1AOor2 prim", ZERO_VECTOR * RefRot + RefPos, ZERO_VECTOR, (ZERO_ROTATION) * RefRot, PIN); | ||
1805 | llOwnerSay("@setrot:" + (string) TPangle + "=force"); | ||
1806 | s(Owner, "Switching AO to 1ring object."); | ||
1807 | oldController(Owner); | ||
1808 | TPangle = 999.0; | ||
1809 | Chosen = 1; | ||
1810 | } | ||
1811 | } | ||
1812 | } | ||
1813 | |||
1814 | run_time_permissions(integer perm) | ||
1815 | { | ||
1816 | if (PERMISSION_TAKE_CONTROLS & perm) | ||
1817 | { | ||
1818 | key id = llGetPermissionsKey(); | ||
1819 | d("taking controls from " + llKey2Name(id)); | ||
1820 | llTakeControls( CONTROL_FWD | CONTROL_BACK | CONTROL_LEFT | CONTROL_RIGHT | | ||
1821 | CONTROL_ROT_LEFT | CONTROL_ROT_RIGHT | CONTROL_UP | CONTROL_DOWN, TRUE, FALSE); | ||
1822 | ControlTime = llGetTimeOfDay(); | ||
1823 | addEvent(Tick * Smooth, "Keys"); | ||
1824 | } | ||
1825 | if (PERMISSION_CONTROL_CAMERA & perm) | ||
1826 | { | ||
1827 | lookAtMe(llGetPermissionsKey()); | ||
1828 | if ((llGetPermissionsKey() != Controller) && (NULL_KEY != Controller)) | ||
1829 | { | ||
1830 | d("PERMISSION_CONTROL_CAMERA requesting camera and controls from " + llKey2Name(Controller)); | ||
1831 | llRequestPermissions(Controller, PERMISSION_CONTROL_CAMERA | PERMISSION_TAKE_CONTROLS); | ||
1832 | } | ||
1833 | } | ||
1834 | } | ||
1835 | } | ||
diff --git a/1chatter.lsl b/1chatter.lsl new file mode 100644 index 0000000..fdad263 --- /dev/null +++ b/1chatter.lsl | |||
@@ -0,0 +1,1597 @@ | |||
1 | |||
2 | // Library of generic functions for other scripts to use. | ||
3 | |||
4 | string Version = "1chatter v0.1 dev version"; | ||
5 | |||
6 | // BEGIN boilerplate. | ||
7 | integer DEBUG = FALSE; | ||
8 | float Start; | ||
9 | string ScriptName; | ||
10 | key ScriptKey; | ||
11 | key LibraryKey; | ||
12 | key Owner; | ||
13 | string URL; | ||
14 | |||
15 | list Commands; | ||
16 | integer cNAME = 0; | ||
17 | integer cARGS = 1; | ||
18 | integer cAUTH = 2; | ||
19 | integer cSTRIDE = 3; | ||
20 | |||
21 | list Scanners = []; // Current set of scans. | ||
22 | integer nMENU = 0; // Menu that started the scan. | ||
23 | integer nID = 1; // Avatar UUID we are doing this scan for. | ||
24 | integer nTYPE = 2; // The type of scan. | ||
25 | integer nTITLE = 3; // Title of the menu to show. | ||
26 | integer nCMD = 4; // Command to run with the result. | ||
27 | integer nSTRIDE = 5; | ||
28 | |||
29 | // utilities commands, "l"ibrary. | ||
30 | string lSEP = "$!#"; // Used to seperate lists when sending them as strings. | ||
31 | integer lRESET = -1; | ||
32 | integer lRESET_DONE = -2; | ||
33 | integer lALIAS = -3; | ||
34 | integer lALIAS_DONE = -4; | ||
35 | integer lSETTING = -5; | ||
36 | integer lSETTING_DONE = -6; | ||
37 | integer lSUBSTITUTE = -7; | ||
38 | integer lSUBSTITUTE_DONE = -8; | ||
39 | integer lNEXT_WORD = -9; | ||
40 | integer lNEXT_WORD_DONE = -10; | ||
41 | integer lCONTROL = -13; | ||
42 | integer lCONTROL_DONE = -14; | ||
43 | integer lCMD = -15; | ||
44 | integer lCMD_DONE = -16; | ||
45 | integer lSETTINGS = -17; | ||
46 | integer lSETTINGS_DONE = -18; | ||
47 | integer lSCAN = -19; | ||
48 | integer lDYNAMIC = -20; | ||
49 | integer lMENU = -21; | ||
50 | |||
51 | d(string m) {if (DEBUG) llInstantMessage(Owner, llGetScriptName() + ": " + m);} | ||
52 | D(string m) {llRegionSay(DEBUG_CHANNEL, llGetScriptName() + ": " + m);} | ||
53 | s(string m) {s(Owner, m);} | ||
54 | s(key id, string m) {if (id == Owner) llOwnerSay(m); else llInstantMessage(id, m);} | ||
55 | sendScript(integer cmd, list args) {sendScript(LibraryKey, cmd, ScriptName, args);} | ||
56 | sendScript(key them, integer cmd, list args) {sendScript(them, cmd, inKey2Name(them), args);} | ||
57 | sendScript(integer cmd, string name, list args) {sendScript(LibraryKey, cmd, name, args);} | ||
58 | sendScript(key them, integer cmd, string name, list args) | ||
59 | { | ||
60 | llMessageLinked(LINK_SET, cmd, llDumpList2String([ScriptKey, name] + args, lSEP), them); | ||
61 | } | ||
62 | sendPrim(key them, string cmd, list args) {osMessageObject(them, llDumpList2String([ScriptName] + args, lSEP));} | ||
63 | |||
64 | string inKey2Name(key k) | ||
65 | { | ||
66 | if (k == LibraryKey) return "1chatter"; | ||
67 | integer i = llGetInventoryNumber(INVENTORY_SCRIPT); | ||
68 | while (i-- > 0) | ||
69 | { | ||
70 | string n = llGetInventoryName(INVENTORY_SCRIPT, i); | ||
71 | if (llGetInventoryKey(n) == k) return n; | ||
72 | } | ||
73 | return k; | ||
74 | } | ||
75 | |||
76 | integer uSubStringLastIndex(string hay, string pin) | ||
77 | { | ||
78 | integer i2 = -1; | ||
79 | integer i; | ||
80 | |||
81 | if (pin == "") | ||
82 | return 0; | ||
83 | while (~i) | ||
84 | { | ||
85 | i = llSubStringIndex(llGetSubString(hay, ++i2, -1), pin); | ||
86 | i2 += i; | ||
87 | } | ||
88 | return i2; | ||
89 | } | ||
90 | |||
91 | integer listFindString(list lst, string name, integer stride) | ||
92 | { | ||
93 | integer f = llListFindList(lst, [name]); | ||
94 | integer ix = f / stride; | ||
95 | |||
96 | // Round to nearest stride. | ||
97 | ix = ix * stride; | ||
98 | |||
99 | // Sanity check, make sure we found a name, not something else, else do it the slow way. | ||
100 | if ((-1 != f) && (ix != f)) | ||
101 | { | ||
102 | integer l = llGetListLength(lst); | ||
103 | integer i; | ||
104 | |||
105 | f = -1; | ||
106 | for (i = 0; i < l; i += stride) | ||
107 | { | ||
108 | if (llList2String(lst, i) == name) | ||
109 | { | ||
110 | f = i; | ||
111 | i = l; | ||
112 | } | ||
113 | } | ||
114 | } | ||
115 | return f; | ||
116 | } | ||
117 | |||
118 | string getFor(string name) | ||
119 | { | ||
120 | string fr; | ||
121 | integer i = llSubStringIndex(name, "."); | ||
122 | |||
123 | if (-1 != i) | ||
124 | fr = llGetSubString(name, 0, i); | ||
125 | return fr; | ||
126 | } | ||
127 | |||
128 | string noFor(string name) | ||
129 | { | ||
130 | string r = name; | ||
131 | integer i = llSubStringIndex(name, "."); | ||
132 | |||
133 | if (-1 != i) | ||
134 | r = llGetSubString(name, i + 1, -1); | ||
135 | return r; | ||
136 | } | ||
137 | |||
138 | list validateName(string var, string type) | ||
139 | { | ||
140 | list v = llParseStringKeepNulls(var, ["."], []); | ||
141 | if (2 != llGetListLength(v)) | ||
142 | { | ||
143 | d("Invalid " + type + " name - " + var); | ||
144 | return ["", var]; | ||
145 | } | ||
146 | var = llList2String(v, 1); | ||
147 | if ("setting" == type) | ||
148 | var = llToUpper(var); | ||
149 | return [llList2String(v, 0) + ".", var]; | ||
150 | } | ||
151 | |||
152 | |||
153 | list Aliases; | ||
154 | integer aALIAS = 0; | ||
155 | integer aNAME = 1; | ||
156 | integer aSTRIDE = 2; | ||
157 | |||
158 | string alias(string n) | ||
159 | { | ||
160 | integer a = listFindString(Aliases, llToLower(n), 2); | ||
161 | if (-1 != a) | ||
162 | n = llList2String(Aliases, a + 1); | ||
163 | return n; | ||
164 | } | ||
165 | |||
166 | |||
167 | // Access "f"rom some source - | ||
168 | integer fINT = 0; // Internal, not sure we need this, means the same script. | ||
169 | integer fCARD = 1; // Read from a note card. | ||
170 | integer fCHAT = 2; // Chat channel. | ||
171 | integer fLINK = 3; // Link message. | ||
172 | integer fMENU = 4; // Menu, in reality this is just a chat channel. | ||
173 | integer fRLV = 5; // RLV, again from a chat channel. | ||
174 | integer fRELAY = 6; // RLV relay. | ||
175 | integer fOSM = 7; // osMessageObject() | ||
176 | integer fHTTP = 8; // From the web. | ||
177 | |||
178 | // LSL security is entirely hopeless, see the notecard "1ring security". | ||
179 | // "a"ccess level - | ||
180 | integer aNONE = 0; | ||
181 | integer aBOSS = 1; | ||
182 | integer aTRUST = 2; | ||
183 | integer aGROUP = 4; | ||
184 | integer aBOSSES = 3; | ||
185 | integer aWEARER = 8; | ||
186 | integer aPRIV = 9; | ||
187 | integer aMORE = 11; | ||
188 | integer aMOST = 15; | ||
189 | integer aPUBLIC = 16; | ||
190 | integer aALL = 31; | ||
191 | list Access = | ||
192 | [ | ||
193 | "NONE", 0, | ||
194 | "BOSS", 1, | ||
195 | "TRUST", 2, | ||
196 | "GROUP", 4, | ||
197 | "BOSSES", 3, | ||
198 | "WEARER", 8, | ||
199 | "PRIV", 9, | ||
200 | "MORE", 11, | ||
201 | "MOST", 15, | ||
202 | "PUBLIC", 16, | ||
203 | "ALL", 31 | ||
204 | ]; | ||
205 | |||
206 | integer decodeAccess(string a) | ||
207 | { | ||
208 | integer r = aALL; | ||
209 | integer f = listFindString(Access, a, 2); | ||
210 | if (-1 != f) | ||
211 | r = llList2Integer(Access, f + 1); | ||
212 | return r; | ||
213 | } | ||
214 | |||
215 | integer access(key id, string what, string fr, integer auth, integer bitch) | ||
216 | { | ||
217 | list bosses = llParseString2List(getSetting(fr + "BOSS"), [","], []); | ||
218 | integer l = llGetListLength(bosses); | ||
219 | integer j = aPUBLIC; | ||
220 | integer i; | ||
221 | integer b = FALSE; | ||
222 | |||
223 | //d("access " + id + " " + what + " FOR " + fr + " " + auth); | ||
224 | if (id == Owner) j = j | aWEARER | aGROUP; | ||
225 | if (llSameGroup(id)) j = j | aGROUP; | ||
226 | if ("1" == getSetting(fr + "PUBLIC")) j = j | aTRUST; | ||
227 | for (i = 0; i < l; ++i) | ||
228 | { | ||
229 | if (id == llList2Key(bosses, i)) | ||
230 | { | ||
231 | j = j | aBOSS | aTRUST; | ||
232 | i = l; | ||
233 | } | ||
234 | } | ||
235 | bosses = llParseString2List(getSetting(fr + "TRUSTEE"), [","], []); | ||
236 | l = llGetListLength(bosses); | ||
237 | for (i = 0; i < l; ++i) | ||
238 | { | ||
239 | if (id == llList2Key(bosses, i)) | ||
240 | { | ||
241 | j = j | aTRUST; | ||
242 | i = l; | ||
243 | } | ||
244 | } | ||
245 | bosses = llParseString2List(getSetting(fr + "BLOCKED"), [","], []); | ||
246 | l = llGetListLength(bosses); | ||
247 | for (i = 0; i < l; ++i) | ||
248 | { | ||
249 | if (id == llList2Key(bosses, i)) | ||
250 | { | ||
251 | b = TRUE; | ||
252 | j = 0; | ||
253 | i = l; | ||
254 | } | ||
255 | } | ||
256 | j = auth & j; | ||
257 | |||
258 | if (bitch && (0 == j)) | ||
259 | { | ||
260 | if (b) | ||
261 | { | ||
262 | s(Owner, "Blocked user " + llKey2Name(id) + " failed to use this."); | ||
263 | s(id, "You are blocked from using this."); | ||
264 | } | ||
265 | else | ||
266 | { | ||
267 | string who; | ||
268 | |||
269 | if (aNONE == auth) who = "no "; | ||
270 | if (auth & aWEARER) who += "'" + llKey2Name(llGetKey()) + "' owner, "; | ||
271 | if (auth & aBOSS) who += noFor(alias(fr + "boss")) + ", "; | ||
272 | if (auth & aTRUST) who += noFor(alias(fr + "trusted")) + ", "; | ||
273 | if (auth & aGROUP) who += "group, "; | ||
274 | if (auth & aPUBLIC) who += "public, "; | ||
275 | if (", " == llGetSubString(who, -2, -1)) | ||
276 | who = llGetSubString(who, 0, -3) + " "; | ||
277 | i = uSubStringLastIndex(who, ", "); | ||
278 | if (-1 != i) | ||
279 | who = llGetSubString(who, 0, i + 1) + "and " + llGetSubString(who, i + 2, -1); | ||
280 | s(id, "The " + what + " is allowed only for " + who + "users."); | ||
281 | } | ||
282 | } | ||
283 | return j; | ||
284 | } | ||
285 | |||
286 | |||
287 | string TRUEs = "t1aopswy"; | ||
288 | string FALSEs = "f0bgnuz"; | ||
289 | integer isBool(string b) | ||
290 | { | ||
291 | integer r = FALSE; | ||
292 | |||
293 | if ("" != b) | ||
294 | r = (-1 != llSubStringIndex(TRUEs, llToLower(llGetSubString(b, 0, 0)))); | ||
295 | return r; | ||
296 | } | ||
297 | |||
298 | // "s"ettings list - | ||
299 | list Settings; | ||
300 | integer sNAME = 0; | ||
301 | integer sTYPE = 1; | ||
302 | integer sVALUE = 2; | ||
303 | integer sAUTH = 3; | ||
304 | integer sSTRIDE = 4; | ||
305 | |||
306 | string getSetting(string var) | ||
307 | { | ||
308 | list v = validateName(var, "setting"); | ||
309 | var = llList2String(v, 0) + llList2String(v, 1); | ||
310 | string result; | ||
311 | integer f = listFindString(Settings, var, sSTRIDE); | ||
312 | if (-1 != f) | ||
313 | result = llList2String(Settings, f + sVALUE); | ||
314 | return result; | ||
315 | } | ||
316 | |||
317 | integer setSetting(key id, string var, string val, integer source) | ||
318 | { | ||
319 | list v = validateName(var, "setting"); | ||
320 | string fr = llList2String(v, 0); | ||
321 | var = fr + llList2String(v, 1); | ||
322 | integer f = listFindString(Settings, var, sSTRIDE); | ||
323 | |||
324 | if (-1 != f) | ||
325 | { | ||
326 | integer acs = access(id, var + " setting", fr, llList2Integer(Settings, f + sAUTH), TRUE); | ||
327 | if (0 == acs) | ||
328 | return acs; | ||
329 | |||
330 | string type = llToUpper(llList2String(Settings, f + sTYPE)); | ||
331 | |||
332 | if ("Y" == type) | ||
333 | val = (string) isBool(val); | ||
334 | Settings = llListReplaceList(Settings, [val], f + sVALUE, f + sVALUE); | ||
335 | //d("setSetting " + var + " = " + val); | ||
336 | if (fCARD != source) | ||
337 | { | ||
338 | string card = "." + llGetSubString(fr, 0, -2) + ".settings"; | ||
339 | list data; | ||
340 | if (NULL_KEY != llGetInventoryKey(card)) | ||
341 | data = llParseString2List(osGetNotecard(card), ["\n"], []); | ||
342 | integer l = llGetListLength(data); | ||
343 | string ar = llList2String(v, 1); | ||
344 | |||
345 | // TODO - could use a bit more sophistication here, but it works as is. | ||
346 | for (f = 0; f < l; ++f) | ||
347 | { | ||
348 | list p = llParseString2List(llList2String(data, f), ["="], []); | ||
349 | string k = llList2String(p, 0); | ||
350 | if (k == var) | ||
351 | { | ||
352 | data = llListReplaceList(data, [k + "=" + val], f, f); | ||
353 | f = l + 1; | ||
354 | } | ||
355 | if (k == ar) | ||
356 | { | ||
357 | data = llListReplaceList(data, [k + "=" + val], f, f); | ||
358 | f = l + 1; | ||
359 | } | ||
360 | } | ||
361 | if (f == l) | ||
362 | data += [ar + "=" + val]; | ||
363 | llRemoveInventory(card); | ||
364 | osMakeNotecard(card, data); | ||
365 | d("setSetting " + var + " saved to " + card); | ||
366 | } | ||
367 | // Hopefully this wont result in any recursive setSettings calls. | ||
368 | doThing(id, "SET " + var + "=" + val, fr, llList2String(v, 1), val, source); | ||
369 | |||
370 | return acs; | ||
371 | } | ||
372 | return 0; | ||
373 | } | ||
374 | |||
375 | // TODO - Hmmm, only called once. | ||
376 | readSettings(string card) | ||
377 | { | ||
378 | float now = llGetTimeOfDay(); | ||
379 | key boss = llGetOwner(); | ||
380 | string fr = card + "."; | ||
381 | |||
382 | card = "." + card + ".settings"; | ||
383 | if (NULL_KEY != llGetInventoryKey(card)) | ||
384 | { | ||
385 | list d = llParseString2List(osGetNotecard(card), ["\n"], []); | ||
386 | integer l = llGetListLength(d); | ||
387 | integer i; | ||
388 | |||
389 | for (i = 0; i < l; ++i) | ||
390 | { | ||
391 | string cmd = alias(llList2String(d, i)); | ||
392 | integer j = llSubStringIndex(cmd, "="); | ||
393 | if (-1 == j) | ||
394 | j = llSubStringIndex(cmd, " "); | ||
395 | if (-1 == j) | ||
396 | j = llStringLength(cmd); | ||
397 | string f = fr; | ||
398 | string var = llStringTrim(llGetSubString(cmd, 0, j - 1), STRING_TRIM); | ||
399 | |||
400 | if (-1 != llSubStringIndex(var, ".")) | ||
401 | f = ""; | ||
402 | setSetting(boss, f + var, llStringTrim(llGetSubString(cmd, j + 1, -1), STRING_TRIM), fCARD); | ||
403 | } | ||
404 | } | ||
405 | else | ||
406 | setSetting(boss, fr + "BOSS", Owner, fCARD); | ||
407 | // d("Read " + card + " in " + (string) (llGetTimeOfDay() - now) + " seconds."); | ||
408 | } | ||
409 | |||
410 | dumpAliases(list s, string title) | ||
411 | { | ||
412 | integer l = llGetListLength(s); | ||
413 | integer i; | ||
414 | |||
415 | d("v--------------- " + title); | ||
416 | for (i = 0; i < l; i += aSTRIDE) | ||
417 | { | ||
418 | d( | ||
419 | llList2String(s, i + aALIAS) + "~" + | ||
420 | llList2String(s, i + aNAME) | ||
421 | ); | ||
422 | } | ||
423 | d("^---------------- LIBRARY"); | ||
424 | } | ||
425 | |||
426 | dumpCommands(list s, string title) | ||
427 | { | ||
428 | integer l = llGetListLength(s); | ||
429 | integer i; | ||
430 | |||
431 | d("v--------------- " + title); | ||
432 | for (i = 0; i < l; i += cSTRIDE) | ||
433 | { | ||
434 | d( | ||
435 | llList2String(s, i + cNAME) + "~" + | ||
436 | llList2String(s, i + cARGS) + "~" + | ||
437 | llList2String(s, i + cAUTH) | ||
438 | ); | ||
439 | } | ||
440 | d("^---------------- LIBRARY"); | ||
441 | } | ||
442 | |||
443 | dumpSettings(list s, string title) | ||
444 | { | ||
445 | integer l = llGetListLength(s); | ||
446 | integer i; | ||
447 | |||
448 | d("v--------------- " + title); | ||
449 | for (i = 0; i < l; i += sSTRIDE) | ||
450 | { | ||
451 | d( | ||
452 | llList2String(s, i + sNAME) + "~" + | ||
453 | llList2String(s, i + sTYPE) + "~" + | ||
454 | llList2String(s, i + sVALUE) + "~" + | ||
455 | llList2String(s, i + sAUTH) | ||
456 | ); | ||
457 | } | ||
458 | d("^---------------- LIBRARY"); | ||
459 | } | ||
460 | |||
461 | |||
462 | |||
463 | // "e"vents | ||
464 | float NextEvent = 307584000.0; // Ten years. | ||
465 | list Events = []; | ||
466 | integer eTIME = 0; // Time of it's next event. | ||
467 | integer eSCRIPT = 1; // Rest of scriptlet after the delay command. | ||
468 | integer eKEY = 2; // Key of script asking for this timer. | ||
469 | integer eSTRIDE = 3; | ||
470 | |||
471 | |||
472 | addEvent(float delay, string cmds, key id) | ||
473 | { | ||
474 | float now = llGetTimeOfDay(); | ||
475 | list ev = [now + delay, cmds, id]; | ||
476 | integer f = findEventByID(cmds); | ||
477 | |||
478 | if (0.0 >= delay) | ||
479 | { | ||
480 | if (-1 != f) | ||
481 | Events = llListReplaceList(Events, [], f, f + eSTRIDE - 1); | ||
482 | } | ||
483 | else if (-1 == f) | ||
484 | Events += ev; | ||
485 | else | ||
486 | Events = llListReplaceList(Events, ev, f, f + eSTRIDE - 1); | ||
487 | Events = llListSort(Events, eSTRIDE, TRUE); | ||
488 | if (llList2Float(Events, 0) < NextEvent) | ||
489 | { | ||
490 | NextEvent = llList2Float(Events, 0); | ||
491 | llSetTimerEvent(NextEvent - now); | ||
492 | } | ||
493 | } | ||
494 | |||
495 | integer findEventByID(string id) | ||
496 | { | ||
497 | integer f = llListFindList(Events, [id]); | ||
498 | integer ix = f / eSTRIDE; | ||
499 | |||
500 | // Round to nearest stride. | ||
501 | ix = ix * eSTRIDE; | ||
502 | |||
503 | if ((-1 != f) && ((ix + eSCRIPT) == f)) | ||
504 | return f - eSCRIPT; | ||
505 | else | ||
506 | return -1; | ||
507 | } | ||
508 | |||
509 | |||
510 | // For marking up menu buttons. | ||
511 | string Enclosed = "ⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ⓪⓵⓶⓷⓸⓹⓺⓻⓼⓽ⒶⒷⒸⒹⒺⒻⒼⒽⒾⒿⓀⓁⓂⓃⓄⓅⓆⓇⓈⓉⓊⓋⓌⓍⓎⓏ"; | ||
512 | string DownsideUp = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; | ||
513 | string UpsideDown = "ɐqɔpǝɟɓɥᴉſʞןɯuodbɹsʇnʌʍxʎz0lᘔƐᔭ59Ɫ86∀qↃpƎℲ⅁HIᒋʞ⅂ꟽNOԀÒᴚS⊥∩ɅMX⅄Z"; | ||
514 | |||
515 | string enclosed(string text) | ||
516 | { | ||
517 | string result; | ||
518 | integer l = llStringLength(text); | ||
519 | integer i; | ||
520 | |||
521 | for (i = 0; i < l; ++i) | ||
522 | { | ||
523 | string c = llGetSubString(text, i, i); | ||
524 | integer f = llSubStringIndex(DownsideUp, c); | ||
525 | if (-1 != f) | ||
526 | c = llGetSubString(Enclosed, f, f); | ||
527 | result += c; | ||
528 | } | ||
529 | return result; | ||
530 | } | ||
531 | |||
532 | string upsideDown(string text) | ||
533 | { | ||
534 | string result; | ||
535 | integer i = llStringLength(text) - 1; | ||
536 | |||
537 | for (; i > -1; --i) | ||
538 | { | ||
539 | string c = llGetSubString(text, i, i); | ||
540 | integer f = llSubStringIndex(DownsideUp, c); | ||
541 | if (-1 != f) | ||
542 | c = llGetSubString(UpsideDown, f, f); | ||
543 | result += c; | ||
544 | } | ||
545 | return result; | ||
546 | } | ||
547 | |||
548 | |||
549 | // "m"enus | ||
550 | string MainMenu; | ||
551 | list Menus; // List of menus. | ||
552 | integer mNAME = 0; // Name of menu. | ||
553 | integer mTITLE = 1; // Title for the menu. | ||
554 | integer mAUTH = 2; // Authorised users of menu - 0 = wearer, 1 = boss, 2 = all | ||
555 | integer mENTRIES = 3; // | separated list of entries. | ||
556 | integer mCMDS = 4; // | separated list of commands, matching the entries. | ||
557 | integer mDYN = 5; // Dynamic menu, autoremove it when done. | ||
558 | integer mSTRIDE = 6; | ||
559 | |||
560 | integer findMenu(string name) | ||
561 | { | ||
562 | return listFindString(Menus, name, mSTRIDE); | ||
563 | } | ||
564 | |||
565 | saveMenu(string name, string title, integer auth, string entries, string commands, integer dyn) | ||
566 | { | ||
567 | integer f = findMenu(name); | ||
568 | |||
569 | if (-1 == f) | ||
570 | Menus += [name, title, auth, entries, commands, dyn]; | ||
571 | else | ||
572 | { | ||
573 | if ("" == title) | ||
574 | title = llList2String(Menus, f + mTITLE); | ||
575 | if ("" == entries) | ||
576 | entries = llList2String(Menus, f + mENTRIES); | ||
577 | if ("" == commands) | ||
578 | commands = llList2String(Menus, f + mCMDS); | ||
579 | Menus = llListReplaceList(Menus, [name, title, auth, entries, commands, dyn], f, f + mSTRIDE - 1); | ||
580 | } | ||
581 | } | ||
582 | |||
583 | addMenuItem(string fr, string menu, string cmd, list args) | ||
584 | { | ||
585 | integer f = findMenu(menu); | ||
586 | |||
587 | if (-1 != f) | ||
588 | { | ||
589 | string e = llList2String(Menus, f + mENTRIES); | ||
590 | string c = llList2String(Menus, f + mCMDS); | ||
591 | |||
592 | if (llGetListLength(args) < 2) args += [" "]; | ||
593 | if ("" != e) e += "|"; | ||
594 | if ("" != c) c += "|"; | ||
595 | if ("TOGGLE" == cmd) | ||
596 | e += getToggle(fr, llList2String(args, 0)); | ||
597 | else | ||
598 | e += llList2String(args, 0); | ||
599 | if ("TOMENU" == cmd) e += "…"; | ||
600 | c += llDumpList2String(llListReplaceList(args, [], 0, 0), " "); | ||
601 | saveMenu(menu, llList2String(Menus, f + mTITLE), llList2Integer(Menus, f + mAUTH), e, c, llList2Integer(Menus, f + mDYN)); | ||
602 | } | ||
603 | } | ||
604 | |||
605 | dynamicMenu(key id, string menu, string name, string title, string entries, string command) | ||
606 | { | ||
607 | //d("dynamicMenu " + menu + " -> " + name + "\n" + id + "@" + name); | ||
608 | saveMenu(id + "@" + name, title, aALL, entries, command, TRUE); | ||
609 | saveMuser(id, id + "@" + name, entries, -1, menu); | ||
610 | showMenu(id); | ||
611 | } | ||
612 | |||
613 | string getToggle(string fr, string item) | ||
614 | { | ||
615 | string result = getSetting(fr + item); | ||
616 | |||
617 | if ("1" != result) | ||
618 | result = "☐"; | ||
619 | else | ||
620 | result = "▣"; | ||
621 | return result + " " + item; | ||
622 | } | ||
623 | |||
624 | toggleMenu(key id, string fr, list entries, integer e, integer m) | ||
625 | { | ||
626 | integer on = 0; | ||
627 | string this = llList2String(entries, e); | ||
628 | string result = llStringTrim(llGetSubString(this, 1, -1), STRING_TRIM); | ||
629 | string t = llGetSubString(this, 0, 0); | ||
630 | if ("☐" == t) {t = "▣"; on = 1;} | ||
631 | else if ("▣" == t) {t = "☐"; on = 0;} | ||
632 | else if ("○" == t) {t = "◉"; on = 1;} | ||
633 | else if ("◉" == t) {t = "○"; on = 0;} | ||
634 | else {s(id, "Not a menu toggle - " + result); return;} | ||
635 | if (0 == setSetting(id, fr + result, (string) on, fMENU)) | ||
636 | return; | ||
637 | //d("toggleMenu " + result + " " + on); | ||
638 | // TODO - if it's a radio group, make sure one and only on is turned on. | ||
639 | entries = llListReplaceList(entries, [t + " " + result], e, e); | ||
640 | saveMenu( llList2String(Menus, m + mNAME), | ||
641 | llList2String(Menus, m + mTITLE), | ||
642 | llList2Integer(Menus, m + mAUTH), | ||
643 | llDumpList2String(entries, "|"), | ||
644 | llList2String(Menus, m + mCMDS), | ||
645 | llList2Integer(Menus, m + mDYN)); | ||
646 | } | ||
647 | |||
648 | |||
649 | // Menu "u"sers | ||
650 | list Musers; // List of menu users. | ||
651 | integer uKEY = 0; // Key of user. | ||
652 | integer uCHAN = 1; // Listen channel for user. | ||
653 | integer uLSTN = 2; // Listen handle. | ||
654 | integer uCURRENT = 3; // Current menu of user. | ||
655 | integer uENTRIES = 4; // Current entries for currently in flight menu. | ||
656 | integer uPAGE = 5; // Current page of long menu of user. | ||
657 | integer uSTACK = 6; // | separated menu stack list. | ||
658 | integer uTIME = 7; // Timestamp of last menu shown. | ||
659 | integer uSTRIDE = 8; | ||
660 | |||
661 | // page = -1 means the menu items fit on a single dialog, otherwise it's the page of items. | ||
662 | saveMuser(key avatar, string current, string entries, integer page, string stack) | ||
663 | { | ||
664 | integer f = listFindString(Musers, avatar, uSTRIDE); | ||
665 | float n = llGetTimeOfDay(); | ||
666 | |||
667 | //d("saveMuser " + f + " " + llKey2Name(avatar) + " = " + current + " ~ " + stack + "\n" + entries); | ||
668 | if (-1 == f) | ||
669 | { | ||
670 | stack = "|"; | ||
671 | integer c = (integer) ("0x" + llGetSubString((string) avatar, -4, -1)) + (integer) llFrand(12345); | ||
672 | integer h = llListen(c, "", avatar, ""); | ||
673 | Musers += [avatar, c, h, current, entries, page, stack, n]; | ||
674 | } | ||
675 | else | ||
676 | { | ||
677 | string old = llList2String(Musers, f + uSTACK); | ||
678 | if ("-1" == stack) | ||
679 | stack = old; | ||
680 | else if ("-1" == current) | ||
681 | { | ||
682 | list lst = llParseStringKeepNulls(old, ["|"], []); | ||
683 | current = llList2String(lst, 0); | ||
684 | lst = llListReplaceList(lst, [], 0, 0); | ||
685 | if (1 == llGetListLength(lst)) | ||
686 | { | ||
687 | llListenRemove(llList2Integer(Musers, f + uLSTN)); | ||
688 | Musers = llListReplaceList(Musers, [], f, f + uSTRIDE - 1); | ||
689 | return; | ||
690 | } | ||
691 | stack = llDumpList2String(lst, "|"); | ||
692 | entries = ""; | ||
693 | } | ||
694 | else if (-1 != page) | ||
695 | stack = old; | ||
696 | else if ("" == stack) | ||
697 | ; | ||
698 | else if ("" != old) | ||
699 | stack += "|" + old; | ||
700 | Musers = llListReplaceList(Musers, [avatar, | ||
701 | llList2Integer(Musers, f + uCHAN), llList2Integer(Musers, f + uLSTN), | ||
702 | current, entries, page, stack, n], f, f + uSTRIDE - 1); | ||
703 | } | ||
704 | //d("saveMu2er " + avatar + " = " + current + " ~ " + stack); | ||
705 | } | ||
706 | |||
707 | removeMenu(string r) | ||
708 | { | ||
709 | integer f = listFindString(Menus, r, mSTRIDE); | ||
710 | if (-1 != f) | ||
711 | Menus = llListReplaceList(Menus, [], f, f + mSTRIDE - 1); | ||
712 | else | ||
713 | D("removeMenu() " + r + " not found!"); | ||
714 | } | ||
715 | |||
716 | lastMenu(key id, string r) | ||
717 | { | ||
718 | saveMuser(id, "-1", "", -1, ""); | ||
719 | if ("" != r) removeMenu(r); | ||
720 | } | ||
721 | |||
722 | dumpMenus(list menus) | ||
723 | { | ||
724 | integer l = llGetListLength(menus); | ||
725 | integer i; | ||
726 | for (i = 0; i < l; i += mSTRIDE) | ||
727 | { | ||
728 | d( | ||
729 | llList2String(menus, i + mNAME) + "~" + | ||
730 | llList2String(menus, i + mTITLE) + "~" + | ||
731 | llList2String(menus, i + mAUTH) + "~" + | ||
732 | llList2String(menus, i + mENTRIES) + "~" + | ||
733 | llList2String(menus, i + mCMDS) + "~" + | ||
734 | llList2String(menus, i + mDYN) | ||
735 | ); | ||
736 | } | ||
737 | } | ||
738 | |||
739 | showMenu(key id) | ||
740 | { | ||
741 | integer f = listFindString(Musers, id, uSTRIDE); | ||
742 | |||
743 | if (-1 != f) | ||
744 | { | ||
745 | string menu = llList2String (Musers, f + uCURRENT); | ||
746 | integer chan = llList2Integer(Musers, f + uCHAN); | ||
747 | integer page = llList2Integer(Musers, f + uPAGE); | ||
748 | string fr; | ||
749 | |||
750 | if ("" != menu) | ||
751 | { | ||
752 | list v = validateName(menu, "menu"); | ||
753 | fr = llList2String(v, 0); | ||
754 | menu = fr + llList2String(v, 1); | ||
755 | } | ||
756 | integer m = findMenu(menu); | ||
757 | |||
758 | //d("showMenu " + menu + "... " + fr); | ||
759 | if (-1 != m) | ||
760 | { | ||
761 | integer dM = llList2Integer(Menus, m + mDYN); | ||
762 | if (dM) | ||
763 | fr = llGetSubString(fr, 37, -1); | ||
764 | string version = getSetting(fr + "VERSION"); | ||
765 | list entries = llParseStringKeepNulls(llList2String(Menus, m + mENTRIES), ["|"], []); | ||
766 | list cmds = llParseStringKeepNulls(llList2String(Menus, m + mCMDS), ["|"], []); | ||
767 | string title = llList2String(Menus, m + mTITLE); | ||
768 | |||
769 | if (access(id, menu + " menu", fr, llList2Integer(Menus, m + mAUTH), TRUE)) | ||
770 | { | ||
771 | integer l = llGetListLength(entries); | ||
772 | integer n; | ||
773 | |||
774 | if (llList2Integer(Menus, m + mDYN)) | ||
775 | { | ||
776 | for (; n < l; ++n) | ||
777 | { | ||
778 | string button = llGetSubString(llList2String(entries, n), 0, 23); | ||
779 | entries = llListReplaceList(entries, [button], n, n); | ||
780 | } | ||
781 | n = l; | ||
782 | } | ||
783 | // Show missing items as upside down text. | ||
784 | // Show no access items enclosed. | ||
785 | for (; n < l; ++n) | ||
786 | { | ||
787 | string button = llGetSubString(llList2String(entries, n), 0, 23); | ||
788 | string first = llGetSubString(button, 0, 0); | ||
789 | string last = llGetSubString(button, -1, -1); | ||
790 | entries = llListReplaceList(entries, [button], n, n); | ||
791 | if (("▲" == first) || ("◄" == first) || ("►" == last)) | ||
792 | { | ||
793 | } | ||
794 | else if ("…" == last) | ||
795 | { | ||
796 | string t = button; | ||
797 | button = llGetSubString(button, 0, -2); | ||
798 | integer o = findMenu(fr + button); | ||
799 | if (" " == llList2String(cmds, n)) | ||
800 | { | ||
801 | if (-1 == o) | ||
802 | t = upsideDown(last + button); | ||
803 | else if (!access(id, t, fr, llList2Integer(Menus, o + mAUTH), FALSE)) | ||
804 | t = enclosed(button + last); | ||
805 | entries = llListReplaceList(entries, [t], n, n); | ||
806 | } | ||
807 | } | ||
808 | else if (("☐" == first) || ("▣" == first) || ("○" == first) || ("◉" == first)) | ||
809 | { | ||
810 | button = llStringTrim(llGetSubString(button, 1, -1), STRING_TRIM); | ||
811 | string t = getToggle(fr, button); | ||
812 | integer o = listFindString(Settings, fr + llToUpper(button), sSTRIDE); | ||
813 | |||
814 | if (-1 == o) | ||
815 | t = upsideDown(button + " " + llGetSubString(t, 0, 0)); | ||
816 | else if (!access(id, t, fr, llList2Integer(Settings, o + sAUTH), FALSE)) | ||
817 | t = enclosed(t); | ||
818 | entries = llListReplaceList(entries, [t], n, n); | ||
819 | } | ||
820 | else | ||
821 | { | ||
822 | string frr = fr; | ||
823 | string t = button; | ||
824 | string c = llList2String(cmds, n); | ||
825 | integer o = llSubStringIndex(c, " "); | ||
826 | if (-1 != o) | ||
827 | c = llGetSubString(c, 0, o - 1); | ||
828 | o = llSubStringIndex(c, "."); | ||
829 | if (-1 != o) | ||
830 | { | ||
831 | frr = llGetSubString(c, 0, o); | ||
832 | c = llGetSubString(c, o + 1, -1); | ||
833 | } | ||
834 | o = listFindString(Commands, frr + llToUpper(c), cSTRIDE); | ||
835 | if (("" == c) || (-1 == o)) | ||
836 | t = upsideDown(t); | ||
837 | else if (!access(id, c, frr, llList2Integer(Commands, o + cAUTH), FALSE)) | ||
838 | t = enclosed(t); | ||
839 | entries = llListReplaceList(entries, [t], n, n); | ||
840 | } | ||
841 | } | ||
842 | string mn = menu; | ||
843 | if (dM) | ||
844 | mn = llGetSubString(mn, 37, -1); | ||
845 | |||
846 | if (l > 11) | ||
847 | { | ||
848 | if (-1 == page) | ||
849 | page = 1; | ||
850 | integer offset = page * 9; | ||
851 | llDialog(id, version + " - " + mn + "\n\n" + title, | ||
852 | ["◄ Previous", "▲ Exit", "Next ►"] | ||
853 | + llList2List(entries, offset-3, offset-1) | ||
854 | + llList2List(entries, offset-6, offset-4) | ||
855 | + llList2List(entries, offset-9, offset-7), | ||
856 | chan); | ||
857 | } | ||
858 | else | ||
859 | { | ||
860 | llDialog(id, version + " - " + mn + "\n\n" + title, | ||
861 | llList2List(entries, -2, -1) + ["▲ Exit"] | ||
862 | + llList2List(entries, -5, -3) | ||
863 | + llList2List(entries, -8, -6) | ||
864 | + llList2List(entries, -11, -9), | ||
865 | chan); | ||
866 | } | ||
867 | saveMuser(id, menu, llDumpList2String(entries, "|"), page, "-1"); | ||
868 | } | ||
869 | } | ||
870 | else if ("" != menu) | ||
871 | { | ||
872 | d("'" + menu + "' menu not found!"); | ||
873 | if (DEBUG) | ||
874 | dumpMenus(Menus); | ||
875 | } | ||
876 | else | ||
877 | { | ||
878 | llListenRemove(llList2Integer(Musers, f + uLSTN)); | ||
879 | Musers = llListReplaceList(Musers, [], f, f + uSTRIDE - 1); | ||
880 | } | ||
881 | } | ||
882 | } | ||
883 | |||
884 | // return TRUE if caller should showMenu() | ||
885 | integer handleMenu(integer f, key id, string button) | ||
886 | { | ||
887 | string cmd = button; | ||
888 | string data; | ||
889 | string menu = llList2String(Musers, f + uCURRENT); | ||
890 | string dM; | ||
891 | string fr; | ||
892 | list entries = llParseStringKeepNulls(llList2String(Musers, f + uENTRIES), ["|"], []); | ||
893 | list lst; | ||
894 | integer page = llList2Integer(Musers, f + uPAGE); | ||
895 | integer m = findMenu(menu); // This was already checked before it was stuffed into Musers. | ||
896 | integer e = 0; | ||
897 | |||
898 | //d("handleMenu(" + llKey2Name(id) + "," + button + ") " + menu + " -> " + cmd); | ||
899 | if (llList2Integer(Menus, m + mDYN)) | ||
900 | dM = menu; | ||
901 | // Find the corresponding command for this button. | ||
902 | lst = llParseStringKeepNulls(llList2String(Menus, m + mCMDS), ["|"], []); | ||
903 | if (("◄ Previous" != button) && ("▲ Exit" != button) && ("Next ►" != button)) | ||
904 | { | ||
905 | if (llGetListLength(lst) != 1) | ||
906 | e = llListFindList(entries, [button]); | ||
907 | if (-1 == e) | ||
908 | D("handleMenu() button |" + button + "| not found, OOPS! Was looking in -\n" | ||
909 | + llList2String(Musers, f + uENTRIES)); | ||
910 | else | ||
911 | cmd = llList2String(lst, e); | ||
912 | } | ||
913 | |||
914 | f = listFindString(Aliases, llToLower(cmd), 2); | ||
915 | if (-1 != f) | ||
916 | cmd = llList2String(Aliases, f + 1); | ||
917 | f = llSubStringIndex(cmd, "="); | ||
918 | if (-1 != f) | ||
919 | { | ||
920 | data = llGetSubString(cmd, f + 1, -1); | ||
921 | cmd = llGetSubString(cmd, 0, f - 1); | ||
922 | } | ||
923 | else | ||
924 | { | ||
925 | f = llSubStringIndex(cmd, " "); | ||
926 | if (-1 != f) | ||
927 | { | ||
928 | data = llGetSubString(cmd, f + 1, -1); | ||
929 | cmd = llGetSubString(cmd, 0, f - 1); | ||
930 | } | ||
931 | } | ||
932 | |||
933 | f = llSubStringIndex(cmd, "."); | ||
934 | if (-1 != f) | ||
935 | { | ||
936 | fr = llGetSubString(cmd, 0, f); | ||
937 | cmd = llGetSubString(cmd, f + 1, -1); | ||
938 | } | ||
939 | else | ||
940 | { | ||
941 | f = llSubStringIndex(menu, "."); | ||
942 | if (-1 != f) | ||
943 | { | ||
944 | fr = llGetSubString(menu, 0, f); | ||
945 | f = llSubStringIndex(fr, "@"); | ||
946 | if (-1 != f) | ||
947 | fr = llGetSubString(fr, f + 1, -1); | ||
948 | } | ||
949 | } | ||
950 | cmd = llToUpper(llStringTrim(cmd, STRING_TRIM)); | ||
951 | data = llStringTrim(data, STRING_TRIM); | ||
952 | |||
953 | if (!access(id, menu + " menu", fr, llList2Integer(Menus, m + mAUTH), TRUE)) | ||
954 | return FALSE; | ||
955 | |||
956 | string first = llGetSubString(button, 0, 0); | ||
957 | string last = llGetSubString(button, -1, -1); | ||
958 | |||
959 | d("handleMenu(" + llKey2Name(id) + "," + button + ") " + menu + " -> " + fr + cmd + " -> " + data); | ||
960 | |||
961 | // Check if it's a special menu item type. | ||
962 | // If this is a TOMENU entry and there was no command | ||
963 | if (("…" == last) && (" " == llList2String(lst, e))) | ||
964 | { | ||
965 | data = llGetSubString(button, 0, -2); | ||
966 | f = findMenu(fr + data); | ||
967 | if (-1 != f) | ||
968 | saveMuser(id, fr + data, "", -1, menu); | ||
969 | } | ||
970 | else if ("▲" == first) | ||
971 | { | ||
972 | f = doThing(id, menu + "->" + button, fr, cmd, data, fMENU); | ||
973 | lastMenu(id, dM); | ||
974 | return f; | ||
975 | } | ||
976 | else if ("◄" == first) | ||
977 | { | ||
978 | --page; | ||
979 | if (0 > page) | ||
980 | page = llGetListLength(entries) / 9; | ||
981 | saveMuser(id, menu, "", page, ""); | ||
982 | } | ||
983 | else if ("►" == last) | ||
984 | { | ||
985 | ++page; | ||
986 | if (llGetListLength(entries) < ((page - 1) * 9)) | ||
987 | page = 1; | ||
988 | saveMuser(id, menu, "", page, ""); | ||
989 | } | ||
990 | else if (("☐" == first) || ("▣" == first) || ("○" == first) || ("◉" == first)) | ||
991 | { | ||
992 | toggleMenu(id, fr, entries, e, m); | ||
993 | return FALSE; | ||
994 | } | ||
995 | else if ("MENU" == cmd) | ||
996 | { | ||
997 | if ("" == getFor(data)) | ||
998 | data = fr + data; | ||
999 | saveMuser(id, data, "", -1, menu); | ||
1000 | } | ||
1001 | else | ||
1002 | { | ||
1003 | integer i = TRUE; | ||
1004 | f = listFindString(Commands, fr + cmd, cSTRIDE); | ||
1005 | if (-1 != f) | ||
1006 | i = doThing(id, menu + "->" + button, fr, cmd, data, fMENU); | ||
1007 | if ("" != dM) | ||
1008 | lastMenu(id, dM); | ||
1009 | return i; | ||
1010 | } | ||
1011 | |||
1012 | return TRUE; | ||
1013 | } | ||
1014 | |||
1015 | integer doThing(key id, string command, string fr, string cmd, string data, integer source) | ||
1016 | { | ||
1017 | if ("*.." == fr) return TRUE; | ||
1018 | // Coz the card reader is about to send the lot anyway. | ||
1019 | if (fCARD == source) return TRUE; | ||
1020 | key them = getSetting(fr + "SCRIPTKEY"); | ||
1021 | //d("doThing(" + id + "," + command + " -> " + fr + " . " + cmd + " |" + data + "|"); | ||
1022 | |||
1023 | if (("" == them) && ("*." != fr)) | ||
1024 | D("Client script " + llGetSubString(fr, 0, -1) + " not found!\ndoThing(" | ||
1025 | + id + " does " + command + " - " + fr + " " + cmd + " | " + data + ")"); | ||
1026 | else | ||
1027 | { | ||
1028 | integer f = listFindString(Commands, fr + cmd, cSTRIDE); | ||
1029 | if (-1 != f) | ||
1030 | { | ||
1031 | if (0 == access(id, cmd + " command", fr, llList2Integer(Commands, f + cAUTH), TRUE)) | ||
1032 | return TRUE; | ||
1033 | } | ||
1034 | |||
1035 | if ("*." == fr) | ||
1036 | them = NULL_KEY; | ||
1037 | if ("TIMER" == cmd) | ||
1038 | { | ||
1039 | list dt = llParseString2List(data, [" "], []); | ||
1040 | float time = llList2Float(dt, 0); | ||
1041 | dt = llListReplaceList(dt, [], 0, 0); | ||
1042 | addEvent(time, fr + llDumpList2String(dt, " "), id); | ||
1043 | } | ||
1044 | else | ||
1045 | { | ||
1046 | sendScript(them, lCMD, fr, [source, id, command, cmd, data]); | ||
1047 | return FALSE; | ||
1048 | } | ||
1049 | } | ||
1050 | return TRUE; | ||
1051 | } | ||
1052 | |||
1053 | // TODO - Could use this from the various "save*()" functions? | ||
1054 | list saveThing(list in, integer stride, list add, integer l, string fr) | ||
1055 | { | ||
1056 | if (l == stride) | ||
1057 | { | ||
1058 | string first = llList2String(add, 0); | ||
1059 | |||
1060 | if ("" != fr) | ||
1061 | { | ||
1062 | first = fr + first; | ||
1063 | add = llListReplaceList(add, [first], 0, 0); | ||
1064 | } | ||
1065 | |||
1066 | integer f = listFindString(in, first, stride); | ||
1067 | |||
1068 | if (-1 == f) | ||
1069 | in += add; | ||
1070 | else | ||
1071 | in = llListReplaceList(in, add, f, f + stride - 1); | ||
1072 | } | ||
1073 | else | ||
1074 | D("Wrong number of arguments, should be " + stride + " but is " + l + "!\n" | ||
1075 | + llDumpList2String(add, " ~ ")); | ||
1076 | |||
1077 | return in; | ||
1078 | } | ||
1079 | |||
1080 | readTheme(string card) | ||
1081 | { | ||
1082 | float now = llGetTimeOfDay(); | ||
1083 | string cd = card; | ||
1084 | |||
1085 | card = "~" + cd + ".alias.data"; | ||
1086 | if (NULL_KEY != llGetInventoryKey(card)) | ||
1087 | Aliases += llList2List(llParseStringKeepNulls(osGetNotecard(card), ["\n"], []), 0, -2); | ||
1088 | card = "~" + cd + ".command.data"; | ||
1089 | if (NULL_KEY != llGetInventoryKey(card)) | ||
1090 | Commands += llList2List(llParseStringKeepNulls(osGetNotecard(card), ["\n"], []), 0, -2); | ||
1091 | card = "~" + cd + ".setting.data"; | ||
1092 | if (NULL_KEY != llGetInventoryKey(card)) | ||
1093 | Settings += llList2List(llParseStringKeepNulls(osGetNotecard(card), ["\n"], []), 0, -2); | ||
1094 | // s("Read " + cd + " data in " + (string) (llGetTimeOfDay() - now) + " seconds."); | ||
1095 | |||
1096 | now = llGetTimeOfDay(); | ||
1097 | card = "." + cd + ".theme"; | ||
1098 | if (NULL_KEY != llGetInventoryKey(card)) | ||
1099 | { | ||
1100 | list data = llParseString2List(osGetNotecard(card), ["\n", ";"], []); | ||
1101 | integer l = llGetListLength(data); | ||
1102 | integer i; | ||
1103 | string fr; | ||
1104 | string menu; | ||
1105 | |||
1106 | for (i = 0; i < l; ++i) | ||
1107 | { | ||
1108 | string line = llStringTrim(llList2String( | ||
1109 | llParseStringKeepNulls(llList2String(data, i), ["//", "#"], []), 0), STRING_TRIM); | ||
1110 | if ("" != line) | ||
1111 | { | ||
1112 | list args = llParseStringKeepNulls(line, ["|", ","], []); | ||
1113 | list first; | ||
1114 | integer m = llGetListLength(args); | ||
1115 | integer j; | ||
1116 | |||
1117 | for (j = 0; j < m; ++j) | ||
1118 | first += [llStringTrim(llList2String(args, j), STRING_TRIM)]; | ||
1119 | args = first; | ||
1120 | first = llParseStringKeepNulls(llList2String(args, 0), [" ", "="], []); | ||
1121 | |||
1122 | string cmd = llToUpper(llStringTrim(llList2String(first, 0), STRING_TRIM)); | ||
1123 | |||
1124 | first = llListReplaceList(first, [], 0, 0); | ||
1125 | args = [llDumpList2String(first, " ")] + llListReplaceList(args, [], 0, 0); | ||
1126 | m = llGetListLength(args); | ||
1127 | |||
1128 | if ("ALIAS" == cmd) | ||
1129 | { | ||
1130 | args = llListReplaceList(args, [llToLower(llList2String(args, 0))], 0, 0); | ||
1131 | Aliases = saveThing(Aliases, aSTRIDE, args, m, fr); | ||
1132 | } | ||
1133 | else if ("COMMAND" == cmd) | ||
1134 | Commands = saveThing(Commands, cSTRIDE, | ||
1135 | llListReplaceList(args, [decodeAccess(llList2String(args, 2))], cAUTH, cAUTH), | ||
1136 | m, fr); | ||
1137 | else if ("SETTING" == cmd) | ||
1138 | { | ||
1139 | // TODO - should do isBool() here if needed. | ||
1140 | Settings = saveThing(Settings, sSTRIDE, | ||
1141 | llListReplaceList(args, [decodeAccess(llList2String(args, 3))], sAUTH, sAUTH), | ||
1142 | m, fr); | ||
1143 | } | ||
1144 | else if ("FOR" == cmd) | ||
1145 | { | ||
1146 | fr = llList2String(args, 0) + "."; | ||
1147 | menu = ""; | ||
1148 | } | ||
1149 | else if ("MAIN" == cmd) | ||
1150 | MainMenu = fr + llList2String(args, 0); | ||
1151 | else if ("MENU" == cmd) | ||
1152 | { | ||
1153 | menu = llList2String(args, 0); | ||
1154 | if ("" != fr) menu = fr + menu; | ||
1155 | saveMenu(menu, llList2String(args, 1), decodeAccess(llList2String(args, 2)), "", "", FALSE); | ||
1156 | } | ||
1157 | else if (("BUTTON" == cmd) || ("TOGGLE" == cmd) || ("TOMENU" == cmd)) | ||
1158 | addMenuItem(fr, menu, cmd, args); | ||
1159 | else | ||
1160 | addMenuItem(fr, menu, cmd, [llList2String(args, 0), cmd + " " + llDumpList2String(args, ",")]); | ||
1161 | } | ||
1162 | } | ||
1163 | d("Read " + card + " in " + (string) (llGetTimeOfDay() - now) + " seconds."); | ||
1164 | } | ||
1165 | if ("" == MainMenu) | ||
1166 | MainMenu = llList2String(Menus, 0); | ||
1167 | } | ||
1168 | |||
1169 | list wipe(list o, string fr, integer stride) | ||
1170 | { | ||
1171 | return wipe(o, fr, stride, 0); | ||
1172 | } | ||
1173 | |||
1174 | list wipe(list o, string fr, integer stride, integer f) | ||
1175 | { | ||
1176 | integer l = llGetListLength(o); | ||
1177 | integer i; | ||
1178 | list n; | ||
1179 | |||
1180 | for (i = 0; i < l; i += stride) | ||
1181 | { | ||
1182 | if ((fr + ".") != getFor(llList2String(o, i + f))) | ||
1183 | n += llList2List(o, i, i + stride - 1); | ||
1184 | } | ||
1185 | return n; | ||
1186 | } | ||
1187 | |||
1188 | key URLrequestID; | ||
1189 | key URLCheckrequestID; | ||
1190 | integer Attached; | ||
1191 | float VelTime; | ||
1192 | vector VelPos; | ||
1193 | init() | ||
1194 | { | ||
1195 | Attached = (0 != llGetAttached()); | ||
1196 | if (Attached) | ||
1197 | { | ||
1198 | integer i = llGetInventoryNumber(INVENTORY_SCRIPT); | ||
1199 | while (i-- > 0) | ||
1200 | { | ||
1201 | string s = llGetInventoryName(INVENTORY_SCRIPT, i); | ||
1202 | if (osGetInventoryDesc(s) == "1chatter client") | ||
1203 | { | ||
1204 | d("RESETTING " + s); | ||
1205 | llResetOtherScript(s); | ||
1206 | } | ||
1207 | } | ||
1208 | } | ||
1209 | llListen(DEBUG_CHANNEL, "", NULL_KEY, ""); | ||
1210 | VelTime = llGetTimeOfDay(); | ||
1211 | VelPos = llGetPos(); | ||
1212 | addEvent(300, "Musers", ScriptKey); | ||
1213 | URLrequestID = llRequestURL(); | ||
1214 | } | ||
1215 | |||
1216 | default | ||
1217 | { | ||
1218 | state_entry() | ||
1219 | { | ||
1220 | Start = llGetTimeOfDay(); | ||
1221 | Owner = llGetOwner(); | ||
1222 | d("\n\n1chatter resetting client scripts @ " + (string) Start + "\n"); | ||
1223 | ScriptName = llGetScriptName(); ScriptKey = llGetInventoryKey(ScriptName); | ||
1224 | LibraryKey = ScriptKey; | ||
1225 | init(); | ||
1226 | } | ||
1227 | |||
1228 | on_rez(integer param) | ||
1229 | { | ||
1230 | // TODO - should clear any listeners first. | ||
1231 | Musers = []; | ||
1232 | } | ||
1233 | |||
1234 | changed(integer change) | ||
1235 | { | ||
1236 | // if (change & CHANGED_INVENTORY) | ||
1237 | // llResetScript(); | ||
1238 | if (change & CHANGED_OWNER) | ||
1239 | llResetScript(); | ||
1240 | if (change & CHANGED_REGION) | ||
1241 | { | ||
1242 | llReleaseURL(URL); | ||
1243 | URL = ""; | ||
1244 | URLrequestID = llRequestURL(); | ||
1245 | } | ||
1246 | } | ||
1247 | |||
1248 | dataserver(key id, string data) | ||
1249 | { | ||
1250 | list input = llParseStringKeepNulls(data, [lSEP], []); | ||
1251 | string fr = llList2String(input, 0); | ||
1252 | string cmd = llList2String(input, 1); | ||
1253 | |||
1254 | if ("PIN" == fr) | ||
1255 | { | ||
1256 | fr = "*"; | ||
1257 | fr="1AOor2"; | ||
1258 | cmd = "PIN"; | ||
1259 | input = [fr, cmd, llList2String(input, 1)]; | ||
1260 | } | ||
1261 | else | ||
1262 | id = llGetOwnerKey(id); | ||
1263 | doThing(id, llDumpList2String(input, "|"), fr + ".", cmd, llDumpList2String(llList2List(input, 2, -1), ","), fOSM); | ||
1264 | } | ||
1265 | |||
1266 | http_request(key id, string method, string body) | ||
1267 | { | ||
1268 | integer responseStatus = 400; | ||
1269 | string responseBody = "Unsupported method"; | ||
1270 | //d("HTTP " + method + " -> " + body); | ||
1271 | if (method == URL_REQUEST_DENIED) | ||
1272 | d("Error trying to get a URL - " + body); | ||
1273 | else if (method == URL_REQUEST_GRANTED) | ||
1274 | { | ||
1275 | URLrequestID = NULL_KEY; | ||
1276 | URL = body; | ||
1277 | llSleep(0.01); // The other scripts wont have recovered from the TP yet. | ||
1278 | sendScript(NULL_KEY, lCMD, "*.", [fHTTP, ScriptKey, method, "URL", URL]); | ||
1279 | // Check for dropped URL. | ||
1280 | // llSetTimerEvent(30.0); | ||
1281 | } | ||
1282 | else if (method == "POST") | ||
1283 | { | ||
1284 | list bdy = llParseStringKeepNulls(llUnescapeURL(body), ["|"], []); | ||
1285 | sendScript(NULL_KEY, lCMD, "*.", [fHTTP, ScriptKey, method, llList2String(bdy, 0), body]); | ||
1286 | // responseBody = llEscapeURL("|REGION" + llGetRegionName() + "|" + (string)llGetPos()); | ||
1287 | responseStatus = 200; | ||
1288 | responseBody = ""; | ||
1289 | llHTTPResponse(id, responseStatus, responseBody); | ||
1290 | } | ||
1291 | } | ||
1292 | |||
1293 | http_response(key id, integer status, list metaData, string body) | ||
1294 | { | ||
1295 | if (id == URLCheckrequestID) | ||
1296 | { | ||
1297 | URLCheckrequestID = NULL_KEY; | ||
1298 | if (status != 200) | ||
1299 | d("HTTP error code " + status + "\n" + body); | ||
1300 | // else | ||
1301 | // llOwnerSay("self check worked - " + body); | ||
1302 | } | ||
1303 | else if (id == NULL_KEY) | ||
1304 | d("Too many HTTP requests too fast!"); | ||
1305 | } | ||
1306 | |||
1307 | // Handle commands from other scripts. | ||
1308 | link_message(integer sender_num, integer num, string message, key id) | ||
1309 | { | ||
1310 | if ((id != ScriptKey) && (id != NULL_KEY)) return; | ||
1311 | list input = llParseStringKeepNulls(message, [lSEP], []); | ||
1312 | key them = llList2Key(input, 0); | ||
1313 | string fr = llList2Key(input, 1); | ||
1314 | |||
1315 | //d("linky " + num + " " + message + " " + fr); | ||
1316 | if (lRESET == num) | ||
1317 | { | ||
1318 | if ("" != fr) | ||
1319 | { | ||
1320 | Aliases = wipe(Aliases, fr, aSTRIDE); | ||
1321 | Commands = wipe(Commands, fr, cSTRIDE); | ||
1322 | Events = wipe(Events, fr, eSTRIDE, eSCRIPT); | ||
1323 | Menus = wipe(Menus, fr, mSTRIDE); | ||
1324 | Musers = wipe(Musers, fr, uSTRIDE, uSTACK); | ||
1325 | Settings = wipe(Settings, fr, sSTRIDE); | ||
1326 | readTheme(fr); | ||
1327 | Settings = saveThing(Settings, sSTRIDE, ["PREFIX", "S", fr, aALL], 4, fr + "."); | ||
1328 | Settings = saveThing(Settings, sSTRIDE, ["SCRIPTKEY", "K", them, aALL], 4, fr + "."); | ||
1329 | Settings = saveThing(Settings, sSTRIDE, ["VERSION", "S", "", aALL], 4, fr + "."); | ||
1330 | sendScript(them, lALIAS_DONE, Aliases); | ||
1331 | sendScript(them, lRESET_DONE, Settings); | ||
1332 | sendScript(them, lCMD, "*.", [fHTTP, ScriptKey, URL_REQUEST_GRANTED, "URL", URL]); | ||
1333 | } | ||
1334 | // resetPrimShit(); | ||
1335 | } | ||
1336 | else if (lSETTINGS == num) | ||
1337 | { | ||
1338 | if ("" != fr) | ||
1339 | { | ||
1340 | readSettings(fr); | ||
1341 | sendScript(them, lSETTINGS_DONE, Settings); | ||
1342 | } | ||
1343 | } | ||
1344 | else if (lSETTING == num) | ||
1345 | { | ||
1346 | if (id == ScriptKey) | ||
1347 | { | ||
1348 | integer source = llList2Integer(input, 2); | ||
1349 | key a = llList2Key(input, 3); | ||
1350 | |||
1351 | if ("" != fr) | ||
1352 | { | ||
1353 | integer l = llGetListLength(input); | ||
1354 | integer i; | ||
1355 | |||
1356 | for (i = 4; i < l; i += 2) | ||
1357 | { | ||
1358 | setSetting(a, fr + "." + llList2String(input, i), | ||
1359 | llList2String(input, i + 1), source); | ||
1360 | } | ||
1361 | // TODO - track the listen handle, delete old one here. | ||
1362 | integer channel = (integer) getSetting("1ring.CHANNEL"); | ||
1363 | if (channel) | ||
1364 | llListen(channel, "", "", ""); | ||
1365 | //dumpSettings(Settings, "SETTING"); | ||
1366 | } | ||
1367 | } | ||
1368 | } | ||
1369 | else if (lCMD == num) | ||
1370 | { | ||
1371 | doThing(llList2Key(input, 3), llList2String(input, 4), fr + ".", | ||
1372 | llList2String(input, 5), llList2String(input, 6), llList2Integer(input, 2)); | ||
1373 | } | ||
1374 | else if (lCMD_DONE == num) | ||
1375 | { | ||
1376 | //d("lCMD_DONE " + llKey2Name(llList2Key(input, 3)) + " -> " + llList2String(input, 2) + "| returned " + llList2String(input, 4)); | ||
1377 | if (0 != llList2Integer(input, 4)) | ||
1378 | showMenu(llList2Key(input, 3)); | ||
1379 | } | ||
1380 | else if (lDYNAMIC == num) | ||
1381 | { | ||
1382 | string menu = llList2String(input, 3); | ||
1383 | if ("" != menu) | ||
1384 | menu = fr + "." + menu; | ||
1385 | dynamicMenu(llList2Key(input, 2), menu, fr + "." + llList2String(input, 4), | ||
1386 | llList2String(input, 5), llList2String(input, 6), llList2String(input, 7)); | ||
1387 | } | ||
1388 | else if (lMENU == num) | ||
1389 | { | ||
1390 | //d("lMENU " + llKey2Name(llList2Key(input, 2)) + " -> " + fr + "." + llList2String(input, 3) + " " + llList2String(input, 4)); | ||
1391 | key a = llList2String(input, 2); | ||
1392 | string mn = llList2String(input, 3); | ||
1393 | string t = llList2String(input, 4); | ||
1394 | string menu; | ||
1395 | integer f = listFindString(Musers, a, uSTRIDE); | ||
1396 | integer m = findMenu(fr + "." + mn); | ||
1397 | |||
1398 | if (-1 != f) | ||
1399 | menu = llList2String(Musers, f + uCURRENT); | ||
1400 | if (-1 != m) | ||
1401 | { | ||
1402 | if ("" != t) | ||
1403 | Menus = llListReplaceList(Menus, t, m + mTITLE, m + mTITLE); | ||
1404 | if ((fr + "." + mn) == menu) | ||
1405 | menu = "-1"; | ||
1406 | saveMuser(a, fr + "." + mn, "", -1, menu); | ||
1407 | showMenu(a); | ||
1408 | } | ||
1409 | } | ||
1410 | else if (lSCAN == num) | ||
1411 | { | ||
1412 | d("lSCAN " + llDumpList2String(input, " ~ ")); | ||
1413 | key a = llList2Key(input, 2); | ||
1414 | integer f = listFindString(Musers, a, uSTRIDE); | ||
1415 | |||
1416 | if (-1 != f) | ||
1417 | { | ||
1418 | Scanners += [llList2String(Musers, f + uCURRENT), a] + llList2List(input, 3, -1); | ||
1419 | if (nSTRIDE == llGetListLength(Scanners)) | ||
1420 | llSensor("", NULL_KEY, llList2Integer(input, 3), 100.0, PI); | ||
1421 | } | ||
1422 | } | ||
1423 | } | ||
1424 | |||
1425 | listen(integer channel, string name, key id, string message) | ||
1426 | { | ||
1427 | //d("listen " + channel + ", " + llKey2Name(id) + "-> " + message + " " + llGetListLength(Musers)); | ||
1428 | // See if it's a prefix on the prefix channel, strip off the prefix and feed the remains to doThing(). | ||
1429 | if (channel == (integer) getSetting("1ring.CHANNEL")) | ||
1430 | { | ||
1431 | d("listen 0ring.CHANNEL " + message); | ||
1432 | string p = getSetting("1ring.PREFIX"); | ||
1433 | if (llGetSubString(message, 0, llStringLength(p) - 1) == p) | ||
1434 | { | ||
1435 | string button = llGetSubString(message, llStringLength(p), -1); | ||
1436 | list lst = llParseStringKeepNulls(llStringTrim(button, STRING_TRIM), [" "], []); | ||
1437 | string cmd = llList2String(lst, 0); | ||
1438 | string data = llDumpList2String(llListReplaceList(lst, [], 1, 1), " "); | ||
1439 | d("listen 1ring.CHANNEL " + message + " -> " + button + " " + cmd + " " + data); | ||
1440 | doThing(id, message, "1ring.", cmd, data, fCHAT); | ||
1441 | return; | ||
1442 | } | ||
1443 | } | ||
1444 | |||
1445 | integer l = llGetListLength(Musers); | ||
1446 | integer i; | ||
1447 | |||
1448 | for (i = 0; i < l; i += uSTRIDE) | ||
1449 | { | ||
1450 | if (channel == llList2Integer(Musers, i + uCHAN)) | ||
1451 | { | ||
1452 | if (handleMenu(i, id, message)) showMenu(id); | ||
1453 | return; | ||
1454 | } | ||
1455 | } | ||
1456 | |||
1457 | // Catch OhSillyThreatLevel messages. | ||
1458 | if (channel == DEBUG_CHANNEL) | ||
1459 | { | ||
1460 | key root = llList2Key(llGetObjectDetails(id, [OBJECT_ROOT]), 0); | ||
1461 | if ((llGetLinkKey(LINK_ROOT) == root) && ("OSSL Runtime Error: os" == llGetSubString(message, 0, 21))) | ||
1462 | { | ||
1463 | string function = llGetSubString(message, 20, -1); | ||
1464 | integer f = llSubStringIndex(function, " "); | ||
1465 | if (-1 != f) | ||
1466 | { | ||
1467 | if (" permission denied. Script creator is not in the list of users allowed to execute this function and prim owner also has no permission." == llGetSubString(function, f, -1)) | ||
1468 | { | ||
1469 | llMessageLinked(LINK_SET, DEBUG_CHANNEL, llGetSubString(function, 0, f - 1), id); | ||
1470 | llRegionSay(DEBUG_CHANNEL, "OPENSIM SUCKS!"); | ||
1471 | return; | ||
1472 | } | ||
1473 | } | ||
1474 | } | ||
1475 | } | ||
1476 | } | ||
1477 | |||
1478 | no_sensor() | ||
1479 | { | ||
1480 | if (0 != llGetListLength(Scanners)) | ||
1481 | { | ||
1482 | key id = llList2Key(Scanners, nID); | ||
1483 | Scanners = llListReplaceList(Scanners, [], 0, nSTRIDE - 1); | ||
1484 | if (0 != llGetListLength(Scanners)) | ||
1485 | llSensor("", NULL_KEY, llList2Integer(Scanners, nTYPE), 100.0, PI); | ||
1486 | showMenu(id); | ||
1487 | } | ||
1488 | } | ||
1489 | |||
1490 | sensor(integer num) | ||
1491 | { | ||
1492 | if (0 != llGetListLength(Scanners)) | ||
1493 | { | ||
1494 | string cmd = llList2String(Scanners, nCMD); | ||
1495 | list items = []; | ||
1496 | list keys = []; | ||
1497 | key id = llList2Key(Scanners, nID); | ||
1498 | string menu = llList2String(Scanners, nMENU); | ||
1499 | integer i; | ||
1500 | for (i = 0; i < num; ++i) | ||
1501 | { | ||
1502 | items += [llGetSubString(llDetectedName(i), 0, 23)]; | ||
1503 | keys += [cmd + " " + llDetectedKey(i)]; | ||
1504 | } | ||
1505 | Scanners = llListReplaceList(Scanners, [], 0, nSTRIDE - 1); | ||
1506 | if (0 != llGetListLength(Scanners)) | ||
1507 | llSensor("", NULL_KEY, llList2Integer(Scanners, nTYPE), 100.0, PI); | ||
1508 | dynamicMenu(id, menu, menu, | ||
1509 | llList2String(Scanners, nTITLE), | ||
1510 | llDumpList2String(items, "|"), llDumpList2String(keys, "|")); | ||
1511 | } | ||
1512 | } | ||
1513 | |||
1514 | timer() | ||
1515 | { | ||
1516 | float now = llGetTimeOfDay(); | ||
1517 | integer i; | ||
1518 | |||
1519 | while (NextEvent <= now) | ||
1520 | { | ||
1521 | string script = llList2String(Events, eSCRIPT); | ||
1522 | key them = llList2Key(Events, eKEY); | ||
1523 | |||
1524 | Events = llListReplaceList(Events, [], 0, eSTRIDE - 1); | ||
1525 | if (0 != llGetListLength(Events)) | ||
1526 | NextEvent = llList2Float(Events, 0); | ||
1527 | else | ||
1528 | NextEvent = 307584000.0; | ||
1529 | now = llGetTimeOfDay(); | ||
1530 | |||
1531 | if ("Musers" == script) | ||
1532 | { | ||
1533 | integer f = llGetListLength(Musers); | ||
1534 | list t = []; | ||
1535 | |||
1536 | for (i = 0; i < f; i += uSTRIDE) | ||
1537 | { | ||
1538 | key a = llList2Key(Musers, i + uKEY); | ||
1539 | |||
1540 | if (ZERO_VECTOR != llGetAgentSize(a)) | ||
1541 | { | ||
1542 | if (now < (llList2Float(Musers, i + uTIME) + 300.0)) | ||
1543 | t += llList2List(Musers, i, i + uSTRIDE - 1); | ||
1544 | else | ||
1545 | { | ||
1546 | s(a, "You have taken too long to respond to the menu, it's disabled now."); | ||
1547 | a = NULL_KEY; | ||
1548 | } | ||
1549 | } | ||
1550 | else | ||
1551 | a = NULL_KEY; | ||
1552 | if (NULL_KEY == a) // Remove those no longer in the sim, or menu timeouts. | ||
1553 | llListenRemove(llList2Integer(Musers, i + uLSTN)); | ||
1554 | } | ||
1555 | Musers = t; | ||
1556 | addEvent(300, script, them); | ||
1557 | } | ||
1558 | else | ||
1559 | { | ||
1560 | list t = llParseStringKeepNulls(script, ["."], []); | ||
1561 | string fr = llList2Key(t, 0); | ||
1562 | list dt = llParseString2List(llList2String(t, 1), [" "], []); | ||
1563 | string cmd = llList2String(dt, 0); | ||
1564 | dt = llListReplaceList(dt, [], 0, 0); | ||
1565 | //d("timer " + script + "->" + fr + " ... " + cmd + "=" + llDumpList2String(dt, " ")); | ||
1566 | sendScript(them, lCMD, fr + ".", [fINT, them, "TimerEvent", cmd, llDumpList2String(dt, " ")]); | ||
1567 | } | ||
1568 | } | ||
1569 | llSetTimerEvent(NextEvent - now); | ||
1570 | now = llGetTimeOfDay(); | ||
1571 | float time = now - VelTime; | ||
1572 | vector vp = llGetPos(); | ||
1573 | float dist = llVecDist(VelPos, vp); | ||
1574 | float speed = dist / time; | ||
1575 | VelTime = now; | ||
1576 | VelPos = vp; | ||
1577 | // llSetText("Velocity " + (string) speed + " m/s\n@ " + (string) now, <1.0, 1.0, 1.0>, 1.0); | ||
1578 | llSetText("", <1.0, 1.0, 1.0>, 1.0); | ||
1579 | } | ||
1580 | |||
1581 | touch_start(integer num) | ||
1582 | { | ||
1583 | for (--num; 0 <= num; --num) | ||
1584 | { | ||
1585 | key id = llDetectedKey(num); | ||
1586 | string menu = MainMenu; | ||
1587 | list v = validateName(menu, "menu"); | ||
1588 | string fr = llList2String(v, 0); | ||
1589 | menu = fr + llList2String(v, 1); | ||
1590 | if (access(id, menu + " menu", fr, llList2Integer(Menus, 0 + mAUTH), TRUE)) | ||
1591 | { | ||
1592 | saveMuser(id, menu, "", -1, ""); | ||
1593 | showMenu(id); | ||
1594 | } | ||
1595 | } | ||
1596 | } | ||
1597 | } | ||
diff --git a/1ring.lsl b/1ring.lsl new file mode 100644 index 0000000..176e706 --- /dev/null +++ b/1ring.lsl | |||
@@ -0,0 +1,1424 @@ | |||
1 | |||
2 | // 1ring written by onefang rejected 2019. | ||
3 | // A simple, generic RLV based "collar". | ||
4 | |||
5 | string Version = "1ring v0.1 test version"; | ||
6 | |||
7 | // BEGIN boilerplate. | ||
8 | integer DEBUG = FALSE; | ||
9 | float Start; | ||
10 | string ScriptName; | ||
11 | key ScriptKey; | ||
12 | key LibraryKey; | ||
13 | key Owner; | ||
14 | string URL; | ||
15 | |||
16 | // Settings. | ||
17 | list Aliases; | ||
18 | list Settings; | ||
19 | integer sNAME = 0; | ||
20 | integer sTYPE = 1; | ||
21 | integer sVALUE = 2; | ||
22 | integer sAUTH = 3; | ||
23 | integer sSTRIDE = 4; | ||
24 | |||
25 | // Access "f"rom some source - | ||
26 | integer fINT = 0; // Internal, not sure we need this, means the same script. | ||
27 | integer fCARD = 1; // Read from a note card. | ||
28 | integer fCHAT = 2; // Chat channel. | ||
29 | integer fLINK = 3; // Link message. | ||
30 | integer fMENU = 4; // Menu, in reality this is just a chat channel. | ||
31 | integer fRLV = 5; // RLV, again from a chat channel. | ||
32 | integer fRELAY = 6; // RLV relay. | ||
33 | integer fOSM = 7; // osMessageObject() | ||
34 | integer fHTTP = 8; // From the web. | ||
35 | |||
36 | // utilities commands, "l"ibrary. | ||
37 | string lSEP = "$!#"; // Used to seperate lists when sending them as strings. | ||
38 | integer lRESET = -1; | ||
39 | integer lRESET_DONE = -2; | ||
40 | integer lALIAS = -3; | ||
41 | integer lALIAS_DONE = -4; | ||
42 | integer lSETTING = -5; | ||
43 | integer lSETTING_DONE = -6; | ||
44 | integer lSUBSTITUTE = -7; | ||
45 | integer lSUBSTITUTE_DONE = -8; | ||
46 | integer lNEXT_WORD = -9; | ||
47 | integer lNEXT_WORD_DONE = -10; | ||
48 | integer lCONTROL = -13; | ||
49 | integer lCONTROL_DONE = -14; | ||
50 | integer lCMD = -15; | ||
51 | integer lCMD_DONE = -16; | ||
52 | integer lSETTINGS = -17; | ||
53 | integer lSETTINGS_DONE = -18; | ||
54 | integer lSCAN = -19; | ||
55 | integer lDYNAMIC = -20; | ||
56 | integer lMENU = -21; | ||
57 | |||
58 | // OhSillyThreat detector | ||
59 | list OhSillyThreats = []; | ||
60 | list OhSillyTreats = // OpenSim threat level. | ||
61 | [ | ||
62 | "osKey2Name", // low | ||
63 | // "osGetAvatarList", // none | ||
64 | // "osGetNotecard", // very high (describes what they where when making this decision) | ||
65 | // "osMakeNotecard", // high (describes what they where when making this decision) | ||
66 | // "osGetRezzingObject", // none | ||
67 | "osMessageObject", // low | ||
68 | // "osAvatarPlayAnimation", // very high | ||
69 | // "osAvatarStopAnimation", // very high | ||
70 | // "osForceOtherSit", // very high | ||
71 | "osSetSpeed" // moderate | ||
72 | ]; | ||
73 | |||
74 | d(string m) {if (DEBUG) llInstantMessage(Owner, llGetScriptName() + ": " + m);} | ||
75 | D(string m) {llRegionSay(DEBUG_CHANNEL, llGetScriptName() + ": " + m);} | ||
76 | s(string m) {s(Owner, m);} | ||
77 | s(key id, string m) {if (id == Owner) llOwnerSay(m); else llInstantMessage(id, m);} | ||
78 | sendScript(integer cmd, list args) {sendScript(LibraryKey, cmd, ScriptName, args);} | ||
79 | sendScript(key them, integer cmd, list args) {sendScript(them, cmd, inKey2Name(them), args);} | ||
80 | sendScript(integer cmd, string name, list args) {sendScript(LibraryKey, cmd, name, args);} | ||
81 | sendScript(key them, integer cmd, string name, list args) | ||
82 | { | ||
83 | llMessageLinked(LINK_SET, cmd, llDumpList2String([ScriptKey, name] + args, lSEP), them); | ||
84 | } | ||
85 | sendPrim(key them, string cmd, list args) | ||
86 | { | ||
87 | osMessageObject(them, llDumpList2String([ScriptName, cmd] + args, lSEP)); | ||
88 | } | ||
89 | addEvent(float delay, string cmds) | ||
90 | { | ||
91 | sendScript(lCMD, [fINT, ScriptKey, "TimerEvent", "TIMER", ((string) delay) + " " + cmds]); | ||
92 | } | ||
93 | |||
94 | string inKey2Name(key k) | ||
95 | { | ||
96 | if (k == LibraryKey) return "1chatter"; | ||
97 | integer i = llGetInventoryNumber(INVENTORY_SCRIPT); | ||
98 | while (i-- > 0) | ||
99 | { | ||
100 | string n = llGetInventoryName(INVENTORY_SCRIPT, i); | ||
101 | if (llGetInventoryKey(n) == k) return n; | ||
102 | } | ||
103 | return k; | ||
104 | } | ||
105 | |||
106 | integer listFindString(list lst, string name, integer stride) | ||
107 | { | ||
108 | integer f = llListFindList(lst, [name]); | ||
109 | integer ix = f / stride; | ||
110 | ix = ix * stride; | ||
111 | if ((-1 != f) && (ix != f)) | ||
112 | { | ||
113 | integer l = llGetListLength(lst); | ||
114 | integer i; | ||
115 | f = -1; | ||
116 | for (i = 0; i < l; i += stride) | ||
117 | { | ||
118 | if (llList2String(lst, i) == name) {f = i; i = l;} | ||
119 | } | ||
120 | } | ||
121 | return f; | ||
122 | } | ||
123 | |||
124 | // Note these next two are different from the ones in 1chatter. | ||
125 | string alias(string n) | ||
126 | { | ||
127 | list v = validateName(n, "alias"); | ||
128 | n = llList2String(v, 1); | ||
129 | integer a = listFindString(Aliases, llList2String(v, 0) + llToLower(n), 2); | ||
130 | if (-1 != a) n = llList2String(Aliases, a + 1); | ||
131 | return n; | ||
132 | } | ||
133 | |||
134 | list validateName(string var, string type) | ||
135 | { | ||
136 | list v = llParseStringKeepNulls(var, ["."], []); | ||
137 | if (2 != llGetListLength(v)) | ||
138 | v = ScriptName + v; | ||
139 | var = llList2String(v, 1); | ||
140 | if ("setting" == type) | ||
141 | var = llToUpper(var); | ||
142 | return [llList2String(v, 0) + ".", var]; | ||
143 | } | ||
144 | |||
145 | string getSetting(string var) | ||
146 | { | ||
147 | list v = validateName(var, "setting"); | ||
148 | var = llList2String(v, 0) + llList2String(v, 1); | ||
149 | string result = ""; | ||
150 | integer f = listFindString(Settings, var, sSTRIDE); | ||
151 | if (-1 != f) | ||
152 | result = llList2String(Settings, f + sVALUE); | ||
153 | return result; | ||
154 | } | ||
155 | |||
156 | setSetting(key id, string var, string val, integer source) | ||
157 | { | ||
158 | list v = validateName(var, "setting"); | ||
159 | string fr = llList2String(v, 0); | ||
160 | var = fr + llList2String(v, 1); | ||
161 | integer f = listFindString(Settings, var, sSTRIDE); | ||
162 | |||
163 | if (-1 != f) | ||
164 | { | ||
165 | Settings = llListReplaceList(Settings, [val], f + sVALUE, f + sVALUE); | ||
166 | if (llGetSubString(fr, 0, -2) == ScriptName) | ||
167 | { | ||
168 | if (fINT != source) | ||
169 | { | ||
170 | doThing(id, "SET " + var + "=" + val, fr, llList2String(v, 1), val, fINT); | ||
171 | sendScript(lSETTING, [source, id, llList2String(v, 1), val]); | ||
172 | } | ||
173 | } | ||
174 | } | ||
175 | } | ||
176 | |||
177 | dumpSettings(list settings, string title) | ||
178 | { | ||
179 | integer l = llGetListLength(settings); | ||
180 | integer i; | ||
181 | |||
182 | d("v--------------- " + title); | ||
183 | for (i = 0; i < l; i += sSTRIDE) | ||
184 | { | ||
185 | d( | ||
186 | llList2String(settings, i + sNAME) + "~" + | ||
187 | llList2String(settings, i + sTYPE) + "~" + | ||
188 | llList2String(settings, i + sVALUE) + "~" + | ||
189 | llList2String(settings, i + sAUTH) | ||
190 | ); | ||
191 | } | ||
192 | d("^--------------- " + ScriptName); | ||
193 | } | ||
194 | |||
195 | doSettings(key id, list settings) | ||
196 | { | ||
197 | integer l = llGetListLength(settings); | ||
198 | integer i; | ||
199 | |||
200 | for (i = 0; i < l; i += sSTRIDE) | ||
201 | { | ||
202 | string var = llList2String(settings, i + sNAME); | ||
203 | list v = validateName(var, "setting"); | ||
204 | string fr = llList2String(v, 0); | ||
205 | var = llList2String(v, 1); | ||
206 | string val = llList2String(settings, i + sVALUE); | ||
207 | doThing(id, "SET " + var + "=" + val, fr, var, val, fINT); | ||
208 | } | ||
209 | } | ||
210 | |||
211 | dynamicMenu(key id, string menu, string name, string title, string entries, string command) | ||
212 | { | ||
213 | sendScript(lDYNAMIC, [id, menu, name, title, entries, command]); | ||
214 | } | ||
215 | |||
216 | linky(integer num, string message, key id) | ||
217 | { | ||
218 | if ((id != ScriptKey) && (id != NULL_KEY)) return; | ||
219 | list input = llParseStringKeepNulls(message, [lSEP], []); | ||
220 | key them = llList2Key(input, 0); | ||
221 | string fr = llList2Key(input, 1); | ||
222 | //d("linky " + num + " " + message); | ||
223 | if (lCMD == num) | ||
224 | { | ||
225 | //d("linky lCMD " + llDumpList2String(input, " ~ ")); | ||
226 | if ((fr == (ScriptName + ".")) || (fr == "*.")) | ||
227 | { | ||
228 | key a = llList2Key(input, 3); | ||
229 | string button = llList2String(input, 4); | ||
230 | integer r = doThing(a, button, fr, | ||
231 | llList2String(input, 5), llList2String(input, 6), llList2Integer(input, 2)); | ||
232 | sendScript(lCMD_DONE, [button, a, r]); | ||
233 | } | ||
234 | } | ||
235 | else if (lRESET_DONE == num) | ||
236 | { | ||
237 | d("linky RESET_DONE"); | ||
238 | LibraryKey = them; | ||
239 | Settings = llList2List(input, 2, -1); | ||
240 | |||
241 | // NOT boilerplate | ||
242 | sendRLV("versionnew", 10.0); | ||
243 | string f = llToLower(llKey2Name(Owner)); | ||
244 | string prefix = llGetSubString(f, 0, 0); | ||
245 | integer i = llSubStringIndex(f, " "); | ||
246 | if (-1 != i) | ||
247 | { | ||
248 | string l = llGetSubString(f, i + 1, -1); | ||
249 | if ("Resident" == l) | ||
250 | prefix += llGetSubString(f, 1, 1); | ||
251 | else | ||
252 | prefix += llGetSubString(f, i + 1, i + 1); | ||
253 | } | ||
254 | setSetting(ScriptKey, ScriptName + ".PREFIX", prefix, fCARD); | ||
255 | // NOT boilerplate | ||
256 | |||
257 | setSetting(ScriptKey, ScriptName + ".VERSION", Version, fCARD); | ||
258 | sendScript(lSETTINGS, []); | ||
259 | } | ||
260 | else if (lALIAS_DONE == num) | ||
261 | Aliases = llList2List(input, 2, -1); | ||
262 | else if (lSETTINGS_DONE == num) | ||
263 | { | ||
264 | Settings = llList2List(input, 2, -1); | ||
265 | doSettings(id, Settings); | ||
266 | laterInit(); | ||
267 | s("Finished starting up " + getSetting("VERSION") + " in " + (string) (llGetTimeOfDay() - Start)); | ||
268 | } | ||
269 | else if (DEBUG_CHANNEL == num) | ||
270 | { | ||
271 | key root = llList2Key(llGetObjectDetails(id, [OBJECT_ROOT]), 0); | ||
272 | integer f = llListFindList(OhSillyThreats, [message]); | ||
273 | |||
274 | if (-1 == f) | ||
275 | OhSillyThreats += [message]; | ||
276 | else | ||
277 | d("OhSillyThreats detected the function " + message + "() again!" ); | ||
278 | s("Oh Silly threat system prevented " + message + "()" | ||
279 | + "\n in " + id + " \t" + llKey2Name(id) | ||
280 | + "\n part of " + root + " \t" + llKey2Name(root)); | ||
281 | } | ||
282 | } | ||
283 | // END boilerplate, mostly. | ||
284 | |||
285 | showAccess(key id) | ||
286 | { | ||
287 | list bosses = llParseString2List(getSetting("boss"), [","], []); | ||
288 | list trusts = llParseString2List(getSetting("trustee"), [","], []); | ||
289 | list blocked = llParseString2List(getSetting("blocked"), [","], []); | ||
290 | integer l = llGetListLength(bosses); | ||
291 | integer i; | ||
292 | string report = "Access - group "; | ||
293 | |||
294 | for (i = 0; i < l; ++i) | ||
295 | s(id, osKey2Name(llList2String(bosses, i)) + " is a " + alias(ScriptName + ".boss") + "."); | ||
296 | l = llGetListLength(trusts); | ||
297 | for (i = 0; i < l; ++i) | ||
298 | s(id, osKey2Name(llList2String(trusts, i)) + " is a " + alias(ScriptName + ".trustee") + "."); | ||
299 | l = llGetListLength(blocked); | ||
300 | for (i = 0; i < l; ++i) | ||
301 | s(id, osKey2Name(llList2String(blocked, i)) + " is " + alias(ScriptName + ".blocked") + "."); | ||
302 | if ("0" == getSetting("group")) | ||
303 | report += "off"; | ||
304 | else | ||
305 | report += "on"; | ||
306 | report += ", public "; | ||
307 | if ("0" == getSetting("public")) | ||
308 | report += "off"; | ||
309 | else | ||
310 | report += "on"; | ||
311 | s(id, report + ". The command prefix is /" + getSetting("channel") + getSetting("prefix") + "."); | ||
312 | } | ||
313 | |||
314 | integer changeAccess(key id, string cmd, key person, integer source) | ||
315 | { | ||
316 | list tu = llParseString2List(cmd, ["_"], []); | ||
317 | string t = llList2String(tu, 0); | ||
318 | string u = llList2String(tu, 1); | ||
319 | string lW = "boss"; | ||
320 | string eW = "a boss"; | ||
321 | string mW = lW; | ||
322 | string c = "_" + u; | ||
323 | |||
324 | if ("BLOCKED" == u) | ||
325 | { | ||
326 | lW = "blocked"; | ||
327 | eW = lW; | ||
328 | mW = "blockee"; | ||
329 | } | ||
330 | else if ("TRUSTEE" == u) | ||
331 | { | ||
332 | lW = "trustee"; | ||
333 | eW = "trusted"; | ||
334 | mW = lW; | ||
335 | } | ||
336 | |||
337 | list lst = llParseString2List(getSetting(lW), [","], []); | ||
338 | list r; | ||
339 | integer l; | ||
340 | integer i; | ||
341 | if (("ADD" == t) || ("NEW" == t)) | ||
342 | { | ||
343 | list b = llGetAgentList(AGENT_LIST_REGION, []); | ||
344 | l = llGetListLength(b); | ||
345 | for (i = 0; i < l; ++i) | ||
346 | { | ||
347 | if (-1 == listFindString(lst, llList2String(b, i), 1)) | ||
348 | r += [llList2String(b, i)]; | ||
349 | } | ||
350 | lst = r; | ||
351 | r = []; | ||
352 | } | ||
353 | l = llGetListLength(lst); | ||
354 | for (i = 0; i < l; ++i) | ||
355 | { | ||
356 | key k = llList2String(lst, i); | ||
357 | if (!osIsNpc(k)) | ||
358 | { | ||
359 | r += osKey2Name(k); | ||
360 | if ("NEW" == t) | ||
361 | r += k; | ||
362 | } | ||
363 | } | ||
364 | |||
365 | if ("ADD" == t) | ||
366 | { | ||
367 | if (0 == llGetListLength(r)) | ||
368 | { | ||
369 | s(id, "No one here that isn't already " + eW + "."); | ||
370 | return TRUE; | ||
371 | } | ||
372 | else | ||
373 | dynamicMenu(id, "Access", cmd, "Select a new " + mW, llDumpList2String(r, "|"), "NEW" + c); | ||
374 | } | ||
375 | else if ("DEL" == t) | ||
376 | { | ||
377 | if (0 == llGetListLength(r)) | ||
378 | { | ||
379 | s(id, "There is no one on your " + eW + " list."); | ||
380 | return TRUE; | ||
381 | } | ||
382 | else | ||
383 | dynamicMenu(id, "Access", cmd, "Select a " + mW + " to be removed.", llDumpList2String(r, "|"), "OLD" + c); | ||
384 | } | ||
385 | else if ("NEW" == t) | ||
386 | { | ||
387 | integer f = llListFindList(r, [person]); | ||
388 | if (-1 != f) | ||
389 | { | ||
390 | person = llList2Key(r, f + 1); | ||
391 | lst = llParseString2List(getSetting(lW), [","], []) + [person]; | ||
392 | setSetting(id, lW, llDumpList2String(lst, ","), source); | ||
393 | if ("BLOCKED" == u) | ||
394 | s(person, "You are now blocked from using " + llKey2Name(Owner) | ||
395 | + " " + llKey2Name(llGetKey()) + "."); | ||
396 | else if ("BOSS" == u) | ||
397 | s(person, "You are now one of the parental units for " + llKey2Name(Owner) | ||
398 | + ". Remember kids are for life, not just for Christmas."); | ||
399 | else if ("TRUSTEE" == u) | ||
400 | s(person, "You are now trusted by " + llKey2Name(Owner) + "."); | ||
401 | s(id, "You added a " + mW + " - " + osKey2Name(person)); | ||
402 | } | ||
403 | else | ||
404 | s("Can't find " + eW + " " + person); | ||
405 | } | ||
406 | else if ("OLD" == t) | ||
407 | { | ||
408 | integer f = llListFindList(r, [person]); | ||
409 | if (-1 != f) | ||
410 | { | ||
411 | person = llList2Key(lst, f); | ||
412 | lst = llListReplaceList(lst, [], f, f); | ||
413 | setSetting(id, lW, llDumpList2String(lst, ","), source); | ||
414 | if ("BOSS" == u) | ||
415 | s(person, llKey2Name(Owner) + " divorces you, but keeps the cat."); | ||
416 | else | ||
417 | s(person, "You are no longer " + mW + " by " + llKey2Name(Owner) + "."); | ||
418 | s(id, "You removed a " + mW + " - " + osKey2Name(person)); | ||
419 | } | ||
420 | else | ||
421 | s("Can't find " + eW + " " + person); | ||
422 | } | ||
423 | showAccess(id); | ||
424 | return FALSE; | ||
425 | } | ||
426 | |||
427 | integer doThing(key id, string button, string fr, string cmd, string data, integer source) | ||
428 | { | ||
429 | if ("SET " == llGetSubString(button, 0, 3)) | ||
430 | { | ||
431 | integer set = listFindString(Settings, fr + cmd, sSTRIDE); | ||
432 | if (-1 != set) | ||
433 | setSetting(id, fr + cmd, data, fINT); | ||
434 | } | ||
435 | if ((fr != (ScriptName + ".") && ("*." != fr))) return TRUE; | ||
436 | |||
437 | integer f; | ||
438 | string menu; | ||
439 | f = llSubStringIndex(button, "->"); | ||
440 | if (-1 != f) | ||
441 | { | ||
442 | menu = llGetSubString(button, 0, f - 1); | ||
443 | button = llGetSubString(button, f + 2, -1); | ||
444 | } | ||
445 | |||
446 | //d("doThing " + menu + " ... " + button + " -> " + fr + " . " + cmd + " = " + data); | ||
447 | if ("MENU" == llToUpper(cmd)) | ||
448 | { | ||
449 | sendScript(lMENU, [id, "main", ""]); | ||
450 | return FALSE; | ||
451 | } | ||
452 | else if (("PANIC" == cmd) || ("RUNAWAY" == cmd)) | ||
453 | { | ||
454 | llSay(0, "I'm a teapot, I'm a teapot!"); | ||
455 | setSetting(id, "public", "0", source); | ||
456 | setSetting(id, "hide", "0", source); | ||
457 | setSetting(id, "lock", "0", source); | ||
458 | setSetting(id, "relay", "0", source); | ||
459 | justRLV("clear"); | ||
460 | if ("RUNAWAY" == cmd) | ||
461 | { | ||
462 | llShout(0, "AAAHHHHH!"); | ||
463 | setSetting(id, "boss", "", source); | ||
464 | setSetting(id, "trustee", "", source); | ||
465 | } | ||
466 | llResetScript(); | ||
467 | } | ||
468 | else if ("HIDE" == cmd) | ||
469 | { | ||
470 | if ("0" == data) | ||
471 | llSetLinkAlpha(LINK_SET, 1.0, ALL_SIDES); | ||
472 | else | ||
473 | llSetLinkAlpha(LINK_SET, 0.0, ALL_SIDES); | ||
474 | } | ||
475 | else if ("LOCK" == cmd) | ||
476 | { | ||
477 | if ("0" == data) | ||
478 | justRLV("detach=y"); | ||
479 | else | ||
480 | justRLV("detach=n"); | ||
481 | } | ||
482 | else if ("CHOOSE_ALL" == cmd) | ||
483 | { | ||
484 | list s; | ||
485 | if ("Follow…" == button) | ||
486 | s = [AGENT, "Select someone to follow", "FOLLOW"]; | ||
487 | else if ("Go to…" == button) | ||
488 | s = [ACTIVE | AGENT | PASSIVE, "Select something or someone to go to", "GOTO"]; | ||
489 | else if ("Leash…" == button) | ||
490 | s = [ACTIVE | AGENT | PASSIVE, "Select something or someone to leash to", "LEASH"]; | ||
491 | else if ("Sit…" == button) // This will only be for objects, can't sit on an avatar. | ||
492 | s = [ACTIVE | PASSIVE, "Select something to sit on", "SIT"]; | ||
493 | else | ||
494 | { | ||
495 | D("Unknown CHOOSE_ALL option - " + button); | ||
496 | return FALSE; | ||
497 | } | ||
498 | sendScript(lSCAN, [id] + s); | ||
499 | return FALSE; | ||
500 | } | ||
501 | else if ("FOLLOW" == cmd) | ||
502 | { | ||
503 | s("Following " + llKey2Name(data)); | ||
504 | mode = MODE_FOLLOW; | ||
505 | goto(data); | ||
506 | unleash(); | ||
507 | } | ||
508 | else if ("GOTO" == cmd) | ||
509 | { | ||
510 | s("Going to " + llKey2Name(data)); | ||
511 | mode = MODE_SINGLE; | ||
512 | goto(data); | ||
513 | unleash(); | ||
514 | } | ||
515 | else if ("GOTO_TICK" == cmd) | ||
516 | { | ||
517 | if ((MODE_FOLLOW == mode) || (MODE_LEASH == mode) || (MODE_SINGLE == mode) || (MODE_SCAN == mode)) | ||
518 | goto(Stalkee); | ||
519 | } | ||
520 | else if ("FREE" == cmd) | ||
521 | { | ||
522 | s("YAY! Freeeeeee at last!!!!!"); | ||
523 | stopGoto(TRUE); | ||
524 | llReleaseControls(); | ||
525 | } | ||
526 | else if ("STAY" == cmd) | ||
527 | llRequestPermissions(llGetOwner(), PERMISSION_TAKE_CONTROLS); | ||
528 | else if ("STOP" == cmd) | ||
529 | stopGoto(TRUE); | ||
530 | else if ("GRAB" == cmd) | ||
531 | return doThing(id, llKey2Name(id), fr, "LEASH", id, source); | ||
532 | else if ("LEASH" == cmd) | ||
533 | { | ||
534 | if (("SET " != llGetSubString(button, 0, 3)) && ("" != data)) | ||
535 | { | ||
536 | s("Leashing you to " + llKey2Name(data)); | ||
537 | mode = MODE_LEASH; | ||
538 | goto(data); | ||
539 | unleash(); | ||
540 | leashTo(Stalkee); | ||
541 | } | ||
542 | } | ||
543 | else if ("RELEASE" == cmd) | ||
544 | { | ||
545 | someBoss = id; | ||
546 | stopGoto(TRUE); | ||
547 | } | ||
548 | else if ("SIT" == cmd) | ||
549 | { | ||
550 | s("Sitting on " + llKey2Name(data)); | ||
551 | justRLV("sit:" + data + "=force"); | ||
552 | } | ||
553 | else if ("STAND" == cmd) | ||
554 | justRLV("unsit=force"); | ||
555 | else if (("ADD_BLOCKED" == cmd) || ("ADD_BOSS" == cmd) || ("ADD_TRUSTEE" == cmd) | ||
556 | || ("DEL_BLOCKED" == cmd) || ("DEL_BOSS" == cmd) || ("DEL_TRUSTEE" == cmd) | ||
557 | || ("NEW_BLOCKED" == cmd) || ("NEW_BOSS" == cmd) || ("NEW_TRUSTEE" == cmd) | ||
558 | || ("OLD_BLOCKED" == cmd) || ("OLD_BOSS" == cmd) || ("OLD_TRUSTEE" == cmd)) | ||
559 | return changeAccess(id, cmd, button, source); | ||
560 | else if ("LIST_BOSS" == cmd) | ||
561 | showAccess(id); | ||
562 | else if ("CHOOSE_CLOTHES" == cmd) | ||
563 | { | ||
564 | someBoss = id; | ||
565 | sendRLV("getoutfit"); | ||
566 | return FALSE; | ||
567 | } | ||
568 | else if ("TAKEOFF" == cmd) | ||
569 | justRLV("remoutfit:" + button + "=force"); | ||
570 | else if ("CHOOSE_OUTFIT" == cmd) | ||
571 | { | ||
572 | someBoss = id; | ||
573 | sendRLV("getinv"); | ||
574 | return FALSE; | ||
575 | } | ||
576 | else if ("PUTON" == cmd) | ||
577 | { | ||
578 | string type = llGetSubString(button, 0, 1); | ||
579 | |||
580 | if ("+ " == type) | ||
581 | { | ||
582 | s("Adding contents of #RLV/" + llGetSubString(button, 2, -1)); | ||
583 | justRLV("attachallover:" + llGetSubString(button, 2, -1) + "=force"); | ||
584 | } | ||
585 | else if ("- " == type) | ||
586 | { | ||
587 | s("Removing contents of #RLV/ " + llGetSubString(button, 2, -1)); | ||
588 | justRLV("detachall:" + llGetSubString(button, 2, -1) + "=force"); | ||
589 | } | ||
590 | else | ||
591 | { | ||
592 | // TODO - Doesn't seem to be a way of detaching everything else, | ||
593 | // this just replaces anything the new outfit covers. | ||
594 | // Might be better to show sub folders instead. | ||
595 | s("Replacing outfit with contents of #RLV/ " + button); | ||
596 | justRLV("attachall:" + button + "=force"); | ||
597 | } | ||
598 | } | ||
599 | else if ("CHOOSE_ATTACH" == cmd) | ||
600 | { | ||
601 | someBoss = id; | ||
602 | sendRLV("getattach"); | ||
603 | return FALSE; | ||
604 | } | ||
605 | else if ("DETACH" == cmd) | ||
606 | justRLV("detach:" + button + "=force"); | ||
607 | else if ("RLV_OUT" == cmd) | ||
608 | { | ||
609 | gotRLV(llList2Integer(llParseString2List(data, [" "], []), 0), FALSE, ""); | ||
610 | } | ||
611 | else if (("REGION" == cmd) || ("TELEPORT" == cmd)) | ||
612 | { | ||
613 | list bdy = llParseStringKeepNulls(llUnescapeURL(data), ["|"], []); | ||
614 | TPtDestination = bdy; | ||
615 | // TODO - should validate these parameters. | ||
616 | // TODO - once the stuck detector is in place, we'll know where they used to be, | ||
617 | // try to TP to a similar distance away, in a similar direction. | ||
618 | // Or at least to half the follow distance away. | ||
619 | s("Teleporting you to " + osKey2Name(Stalkee) | ||
620 | + " at " + llList2String(TPtDestination, 1) + " " + llList2String(TPtDestination, 2)); | ||
621 | osTeleportAgent(Owner, llList2String(TPtDestination, 1), llList2Vector(TPtDestination, 2), <1.0,1.0,1.0>); | ||
622 | } | ||
623 | else if ("TESTS" == cmd) | ||
624 | s("selected test " + data); | ||
625 | else if ("URL" == cmd) | ||
626 | { | ||
627 | URL = data; | ||
628 | if (NULL_KEY != LeashKey) | ||
629 | osMessageObject(LeashKey, "URL|" + data); | ||
630 | d("New URL " + URL); | ||
631 | } | ||
632 | else if ("▲" == cmd) | ||
633 | ; | ||
634 | else if (-1 == listFindString(Settings, fr + cmd, sSTRIDE)) | ||
635 | { | ||
636 | if (fMENU == source) | ||
637 | d("Unknown menu command '" + cmd + "' from -\n\t" + button); | ||
638 | else | ||
639 | d("Unknown command '" + cmd + "' from -\n\t" + button); | ||
640 | } | ||
641 | return (source == fMENU); | ||
642 | } | ||
643 | |||
644 | |||
645 | // General variables | ||
646 | integer LMchannel = -8888; | ||
647 | integer LMhandle0 = 0; | ||
648 | integer LMhandle1 = 0; | ||
649 | integer LGchannel = -9119; | ||
650 | key LeashKey = NULL_KEY; | ||
651 | key someBoss = NULL_KEY; | ||
652 | |||
653 | // TP tracker. | ||
654 | list TPtDestination = []; | ||
655 | |||
656 | // Movement | ||
657 | integer mode = 0; | ||
658 | integer MODE_NONE = 0; // Do nothing. | ||
659 | integer MODE_SIT = 1; // Original sit tester - menu for adjusting the sit. | ||
660 | integer MODE_DRIVE = 2; // Drivable box using controls and set pos / rot. | ||
661 | integer MODE_SINGLE = 3; // llMoveToTarget(), single scan, stop when we get there. | ||
662 | integer MODE_SCAN = 4; // llMoveToTarget(), multiple scans, stop when we get there. | ||
663 | integer MODE_FOLLOW = 5; // llMoveToTarget(), multiple scans, keep following after getting there. | ||
664 | integer MODE_LEASH = 5; // llMoveToTarget(), multiple scans, keep following after getting there, with leash. | ||
665 | |||
666 | list ATTACHMENTS = | ||
667 | [ | ||
668 | "none", | ||
669 | "chest", "skull", "left shoulder", "right shoulder", "left hand", "right hand", "left foot", "right foot", | ||
670 | "spine", "pelvis", | ||
671 | "mouth", "chin", "left ear", "right ear", "left eyeball", "right eyeball", "nose", | ||
672 | "r upper arm", "r forearm", "l upper arm", "l forearm", | ||
673 | "right hip", "r upper leg", "r lower leg", "left hip", "l upper leg", "l lower leg", | ||
674 | "stomach", "left pec", "right pec", | ||
675 | "center 2", "top right", "top", "top left", "center", "bottom left", "bottom", "bottom right", | ||
676 | "neck", "root" | ||
677 | ]; | ||
678 | |||
679 | list CLOTHES = | ||
680 | [ | ||
681 | "gloves", | ||
682 | "jacket", "pants", "shirt", | ||
683 | "shoes", "skirt", "socks", | ||
684 | "underpants", "undershirt", | ||
685 | "skin", "eyes", "hair", "shape", "alpha", "tattoo", "physics" | ||
686 | ]; | ||
687 | |||
688 | // simple emoter | ||
689 | string OurName; | ||
690 | integer meListening; | ||
691 | integer bareListening; | ||
692 | integer meChannel = 12; | ||
693 | integer bareChannel = 123; | ||
694 | |||
695 | list nextWord(string separator, string message, string last) | ||
696 | { | ||
697 | list result = []; | ||
698 | integer index; | ||
699 | |||
700 | index = llSubStringIndex(message, separator); | ||
701 | if (0 <= index) | ||
702 | { | ||
703 | if (0 != index) // Check for a corner case. | ||
704 | last += llGetSubString(message, 0, index - 1); | ||
705 | if ((index + 1) < llStringLength(message)) | ||
706 | message = llGetSubString(message, index + 1, -1); | ||
707 | else | ||
708 | message = ""; | ||
709 | result += message + last; | ||
710 | } | ||
711 | else | ||
712 | result += last + message; | ||
713 | |||
714 | return(result); | ||
715 | } | ||
716 | |||
717 | integer findLink(string name) | ||
718 | { | ||
719 | integer n = llGetNumberOfPrims(); | ||
720 | integer link; | ||
721 | for (link = 1; link <= n; ++link) | ||
722 | { | ||
723 | if (llGetLinkName(link) == name) | ||
724 | return link; | ||
725 | } | ||
726 | link = -1; | ||
727 | return link; | ||
728 | } | ||
729 | |||
730 | leashTo(key id) | ||
731 | { | ||
732 | integer link = findLink("leashpoint"); | ||
733 | float age = 4.0; | ||
734 | integer count = 1; | ||
735 | float rate = 0.05; | ||
736 | list p = | ||
737 | [ | ||
738 | PSYS_PART_FLAGS, PSYS_PART_FOLLOW_VELOCITY_MASK | PSYS_PART_TARGET_POS_MASK | PSYS_PART_EMISSIVE_MASK | PSYS_PART_FOLLOW_SRC_MASK, | ||
739 | PSYS_SRC_PATTERN, PSYS_SRC_PATTERN_DROP, | ||
740 | PSYS_PART_START_SCALE, <0.3, 0.3, 0.1>, | ||
741 | PSYS_SRC_ACCEL, <0,0,-0.4>, | ||
742 | PSYS_PART_MAX_AGE, age, | ||
743 | PSYS_SRC_BURST_RADIUS, 0.1, | ||
744 | PSYS_SRC_BURST_RATE, rate, | ||
745 | PSYS_SRC_BURST_PART_COUNT, count, | ||
746 | PSYS_SRC_BURST_SPEED_MIN, 3.0, | ||
747 | PSYS_SRC_BURST_SPEED_MAX, 3.0, | ||
748 | PSYS_SRC_TEXTURE, getSetting("leash"), | ||
749 | PSYS_SRC_TARGET_KEY, id, | ||
750 | PSYS_SRC_ANGLE_BEGIN, 0.0, | ||
751 | PSYS_SRC_ANGLE_END, PI, | ||
752 | PSYS_SRC_OMEGA, <0,0,1>, | ||
753 | PSYS_SRC_MAX_AGE, 0.0 | ||
754 | ]; | ||
755 | if ( ((age / rate) * count) < 4096) | ||
756 | { | ||
757 | if (-1 != link) | ||
758 | llLinkParticleSystem(link, p); | ||
759 | else | ||
760 | llParticleSystem(p); | ||
761 | if (0 == LMhandle0) | ||
762 | { | ||
763 | LMhandle0 = llListen(LMchannel, "", NULL_KEY, (string) llGetOwnerKey(Stalkee) + "handle ok"); | ||
764 | LMhandle1 = llListen(LMchannel, "", NULL_KEY, (string) llGetOwnerKey(Stalkee) + "handle detached"); | ||
765 | llShout(LMchannel, (string) llGetOwnerKey(Stalkee) + "collar"); | ||
766 | llShout(LMchannel, (string) llGetOwnerKey(Stalkee) + "handle"); | ||
767 | llShout(LGchannel, "lockguard " + Owner + " handle link " + llGetLinkKey(link)); | ||
768 | } | ||
769 | } | ||
770 | else | ||
771 | D("Your particle system creates too many concurrent particles. Reduce count or age, or increase rate."); | ||
772 | } | ||
773 | |||
774 | unleash() | ||
775 | { | ||
776 | integer link = findLink("leashpoint"); | ||
777 | if (LMhandle0) llListenRemove(LMhandle0); | ||
778 | if (LMhandle1) llListenRemove(LMhandle1); | ||
779 | LMhandle0 = 0; | ||
780 | LMhandle1 = 0; | ||
781 | if (-1 != link) | ||
782 | llLinkParticleSystem(link, []); | ||
783 | else | ||
784 | llParticleSystem([]); | ||
785 | } | ||
786 | |||
787 | |||
788 | // Following | ||
789 | key Stalker; | ||
790 | key Stalkee; | ||
791 | integer tid = 0; | ||
792 | float RANGE = 4.0; // Meters away that we stop walking towards. | ||
793 | float TAU = 0.1; // Make smaller for more rushed following. | ||
794 | |||
795 | float bb = 0.0; | ||
796 | float tweak = 0.0; | ||
797 | float sr = 10; | ||
798 | integer Lag = 100; | ||
799 | |||
800 | vector getRange(key this) | ||
801 | { | ||
802 | list det = llGetObjectDetails(this, [OBJECT_POS, OBJECT_ROT]); | ||
803 | |||
804 | if (0 == llGetListLength(det)) | ||
805 | { | ||
806 | // Note - we can't use llGetObjectDetails() here, coz they might not be in adjacent sims either. | ||
807 | s("Can't find " + osKey2Name(this) + " in the sim."); | ||
808 | stopGoto(TRUE); | ||
809 | return ZERO_VECTOR; | ||
810 | } | ||
811 | |||
812 | vector pos = llList2Vector(det, 0); | ||
813 | // rotation rot = (rotation)llList2String(det, 1); | ||
814 | |||
815 | if (this != Stalkee) | ||
816 | { | ||
817 | Stalkee = this; | ||
818 | |||
819 | vector tSize = llGetAgentSize(this); | ||
820 | vector mSize = llGetAgentSize(llGetOwner()); | ||
821 | tweak = (mSize.z - tSize.z) * 2; | ||
822 | if (0.0 > tweak) | ||
823 | tweak = 0.0 - tweak; | ||
824 | //d("tweak " + (string) mSize.z + " " + (string) tSize.z + " " + (string) tweak); | ||
825 | if (1.6 > tweak) | ||
826 | tweak = 1.6; | ||
827 | list box = llGetBoundingBox(this); | ||
828 | bb = llVecDist(llList2Vector(box, 1), llList2Vector(box, 0)) / 2.0; | ||
829 | } | ||
830 | pos.z = pos.z - tweak; | ||
831 | // pos += offset * rot; | ||
832 | return pos; | ||
833 | } | ||
834 | |||
835 | goto(key this) | ||
836 | { | ||
837 | if (NULL_KEY == this) | ||
838 | return; | ||
839 | |||
840 | float dist = 0.0; | ||
841 | vector pos = getRange(this); | ||
842 | |||
843 | if (ZERO_VECTOR == pos) | ||
844 | return; | ||
845 | dist = llVecDist(pos, llGetPos()); | ||
846 | vector p = pos - llGetPos(); | ||
847 | float r = llAtan2(p.x, p.y); | ||
848 | if (llFabs(sr - r) > 0.25) | ||
849 | { | ||
850 | justRLV("setrot:" + (string) r + "=force"); | ||
851 | sr = r; | ||
852 | } | ||
853 | if (dist > (RANGE + bb)) | ||
854 | { | ||
855 | if (dist > (2.0 * (RANGE + bb))) | ||
856 | osSetSpeed(Owner, 2.0); | ||
857 | else | ||
858 | osSetSpeed(Owner, 1.0); | ||
859 | |||
860 | llStopMoveToTarget(); | ||
861 | if (0 != tid) | ||
862 | llTargetRemove(tid); | ||
863 | llMoveToTarget(pos, TAU); | ||
864 | } | ||
865 | else | ||
866 | { | ||
867 | if (MODE_SINGLE == mode) | ||
868 | stopGoto(TRUE); | ||
869 | else | ||
870 | stopGoto(FALSE); | ||
871 | } | ||
872 | if ((MODE_FOLLOW == mode) || (MODE_LEASH == mode) || (MODE_SINGLE == mode) || (MODE_SCAN == mode)) | ||
873 | { | ||
874 | float tick = 0.35; | ||
875 | float dil = llGetRegionTimeDilation(); // Between 0 and 1. | ||
876 | float fps = llGetRegionFPS(); // Frames per second, up to 50. | ||
877 | integer newLag = (integer) (dil * fps); | ||
878 | |||
879 | if (llAbs(Lag - newLag) > 9) | ||
880 | { | ||
881 | Lag = newLag; | ||
882 | tick = ((60 - (dil * fps)) / 30) + 0.15; | ||
883 | } | ||
884 | addEvent(tick, "GOTO_TICK"); | ||
885 | } | ||
886 | else | ||
887 | { | ||
888 | Lag = 100; | ||
889 | addEvent(0.0, "GOTO_TICK"); | ||
890 | } | ||
891 | } | ||
892 | |||
893 | stopGoto(integer all) | ||
894 | { | ||
895 | addEvent(0.0, "GOTO_TICK"); | ||
896 | llStopMoveToTarget(); | ||
897 | if (0 != tid) | ||
898 | { | ||
899 | llTargetRemove(tid); | ||
900 | tid = 0; | ||
901 | } | ||
902 | osSetSpeed(Owner, 1.0); | ||
903 | if (all) | ||
904 | { | ||
905 | Stalkee = NULL_KEY; | ||
906 | mode = MODE_NONE; | ||
907 | Lag = 100; | ||
908 | bb = 0.0; | ||
909 | tweak = 0.0; | ||
910 | sr = 10; | ||
911 | unleash(); | ||
912 | } | ||
913 | } | ||
914 | |||
915 | |||
916 | // We have to jump through hoops coz RLV wont tell us if a command failed, or was delayed. | ||
917 | // And viewer support for RLV is all kinds of broken. | ||
918 | integer RLVchannel; | ||
919 | //integer RLVCHAN = -1812221819; // RLV relay channel. | ||
920 | string RLVversion; | ||
921 | list RLVq; // Queue of RLV commands. | ||
922 | integer qCHAN = 0; | ||
923 | integer qCMD = 1; | ||
924 | integer qNUM = 2; | ||
925 | integer qRSP = 3; | ||
926 | integer qTIME = 4; | ||
927 | integer qTRIES = 5; | ||
928 | integer qLSTN = 6; | ||
929 | integer qSTRIDE = 7; | ||
930 | integer RLVl; // Current RLV channel. | ||
931 | integer RLVd; // RLV startup is done. | ||
932 | list RLVa; // all RLV commands. | ||
933 | list RLVb; // RLV comands in blacklist. | ||
934 | list RLVs; // RLV commands currently restricted. | ||
935 | list RLVu = // RLV commands we actually use. | ||
936 | [ | ||
937 | "attachallover", "attachall", "clear", "detach", "detachall", "getattach", | ||
938 | "getcommand", "getstatusall", "notify", | ||
939 | "getinv", "getoutfit", "remoutfit", "setrot", "sit", "unsit", | ||
940 | "versionnew", "versionnumbl" | ||
941 | ]; | ||
942 | |||
943 | justRLV(string cmd) | ||
944 | { | ||
945 | sendRLV(cmd, -1.0); | ||
946 | } | ||
947 | sendRLV(string cmd) | ||
948 | { | ||
949 | sendRLV(cmd, 10.0); | ||
950 | } | ||
951 | sendRLV(string cmd, float time) | ||
952 | { | ||
953 | integer c = llGetListLength(llParseString2List(cmd, [","], [])); | ||
954 | RLVq += [(string)(RLVchannel + RLVl), cmd, (string) c, 0, time, 0, 0]; | ||
955 | ++RLVl; | ||
956 | doRLV(); | ||
957 | } | ||
958 | doRLV() | ||
959 | { | ||
960 | integer i; | ||
961 | // Normally you don't get the length in the middle of the loop, but it may change on us. | ||
962 | for (i = 0; i < llGetListLength(RLVq); i += qSTRIDE) | ||
963 | doRLV(i); | ||
964 | } | ||
965 | doRLV(integer f) | ||
966 | { | ||
967 | integer chan = llList2Integer(RLVq, f + qCHAN); | ||
968 | string cmds = llList2String(RLVq, f + qCMD); | ||
969 | integer t = llList2Integer(RLVq, f + qTRIES); | ||
970 | float time = llList2Float(RLVq, f + qTIME); | ||
971 | integer h = llList2Integer(RLVq, f + qLSTN); | ||
972 | string cmd = ""; | ||
973 | if (0 != h) | ||
974 | return; | ||
975 | // TODO - check if this command is available and not blacklisted. | ||
976 | // Though since getcommands seems to miss "clear" and "setrot", even though they work, seems pointless. | ||
977 | if (0.0 < time) | ||
978 | { | ||
979 | list c = llParseString2List(cmds, [","], []); | ||
980 | integer l = llGetListLength(c); | ||
981 | integer i; | ||
982 | for (i = 0; i < l; ++i) | ||
983 | cmd += "," + llList2String(c, i) + "=" + chan; | ||
984 | cmd = llGetSubString(cmd, 1, -1); | ||
985 | if (0 == h) | ||
986 | { | ||
987 | h = llListen(chan, "", Owner, ""); | ||
988 | RLVq = llListReplaceList(RLVq, [h], f + qLSTN, f + qLSTN); | ||
989 | } | ||
990 | llOwnerSay("@" + cmd); | ||
991 | ++t; | ||
992 | RLVq = llListReplaceList(RLVq, [t], f + qTRIES, f + qTRIES); | ||
993 | addEvent(10.0, "RLV_OUT " + chan + " " + cmds); | ||
994 | } | ||
995 | else if (RLVd) | ||
996 | { | ||
997 | llOwnerSay("@" + cmds); | ||
998 | RLVq = llListReplaceList(RLVq, [], f, f + qSTRIDE - 1); | ||
999 | } | ||
1000 | } | ||
1001 | |||
1002 | getRLV(string name) | ||
1003 | { | ||
1004 | string r; | ||
1005 | integer l = llGetListLength(RLVu); | ||
1006 | integer i; | ||
1007 | for (i = 0; i < l; ++i) | ||
1008 | r += "," + name + ":" + llList2String(RLVu, i); | ||
1009 | sendRLV(llGetSubString(r, 1, -1)); | ||
1010 | } | ||
1011 | |||
1012 | gotStatus(string message) | ||
1013 | { | ||
1014 | if ("" != message) | ||
1015 | { | ||
1016 | list st = llParseString2List(message, ["/"], []); | ||
1017 | integer l = llGetListLength(st); | ||
1018 | integer i; | ||
1019 | for (i = 0; i < l; ++i) | ||
1020 | { | ||
1021 | string r = llList2String(st, i); | ||
1022 | if ("notify:" == llGetSubString(r, 0, 6)) | ||
1023 | { // Not documented anywhere I can find, but this is telling us we already have one. | ||
1024 | // Though I have seen it telling us this is the one we just created. Play it safe. | ||
1025 | integer t = (integer) llGetSubString(r, 7, -1); | ||
1026 | if((RLVchannel - 1) != t) | ||
1027 | { | ||
1028 | d("gotStatus removing " + r); | ||
1029 | justRLV("notify:" + t + "=rem"); | ||
1030 | } | ||
1031 | } | ||
1032 | else | ||
1033 | { | ||
1034 | integer a = TRUE; | ||
1035 | integer j = llSubStringIndex(r, "="); | ||
1036 | if (-1 != j) | ||
1037 | a = (llGetSubString(r, j + 1, j + 1) == "n"); | ||
1038 | if (a) | ||
1039 | { | ||
1040 | d("gotStatus adding " + r); | ||
1041 | RLVs += [r]; | ||
1042 | } | ||
1043 | else | ||
1044 | { | ||
1045 | j = llListFindList(RLVs, [r]); | ||
1046 | if (-1 != j) | ||
1047 | { | ||
1048 | d("gotStatus subtracting " + r); | ||
1049 | RLVs = llListReplaceList(RLVs, [], j, j); | ||
1050 | } | ||
1051 | else | ||
1052 | d("gotStatus already got " + r); | ||
1053 | } | ||
1054 | } | ||
1055 | } | ||
1056 | RLVs = deDup(RLVs); | ||
1057 | s(llGetListLength(RLVs) + " RLV restrictions - " + llDumpList2String(RLVs, " / ")); | ||
1058 | } | ||
1059 | } | ||
1060 | |||
1061 | list deDup(list lst) | ||
1062 | { | ||
1063 | integer l = llGetListLength(lst); | ||
1064 | integer i; | ||
1065 | lst = llListSort(lst, 1, TRUE); | ||
1066 | if (1 < l) | ||
1067 | { | ||
1068 | //d("deDup " + llDumpList2String(lst, " ~ ")); | ||
1069 | for (i = 1; i < l; ++i) | ||
1070 | { | ||
1071 | if (llList2String(lst, i - 1) == llList2String(lst, i)) | ||
1072 | { | ||
1073 | lst = llListReplaceList(lst, [], i, i); | ||
1074 | --i; --l; | ||
1075 | } | ||
1076 | } | ||
1077 | //d("deDup " + llDumpList2String(lst, " ~ ") + "\n"); | ||
1078 | } | ||
1079 | return lst; | ||
1080 | } | ||
1081 | |||
1082 | string gotRLV(integer chan, integer g, string message) | ||
1083 | { | ||
1084 | string r; | ||
1085 | integer f = listFindString(RLVq, (string) chan, qSTRIDE); | ||
1086 | if (-1 != f) | ||
1087 | { | ||
1088 | r = llList2String(RLVq, f + qCMD); | ||
1089 | integer n = llList2Integer(RLVq, f + qNUM); | ||
1090 | integer rsp = llList2Integer(RLVq, f + qRSP); | ||
1091 | integer v = ("versionnew" == r); | ||
1092 | integer t = llList2Integer(RLVq, f + qTRIES); | ||
1093 | integer h = llList2Integer(RLVq, f + qLSTN); | ||
1094 | |||
1095 | if (g) | ||
1096 | { | ||
1097 | //string rp = "FULL "; | ||
1098 | addEvent(0.0, "RLV_OUT " + chan + " " + llList2String(RLVq, f + qCMD)); | ||
1099 | ++rsp; | ||
1100 | RLVq = llListReplaceList(RLVq, [rsp], f + qRSP, f + qRSP); | ||
1101 | list c = llParseString2List(r, [","], []); | ||
1102 | r = llList2String(llParseString2List(llList2String(c, 0), [":", "="], []), 0); | ||
1103 | if (rsp >= n) | ||
1104 | { | ||
1105 | --t; | ||
1106 | if (0 < t) | ||
1107 | { | ||
1108 | RLVq = llListReplaceList(RLVq, [0], f + qRSP, f + qRSP); | ||
1109 | RLVq = llListReplaceList(RLVq, [t], f + qTRIES, f + qTRIES); | ||
1110 | //d("gotRLV " + rp + " " + chan + " " + n + "==" + rsp + " " + t + " " + r + " -> " + message + "\n" + llDumpList2String(llList2List(RLVq, f, f + qSTRIDE - 1), " ~ ")); | ||
1111 | } | ||
1112 | else | ||
1113 | { | ||
1114 | if (0 != h) | ||
1115 | llListenRemove(h); | ||
1116 | //d("gotRLV " + rp + " " + chan + " " + n + "==" + rsp + " " + " " + t + " " + r + " -> " + message); | ||
1117 | RLVq = llListReplaceList(RLVq, [], f, f + qSTRIDE - 1); | ||
1118 | } | ||
1119 | } | ||
1120 | else | ||
1121 | { | ||
1122 | //rp = "PARTIAL"; | ||
1123 | //d("gotRLV " + rp + " " + chan + " " + n + "==" + rsp + " " + " " + t + " " + r + " -> " + message + "\n" + llDumpList2String(llList2List(RLVq, f, f + qSTRIDE - 1), " ~ ")); | ||
1124 | } | ||
1125 | |||
1126 | if (v) | ||
1127 | { | ||
1128 | RLVversion = message; | ||
1129 | s(RLVversion); | ||
1130 | getRLV("getcommand"); | ||
1131 | sendRLV("versionnumbl"); | ||
1132 | } | ||
1133 | else | ||
1134 | { | ||
1135 | if ("getcommand" == r) | ||
1136 | RLVa += llParseString2List(message, ["/", ";"], []); | ||
1137 | else if ("versionnumbl" == r) | ||
1138 | { | ||
1139 | list tm = llParseString2List(message, ["/", ";"], []); | ||
1140 | if (1 < llGetListLength(tm)) | ||
1141 | RLVb += llList2List(tm, 1, -1); | ||
1142 | } | ||
1143 | else if ("getstatusall" == r) | ||
1144 | gotStatus(message); | ||
1145 | else if ("" == r) | ||
1146 | d("RLV report for unknown command - " + message); | ||
1147 | } | ||
1148 | } | ||
1149 | else | ||
1150 | { | ||
1151 | if (2 >= t) | ||
1152 | { // We are either waiting for the viewer to get it's shit together on login, | ||
1153 | // or slowly finding out these commands are not supported anyway. | ||
1154 | // 30 seconds seems about right for the former, and has been documented. | ||
1155 | d("RLV command " + r + " not responding! Trying again. " + t); | ||
1156 | if (1 == t) s("RLV could take up to 60 seconds to start up, you may have to wait for that."); | ||
1157 | if (0 != h) | ||
1158 | { | ||
1159 | llListenRemove(h); | ||
1160 | RLVq = llListReplaceList(RLVq, [0], f + qLSTN, f + qLSTN); | ||
1161 | } | ||
1162 | doRLV(f); | ||
1163 | } | ||
1164 | else | ||
1165 | { | ||
1166 | if (v) // Various docs say RLV might take upto 30 seconds to start in the viewer. | ||
1167 | s(Owner, "Your viewer is not RLV-enabled, so some things wont work."); | ||
1168 | else | ||
1169 | { | ||
1170 | d("RLV command " + r + " not responding! Giving up."); | ||
1171 | s("Your viewer is taking too long to start up RLV."); | ||
1172 | } | ||
1173 | if (0 != h) llListenRemove(h); | ||
1174 | RLVq = llListReplaceList(RLVq, [], f, f + qSTRIDE - 1); | ||
1175 | } | ||
1176 | } | ||
1177 | } | ||
1178 | else if ((RLVchannel - 1) == chan) | ||
1179 | { | ||
1180 | d("gotRLV @notify response - " + message); | ||
1181 | gotStatus(message); | ||
1182 | } | ||
1183 | else | ||
1184 | D("gotRLV() didn't find - " + g + " " + chan + " -> " + message + " " + "\n" + llDumpList2String(RLVq, " ~ ")); | ||
1185 | |||
1186 | if ((!RLVd) && ("" != RLVversion)) | ||
1187 | { | ||
1188 | integer l = llGetListLength(RLVq); | ||
1189 | integer i; | ||
1190 | RLVd = TRUE; | ||
1191 | for (i = 0; i < l; i += qSTRIDE) | ||
1192 | { | ||
1193 | if (0 != llList2Integer(RLVq, i + qLSTN)) | ||
1194 | RLVd = FALSE; | ||
1195 | } | ||
1196 | if (RLVd) | ||
1197 | { | ||
1198 | if (0 == llGetListLength(RLVa)) RLVa = RLVu; | ||
1199 | getRLV("getstatusall"); // Doing this now coz Cool VL is odd at login. | ||
1200 | // TODO - Also deal with "%f" == "=force". | ||
1201 | RLVa = deDup(RLVa); | ||
1202 | RLVb = deDup(RLVb); | ||
1203 | l = llGetListLength(RLVb); | ||
1204 | for (i = 0; i < l; ++i) | ||
1205 | { | ||
1206 | f = llListFindList(RLVa, [llList2String(RLVb, i)]); | ||
1207 | if (-1 != f) | ||
1208 | RLVa = llListReplaceList(RLVa, [], f, f); | ||
1209 | } | ||
1210 | list bl; | ||
1211 | l = llGetListLength(RLVu); | ||
1212 | for (i = 0; i < l; ++i) | ||
1213 | { | ||
1214 | f = llListFindList(RLVa, [llList2String(RLVu, i)]); | ||
1215 | if (-1 == f) | ||
1216 | bl += [llList2String(RLVu, i)]; | ||
1217 | } | ||
1218 | s("RLV started up in " + (string) (llGetTimeOfDay() - Start) + " seconds.\n" | ||
1219 | + llGetListLength(RLVa) + " RLV commands, " | ||
1220 | + llGetListLength(RLVb)+ " RLV black listed commands - " | ||
1221 | + llDumpList2String(RLVb, " / ")); | ||
1222 | if (0 != llGetListLength(bl)) | ||
1223 | s("The following RLV commands are unsupported by your viewer, or blacklisted - " | ||
1224 | + llDumpList2String(bl, ", ") | ||
1225 | + "\nSome things may or may not work. That list may not be accurate."); | ||
1226 | justRLV("notify:" + (RLVchannel - 1) + "=add"); | ||
1227 | doRLV(); | ||
1228 | } | ||
1229 | } | ||
1230 | return r; | ||
1231 | } | ||
1232 | |||
1233 | init() | ||
1234 | { | ||
1235 | stopGoto(TRUE); | ||
1236 | Stalker = llGetOwner(); | ||
1237 | OurName = llGetObjectName(); | ||
1238 | RLVchannel = llAbs((integer) ("0x" + llGetSubString((string) llGetKey(), -4, -1))) + (integer) llFrand(9999.0); | ||
1239 | llListen(RLVchannel, "", llGetOwner(), ""); | ||
1240 | meListening = llListen(meChannel, "", llGetOwner(), ""); | ||
1241 | bareListening = llListen(bareChannel, "", llGetOwner(), ""); | ||
1242 | } | ||
1243 | |||
1244 | laterInit() | ||
1245 | { | ||
1246 | showAccess(Owner); | ||
1247 | } | ||
1248 | |||
1249 | default | ||
1250 | { | ||
1251 | state_entry() | ||
1252 | { | ||
1253 | llSleep(0.2); | ||
1254 | Start = llGetTimeOfDay(); | ||
1255 | Owner = llGetOwner(); | ||
1256 | d("\n\n1ring sending RESET @ " + (string) Start + "\n"); | ||
1257 | ScriptName = llGetScriptName(); ScriptKey = llGetInventoryKey(ScriptName); | ||
1258 | LibraryKey = NULL_KEY; | ||
1259 | sendScript(lRESET, []); | ||
1260 | init(); | ||
1261 | } | ||
1262 | |||
1263 | link_message(integer sender, integer num, string message, key id) | ||
1264 | { | ||
1265 | linky(num, message, id); | ||
1266 | } | ||
1267 | |||
1268 | on_rez(integer param) | ||
1269 | { | ||
1270 | // showAccess(Owner); | ||
1271 | d("on_rez reset"); | ||
1272 | llResetScript(); | ||
1273 | } | ||
1274 | |||
1275 | attach(key id) | ||
1276 | { | ||
1277 | if ((NULL_KEY != id) && ((llGetTimeOfDay() - Start) > 60.0)) | ||
1278 | { | ||
1279 | s("Attach detected, and RLV didn't respond yet, resetting."); | ||
1280 | // llResetScript(); | ||
1281 | } | ||
1282 | } | ||
1283 | |||
1284 | at_target(integer tnum, vector targetpos, vector ourpos) | ||
1285 | { | ||
1286 | if (tnum == tid) | ||
1287 | { | ||
1288 | vector p = targetpos - ourpos; | ||
1289 | justRLV("setrot:" + (string) llAtan2(p.x, p.y) + "=force"); | ||
1290 | if ((MODE_SINGLE == mode) || (MODE_SCAN == mode)) | ||
1291 | stopGoto(TRUE); | ||
1292 | } | ||
1293 | } | ||
1294 | |||
1295 | run_time_permissions(integer perm) | ||
1296 | { | ||
1297 | if (perm & PERMISSION_TAKE_CONTROLS) | ||
1298 | { | ||
1299 | llTakeControls(0, FALSE, FALSE); | ||
1300 | s(llKey2Name(someBoss) + " has made you stay."); | ||
1301 | } | ||
1302 | } | ||
1303 | |||
1304 | listen(integer channel, string name, key id, string message) | ||
1305 | { | ||
1306 | if (channel == LMchannel) | ||
1307 | { | ||
1308 | list dt = llGetObjectDetails(id, [OBJECT_ROOT]); | ||
1309 | key t = llGetSubString(message, 0, 35); | ||
1310 | message = llGetSubString(message, 36, -1); | ||
1311 | if ((llList2Key(dt, 0) == Stalkee) || (llGetOwnerKey(id) == Stalkee)) | ||
1312 | { | ||
1313 | //leash holder announced it got detached... send particles to avi | ||
1314 | if (message == "handle detached") | ||
1315 | leashTo(Stalkee); | ||
1316 | else | ||
1317 | { | ||
1318 | // We heard from a leash holder. re-direct particles | ||
1319 | LeashKey = id; | ||
1320 | if (message == "collar ok") | ||
1321 | leashTo(id); | ||
1322 | if (message == "handle ok") | ||
1323 | leashTo(id); | ||
1324 | osMessageObject(LeashKey, "URL|" + URL); | ||
1325 | d("HEY leashy! " + URL); | ||
1326 | } | ||
1327 | } | ||
1328 | } | ||
1329 | |||
1330 | if ((RLVchannel <= channel) && (channel < (RLVchannel + RLVl))) | ||
1331 | { | ||
1332 | string rlv = gotRLV(channel, TRUE, message); | ||
1333 | list items = []; | ||
1334 | channel = 0; | ||
1335 | if ("getattach" == rlv) | ||
1336 | { | ||
1337 | do | ||
1338 | { | ||
1339 | if (llGetSubString(message, channel, channel) == "1") | ||
1340 | items += llList2String(ATTACHMENTS, channel); | ||
1341 | } while(++channel < 42); | ||
1342 | |||
1343 | dynamicMenu(someBoss, "clothes", "DETACH", "Please select the attachment to detach", | ||
1344 | llDumpList2String(items, "|"), "DETACH"); | ||
1345 | } | ||
1346 | else if ("getoutfit" == rlv) | ||
1347 | { | ||
1348 | do | ||
1349 | { | ||
1350 | if (llGetSubString(message, channel, channel) == "1") | ||
1351 | items += llList2String(CLOTHES, channel); | ||
1352 | } while(++channel < 20); | ||
1353 | |||
1354 | dynamicMenu(someBoss, "clothes", "TAKEOFF", "Please select the clothes to take off", | ||
1355 | llDumpList2String(items, "|"), "TAKEOFF"); | ||
1356 | } | ||
1357 | else if ("getinv" == rlv) | ||
1358 | { | ||
1359 | list folders = llListSort(llCSV2List(message), 1, TRUE); | ||
1360 | integer l = llGetListLength(folders); | ||
1361 | integer i; | ||
1362 | |||
1363 | for (i = 0; i < l; ++i) | ||
1364 | { | ||
1365 | string folder = llList2String(folders, i); | ||
1366 | items += [folder, "+ " + folder, "- " + folder]; | ||
1367 | } | ||
1368 | dynamicMenu(someBoss, "clothes", "PUTON", "Please select the outfit to put on", | ||
1369 | llDumpList2String(items, "|"), "PUTON"); | ||
1370 | } | ||
1371 | } | ||
1372 | |||
1373 | list result = nextWord(" ", message, ""); | ||
1374 | if (channel == meChannel) | ||
1375 | { | ||
1376 | list names = nextWord(" ", llKey2Name(llGetOwner()), ""); | ||
1377 | |||
1378 | if (llGetSubString(message, 0, 0) == "'") | ||
1379 | { | ||
1380 | list suffix = nextWord(" ", message, ""); | ||
1381 | names = ["", llList2String(names, 1) + llList2String(suffix, 1)]; | ||
1382 | message = llList2String(suffix, 0); | ||
1383 | } | ||
1384 | // Rename ourselves to the first word of owners name. | ||
1385 | llSetObjectName(llList2String(names, 1)); | ||
1386 | llSay(0, "/me " + message); | ||
1387 | } | ||
1388 | else if (channel == bareChannel) | ||
1389 | { | ||
1390 | // Rename ourselves to the first word. | ||
1391 | llSetObjectName(llList2String(result, 1)); | ||
1392 | llSay(0, "/me " + llList2String(result, 0)); | ||
1393 | } | ||
1394 | // Change our name back | ||
1395 | llSetObjectName(OurName); | ||
1396 | } | ||
1397 | |||
1398 | changed(integer change) | ||
1399 | { | ||
1400 | if (change & CHANGED_INVENTORY) | ||
1401 | { | ||
1402 | // readSettings(); | ||
1403 | // showAccess(Owner); | ||
1404 | } | ||
1405 | if (change & CHANGED_TELEPORT) | ||
1406 | { | ||
1407 | // TODO - check if this was a requested TP and we are being carried. If so, restart the carry process. | ||
1408 | if (0 != llGetListLength(TPtDestination)) | ||
1409 | { | ||
1410 | // Check if we ended up in a landing spot instead of where we thought we where going. | ||
1411 | vector pos = getRange(Stalkee); | ||
1412 | float dist = llVecDist(pos, llGetPos()); | ||
1413 | if (dist > (2 *(RANGE + bb))) | ||
1414 | { | ||
1415 | s("Double TP to get closer."); | ||
1416 | osTeleportAgent(Owner, llList2String(TPtDestination, 1), llList2Vector(TPtDestination, 2), <1.0,1.0,1.0>); | ||
1417 | } | ||
1418 | TPtDestination = []; | ||
1419 | } | ||
1420 | } | ||
1421 | if (change & CHANGED_OWNER) | ||
1422 | llResetScript(); | ||
1423 | } | ||
1424 | } | ||
diff --git a/OhSillyThreatDetector.lsl b/OhSillyThreatDetector.lsl new file mode 100644 index 0000000..4f17ed6 --- /dev/null +++ b/OhSillyThreatDetector.lsl | |||
@@ -0,0 +1,59 @@ | |||
1 | |||
2 | // Detect when OpenSim reports a function was used that triggered a threat level warning. | ||
3 | // Coz their official method doesn't work, and often their threat levels are set way too high. | ||
4 | |||
5 | // This script will send a link message with number DEBUG_CHANNEL, message being the name of the naughty function, | ||
6 | // and key of the prim containing the naughty script. It's up to the naughty script to catch this link message | ||
7 | // and do something less naughty. | ||
8 | |||
9 | // Since it sniffs on DEBUG_CHANNEL messages, and a script can't hear any message sent by scripts in the same prim, | ||
10 | // you'll need to put this script in a prim without the naughty scripts you want to check. | ||
11 | |||
12 | default | ||
13 | { | ||
14 | state_entry() | ||
15 | { | ||
16 | // llSay(DEBUG_CHANNEL, "G'day, I'm " + llGetKey() + " part of " + llGetLinkKey(LINK_ROOT)); | ||
17 | llListen(DEBUG_CHANNEL, "", NULL_KEY, ""); | ||
18 | } | ||
19 | |||
20 | listen(integer channel, string name, key id, string message) | ||
21 | { | ||
22 | key root = llList2Key(llGetObjectDetails(id, [OBJECT_ROOT]), 0); | ||
23 | // name is the name of the prim, id is the UUID of the prim. | ||
24 | // Reports the name and UUID of the prim itself, not the object it's part of. | ||
25 | // Naturally can't detect debugs from our own prim, even if it's a different script. | ||
26 | // Works from another prim in the same object. | ||
27 | |||
28 | /* Threat level errors look like this (at least in OpenSim 0.8.2) - | ||
29 | OSSL Runtime Error: osSetStateEvents permission denied. Script creator is not in the list of users allowed to execute this function and prim owner also has no permission. | ||
30 | |||
31 | There is also (OpenSim 0.9 at least) - | ||
32 | Max Zephyr script | ||
33 | OSSL Runtime Error: permission denied. All OS functions are disabled.(script: oc_settings event: changed primID:487baa88-d6e5-420c-b18c-f47f5bbd7de4 at <127.9408, 128.0009, 25.75689>) | ||
34 | |||
35 | My script | ||
36 | OSSL Runtime Error: osSetSpeed permission denied. All OS functions are disabled.(script: 1AOor2 event: changed primID:c7db8d3c-ae33-44ce-a685-cf63a37f0cf5 at <123.933, 203.0375, 24.29473>) | ||
37 | |||
38 | From the source (NOTE: first version has no function name) - | ||
39 | ("{0} permission denied. All OS functions are disabled.") | ||
40 | ("{0} permission denied. All OS functions are disabled.", function) | ||
41 | ("{0} permission denied. Allowed threat level is {1} but function threat level is {2}.", function, | ||
42 | ("{0} permission denied. Script creator is not in the list of users allowed to execute this function and prim owner also has no permission.", function | ||
43 | ("{0} permission denied. Script permissions error.", function | ||
44 | |||
45 | OpenSim 0.9.0.1 doesn't include the "OSSL Runtime Error: " bit at the beginning anymore. | ||
46 | OpenSim 0.9.2 seems to not even send it to the DEBUG_CHANNEL for scripts anymore. | ||
47 | */ | ||
48 | if ((llGetLinkKey(LINK_ROOT) == root) && ("OSSL Runtime Error: " == llGetSubString(message, 0, 19))) | ||
49 | { | ||
50 | list e = llParseStringKeepNulls(llGetSubString(message, 20, -1), [" "], []); | ||
51 | //llOwnerSay(llList2String(e, 0)); | ||
52 | if (("permission" == llList2String(e, 1)) && ("denied" == llList2String(e, 2))) | ||
53 | { | ||
54 | string function = llList2String(e, 0); | ||
55 | llMessageLinked(LINK_SET, DEBUG_CHANNEL, function, id); | ||
56 | } | ||
57 | } | ||
58 | } | ||
59 | } | ||
diff --git a/onefang's leash holder.lsl b/onefang's leash holder.lsl new file mode 100644 index 0000000..6d7b360 --- /dev/null +++ b/onefang's leash holder.lsl | |||
@@ -0,0 +1,44 @@ | |||
1 | integer mychannel = -8888; | ||
2 | integer particle_chan = -270510; | ||
3 | string listenfor; | ||
4 | string response; | ||
5 | string detach; | ||
6 | |||
7 | key masterid; | ||
8 | key handle; | ||
9 | |||
10 | default | ||
11 | { | ||
12 | state_entry() | ||
13 | { | ||
14 | masterid = llGetOwner(); | ||
15 | handle = llGetKey(); | ||
16 | listenfor = (string)masterid + "handle"; | ||
17 | response = (string)masterid + "handle ok"; | ||
18 | detach = (string)masterid + "handle detached"; | ||
19 | llListen(mychannel, "", NULL_KEY, listenfor); | ||
20 | llSay(mychannel, response); | ||
21 | } | ||
22 | |||
23 | attach(key id) | ||
24 | { | ||
25 | if (NULL_KEY == id) | ||
26 | llSay(mychannel, detach); | ||
27 | } | ||
28 | |||
29 | listen(integer channel, string name, key id, string message) | ||
30 | { | ||
31 | llSay(mychannel, response); | ||
32 | llSay(particle_chan, handle + "#" + masterid); | ||
33 | } | ||
34 | |||
35 | on_rez(integer param) | ||
36 | { | ||
37 | llResetScript(); | ||
38 | } | ||
39 | |||
40 | touch_start(integer total_number) | ||
41 | { | ||
42 | llSay(particle_chan, handle + "#" + masterid); | ||
43 | } | ||
44 | } | ||