aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/NPC_menu.lsl
diff options
context:
space:
mode:
Diffstat (limited to 'NPC_menu.lsl')
-rw-r--r--NPC_menu.lsl481
1 files changed, 481 insertions, 0 deletions
diff --git a/NPC_menu.lsl b/NPC_menu.lsl
new file mode 100644
index 0000000..0b67c55
--- /dev/null
+++ b/NPC_menu.lsl
@@ -0,0 +1,481 @@
1// Onefang's NPC menu version 1.0.
2// Requires NPC tool, and onefang's utilities scripts.
3
4// Copyright (C) 2013 David Seikel (onefang rejected).
5//
6// Permission is hereby granted, free of charge, to any person obtaining a copy
7// of this software and associated documentation files (the "Software"), to
8// deal in the Software without restriction, including without limitation the
9// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10// sell copies of the Software, and to permit persons to whom the Software is
11// furnished to do so, subject to the following conditions:
12//
13// The above copyright notice and this permission notice shall be included in
14// all copies of the Software and its Copyright notices. In addition publicly
15// documented acknowledgment must be given that this software has been used if no
16// source code of this software is made available publicly. This includes
17// acknowledgments in either Copyright notices, Manuals, Publicity and Marketing
18// documents or any documentation provided with any product containing this
19// software. This License does not apply to any software that links to the
20// libraries provided by this software (statically or dynamically), but only to
21// the software provided.
22//
23// Please see the COPYING-PLAIN for a plain-english explanation of this notice
24// and it's intent.
25//
26// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
27// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
28// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
29// THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
30// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
31// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32
33list chatters = []; // npc key, relay channel.
34integer CHAT_KEY = 0;
35integer CHAT_CHANNEL = 1;
36integer CHAT_STRIDE = 2;
37
38list sensorRequests = []; // name to search for, type of search, range, command, user key, npc key, title for menu, agents flag, every flag
39integer sensorInFlight = FALSE;
40
41// NPC commands.
42string NPC_ANIMATE = "animate"; // NPC name, animation name
43string NPC_ATTENTION= "attention"; // NPC name.
44string NPC_CHANGE = "change"; // NPC name, NPC (notecard) name.
45string NPC_CLONE = "clone"; // Avatar's name.
46string NPC_COME = "come"; // NPC name.
47string NPC_COMPLETE = "complete"; // NPC name.
48string NPC_CREATE = "create"; // NPC (notecard) name, (optional) position vector.
49string NPC_DELETE = "delete"; // NPC name.
50string NPC_DISMISSED= "dismissed"; // NPC name.
51string NPC_FOLLOW = "follow"; // NPC name, (optional) distance (float or vector).
52string NPC_FLY = "fly"; // NPC name, position vector or object/agent name/key.
53string NPC_GO = "go"; // NPC name, position vector or object/agent name/key.
54string NPC_LAND = "land"; // NPC name, position vector or object/agent name/key.
55string NPC_LINK = "link"; // Link number, number, string, (optional) key
56string NPC_LISTEN = "listen"; // new command channel
57string NPC_LOCATE = "locate"; // NPC name
58string NPC_NUKE = "nuke"; // No arguments.
59string NPC_ROTATE = "rotate"; // NPC name, Z rotation in degrees.
60string NPC_SAY = "say"; // NPC name, thing to say, or relay channel.
61string NPC_SCRIPT = "script"; // Script notecard name.
62string NPC_SHOUT = "shout"; // NPC name, thing to shout, or relay channel.
63string NPC_SIT = "sit"; // NPC name, object to sit on.
64string NPC_SLEEP = "sleep"; // Seconds.
65string NPC_STALK = "stalk"; // NPC name, avatar name, (optional) distance (float or vector).
66string NPC_STAND = "stand"; // NPC name.
67string NPC_STOP = "stop"; // NPC name.
68string NPC_STOPANIM = "stopanim"; // NPC name, animation name
69string NPC_TOUCH = "touch"; // NPC name, object to touch.
70string NPC_WHISPER = "whisper"; // NPC name, thing to whisper, or relay channel.
71
72string NPC_MAIN = "Main"; // TODO - does not really matter what this is I think, so use it as the registered menu button.
73string NPC_PICK = "Pick";
74string NPC_NPC = "NPC";
75string NPC_RELAY = "Relay";
76
77string NPC_NPC_EXT = ".avatar";
78string NPC_SCRIPT_EXT = ".npc";
79string NPC_BACKUP_CARD = "Restore";
80string NPC_RECORD_CARD = "Recorded";
81
82integer NPC_RECORD = -100;
83integer NPC_RECORD_STOP = -101;
84integer NPC_NEW_NPC = -102;
85integer NPC_ADD_CHATTER = -103;
86integer NPC_DEL_CHATTER = -104;
87
88
89// Stuff for onefangs common utilities.
90key NPCscriptKey;
91key scriptKey;
92
93string UTILITIES_SCRIPT_NAME = "onefang's utilities";
94string LIST_SEP = "$!#"; // Used to seperate lists when sending them as strings.
95integer UTILITIES_RESET = -1;
96integer UTILITIES_RESET_DONE = -2;
97integer UTILITIES_READ = -3;
98integer UTILITIES_READ_DONE = -4;
99integer UTILITIES_SUBSTITUTE = -5;
100integer UTILITIES_SUBSTITUTE_DONE = -6;
101integer UTILITIES_NEXT_WORD = -7;
102integer UTILITIES_NEXT_WORD_DONE = -8;
103integer UTILITIES_PAYMENT_MADE = -9;
104integer UTILITIES_PAYMENT_MADE_DONE = -10;
105integer UTILITIES_MENU = -11;
106integer UTILITIES_MENU_DONE = -12;
107integer UTILITIES_WARP = -13;
108integer UTILITIES_WARP_DONE = -14;
109integer UTILITIES_AVATAR_KEY = -15;
110integer UTILITIES_AVATAR_KEY_DONE = -16;
111integer UTILITIES_CHAT = -17;
112integer UTILITIES_CHAT_DONE = -18;
113integer UTILITIES_CHAT_FAKE = -19;
114integer UTILITIES_CHAT_FAKE_DONE = -20;
115
116
117showMenu(key id, string type, string title, list buttons, string extra)
118{
119 llMessageLinked(LINK_SET, UTILITIES_MENU, llDumpList2String([id, type, title] + buttons, LIST_SEP), (key) scriptKey + "|" + extra);
120}
121
122NPCmenu(key id, key npc)
123{
124 string name = llKey2Name(npc);
125
126 if ((0 == osIsUUID(npc)) || (npc == NULL_KEY) || ("" == name))
127 {
128 showMenu(id, (string) INVENTORY_NONE, "Main NPC menu.",
129 [
130 "clone avatar..", "create NPC..", "run script..",
131 "nearby NPCs..", "local NPCs..", "NPCs in sim..",
132 "backup NPCs", "nuke NPCs", "restore NPCs",
133 "start recording", "stop recording"
134 ], NPC_MAIN);
135 }
136 else
137 {
138 integer found = llListFindList(chatters, [npc]);
139 string chat = "";
140
141 if (0 <= found)
142 chat = "\nUse /" + llList2String(chatters, found + CHAT_CHANNEL) + " to relay chat to " + llKey2Name(npc);
143 showMenu(id, (string) INVENTORY_NONE, "Play with " + llKey2Name(npc) + " :" + chat,
144 [
145 "change..", "chat relay..", "come here",
146 "go to..", "fly to..", "land at..",
147 "follow me", "stalk them..", "stop moving",
148 "sit..", NPC_STAND,
149 NPC_LOCATE, "take controls", "touch..",
150 "start animation..", "stop animation..",
151 NPC_DELETE
152 ], NPC_NPC + "|" + npc);
153 }
154}
155
156startSensor(key user, key npc, string name, integer type, float range,
157 string command, string title, integer agents, integer everything)
158{
159 if ((AGENT == type) && (range > 9999.9999)) // Assumes we are only looking for NPCs.
160 {
161 integer i;
162 list menu = [];
163 list avatars = osGetAvatarList(); // Strided list, UUID, position, name.
164 integer length = llGetListLength(avatars);
165
166 for (i = 0; i < length; i++)
167 {
168 key this = (key) llList2String(avatars, i * 3);
169
170 if (osIsNpc(this))
171 menu += [llKey2Name(this) + "|" + (string) this];
172 }
173 if (llGetListLength(menu) > 0)
174 showMenu(user, (string) INVENTORY_NONE, "Choose NPC :", menu, NPC_PICK);
175 }
176 else
177 {
178 sensorRequests += [name, type, range, command, user, npc, title, agents, everything];
179 nextSensor();
180 }
181}
182
183nextSensor()
184{
185 if (sensorInFlight)
186 return;
187 if (0 < llGetListLength(sensorRequests))
188 {
189 string name = llList2String(sensorRequests, 0);
190 integer type = llList2Integer(sensorRequests, 1);
191 float range = llList2Float(sensorRequests, 2);
192 string command = llList2String(sensorRequests, 3);
193 key user = llList2String(sensorRequests, 4);
194 key npc = llList2String(sensorRequests, 5);
195 string title = llList2String(sensorRequests, 6);
196 integer agents = llList2Integer(sensorRequests, 7);
197 integer every = llList2Integer(sensorRequests, 8);
198
199 sensorInFlight = TRUE;
200 llSensor(name, "", type, range, TWO_PI);
201 }
202}
203
204sendCommand(key user, string command)
205{
206 // Work around the other script getting reset later, with a fresh key
207 NPCscriptKey = llGetInventoryKey("NPC tool");
208 llMessageLinked(LINK_SET, UTILITIES_CHAT_FAKE, llDumpList2String([0, llKey2Name(user), user, command], LIST_SEP), NPCscriptKey);
209}
210
211init()
212{
213 scriptKey = llGetInventoryKey(llGetScriptName());
214 // Register our interest in touch menus.
215 llMessageLinked(LINK_SET, UTILITIES_MENU, llDumpList2String([NULL_KEY, INVENTORY_NONE, "NPC tool|" + llGetScriptName()], LIST_SEP), (key) scriptKey);
216}
217
218
219default
220{
221 state_entry()
222 {
223 init();
224 }
225
226 on_rez(integer param)
227 {
228 init();
229 }
230
231 attach(key attached)
232 {
233 init();
234 }
235
236 link_message(integer sender_num, integer num, string value, key id)
237 {
238 list keys = llParseStringKeepNulls((string) id, ["|"], []);
239 string extra = llList2String(keys, 1);
240
241 id = (key) llList2String(keys, 0);
242 // Work around the other script getting reset later, with a fresh key
243 NPCscriptKey = llGetInventoryKey("NPC tool");
244//llSay(0, "id = " + (string) id + " extra = " + extra + " VALUE " + value);
245 if (UTILITIES_RESET_DONE == num)
246 init();
247 else if ((NPC_NEW_NPC == num) && (NPCscriptKey == id))
248 {
249 list args = llParseString2List(value, ["|"], []);
250
251 NPCmenu(llList2String(args, 0), llList2String(args, 1));
252 }
253 else if ((NPC_ADD_CHATTER == num) && (NPCscriptKey == id))
254 {
255 list args = llParseString2List(value, ["|"], []);
256 key npc = llList2String(args, 0);
257 integer channel = llList2Integer(args, 1);
258 integer found = llListFindList(chatters, [npc]);
259
260 if (0 <= found)
261 chatters = llDeleteSubList(chatters, found, found + CHAT_STRIDE - 1);
262 chatters += [npc, channel];
263 }
264 else if ((NPC_DEL_CHATTER == num) && (NPCscriptKey == id))
265 {
266 integer found = llListFindList(chatters, [value]);
267
268 if (0 <= found)
269 chatters = llDeleteSubList(chatters, found, found + CHAT_STRIDE - 1);
270 }
271 else if ((UTILITIES_CHAT_DONE == num) && (NPCscriptKey == id))
272 {
273 // incoming channel | incoming name | incoming key | incoming message | prefix | command | list of arguments | rest of message
274 list result = llParseStringKeepNulls(value, [LIST_SEP], []);
275 //integer inchannel = llList2Integer(result, 0);
276 //string inName = llList2String (result, 1);
277 key user = llList2Key (result, 2);
278 //string inMessage = llList2String (result, 3);
279 //string prefix = llList2String (result, 4);
280 string command = llList2String (result, 5);
281 list arguments = llList2List (result, 6, -1); // Includes "rest of message" as the last one.
282
283 if (NPC_NUKE == command)
284 {
285 chatters = [];
286 sensorRequests = [];
287 sensorInFlight = FALSE;
288 }
289 }
290 else if ((UTILITIES_MENU_DONE == num) && (scriptKey == id)) // Big menu button pushed
291 {
292 list input = llParseStringKeepNulls(value, [LIST_SEP], []);
293 key user = (key) llList2String(input, 0);
294 string selection = llList2String(input, 1);
295 list parts = llParseString2List(selection, ["|"], []);
296 key uuid = (key) llList2String(parts, 1);
297 key npc = (key) llList2String(keys, 2);
298 list details = llGetObjectDetails(uuid, [OBJECT_POS]);
299 string menu = extra;
300//llSay(0, extra + " MENU " + value + " KEYS " + llDumpList2String(keys, " "));
301
302 // See if it was our top level menu requested via touch registration.
303 if ("" == selection)
304 {
305 NPCmenu(user, NULL_KEY);
306 return;
307 }
308
309 // Figure out what the user picked.
310 if ((NPC_MAIN == extra) || (NPC_NPC == extra))
311 menu = selection;
312 // Make sure main menu items don't return to an NPC menu, by setting npc to null.
313 if (NPC_MAIN == extra)
314 npc = NULL_KEY;
315
316 // Check if the NPC still exists.
317 if (NPC_NPC == extra)
318 {
319 list npcDetails = llGetObjectDetails(npc, [OBJECT_POS]);
320
321 if (llGetListLength(npcDetails) == 0)
322 {
323 // Bail out if the NPC went AWOL.
324 npc = NULL_KEY;
325 selection = "Exit";
326 }
327 }
328
329 if ("Exit" == selection)
330 {
331 if (NPC_NPC == extra) npc = NULL_KEY;
332 if (NPC_MAIN == extra) return;
333 }
334 // Commands.
335 else if (NPC_ANIMATE == menu) sendCommand(user, NPC_ANIMATE + " " + npc + " " + selection);
336 else if (NPC_CHANGE == menu) sendCommand(user, NPC_CHANGE + " " + npc + " " + selection);
337 else if (NPC_CLONE == menu) sendCommand(user, NPC_CLONE + " " + uuid);
338 else if (NPC_FLY == menu) sendCommand(user, NPC_FLY + " " + npc + " " + llList2String(details, 0));
339 else if (NPC_GO == menu) sendCommand(user, NPC_GO + " " + npc + " " + llList2String(details, 0));
340 else if (NPC_LAND == menu) sendCommand(user, NPC_LAND + " " + npc + " " + llList2String(details, 0));
341 else if (NPC_LOCATE == menu) sendCommand(user, NPC_LOCATE + " " + npc);
342 else if (NPC_RELAY == menu) sendCommand(user, NPC_SAY + " " + npc + " " + selection);
343 else if (NPC_SCRIPT == menu) sendCommand(user, NPC_SCRIPT + " " + selection);
344 else if (NPC_SIT == menu) sendCommand(user, NPC_SIT + " " + npc + " " + uuid);
345 else if (NPC_STALK == menu) sendCommand(user, NPC_STALK + " " + npc + " " + uuid);
346 else if (NPC_STAND == menu) sendCommand(user, NPC_STAND + " " + npc);
347 else if (NPC_STOPANIM == menu) sendCommand(user, NPC_STOPANIM + " " + npc + " " + selection);
348 else if (NPC_TOUCH == menu) sendCommand(user, NPC_TOUCH + " " + npc + " " + uuid);
349 else if ("come here" == menu) sendCommand(user, NPC_COME + " " + npc);
350 else if ("follow me" == menu) sendCommand(user, NPC_FOLLOW + " " + npc);
351 else if ("nuke NPCs" == menu) sendCommand(user, NPC_NUKE);
352 else if ("restore NPCs" == menu) sendCommand(user, NPC_SCRIPT + " " + NPC_BACKUP_CARD + NPC_SCRIPT_EXT);
353 else if ("stop moving" == menu) sendCommand(user, NPC_STOP + " " + npc);
354
355 // Menus.
356 else if ("change.." == menu) showMenu(user, ((string) INVENTORY_NOTECARD) + "|.+\\" + NPC_NPC_EXT,
357 "Choose an NPC to change to :", [], NPC_CHANGE + "|" + npc);
358 else if ("chat relay.." == menu) showMenu(user, (string) INVENTORY_NONE,
359 "Choose a channel to relay chat from :", ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11"], NPC_RELAY + "|" + npc);
360 else if ("create NPC.." == menu) showMenu(user, ((string) INVENTORY_NOTECARD) + "|.+\\" + NPC_NPC_EXT,
361 "Choose an NPC to create :", [], NPC_CREATE);
362 else if ("run script.." == menu) showMenu(user, ((string) INVENTORY_NOTECARD) + "|.+\\" + NPC_SCRIPT_EXT,
363 "Choose a script to run :", [], NPC_SCRIPT);
364 else if ("start animation.." == menu) showMenu(user, (string) INVENTORY_ANIMATION,
365 "Choose an animation to start :", [], NPC_ANIMATE + "|" + npc);
366 else if ("stop animation.." == menu) showMenu(user, (string) INVENTORY_ANIMATION,
367 "Choose an animation to stop :", [], NPC_STOPANIM + "|" + npc);
368
369 // Sensor menus.
370 else if ("clone avatar.." == menu) startSensor(user, "", "", AGENT, 256.0, NPC_CLONE, "Choose person to clone :", TRUE, TRUE);
371 else if ("fly to.." == menu) startSensor(user, npc, "", ACTIVE | PASSIVE, 1024.0, NPC_FLY, "Choose thing to fly to :", FALSE, TRUE);
372 else if ("go to.." == menu) startSensor(user, npc, "", ACTIVE | PASSIVE, 1024.0, NPC_GO, "Choose thing to go to :", FALSE, TRUE);
373 else if ("land at.." == menu) startSensor(user, npc, "", ACTIVE | PASSIVE, 1024.0, NPC_LAND, "Choose thing to land at :", FALSE, TRUE);
374 else if ("local NPCs.." == menu) startSensor(user, "", "", AGENT, 256.0, NPC_PICK, "Choose NPC :", FALSE, FALSE);
375 else if ("nearby NPCs.." == menu) startSensor(user, "", "", AGENT, 20.0, NPC_PICK, "Choose NPC :", FALSE, FALSE);
376 else if ("NPCs in sim.." == menu) startSensor(user, "", "", AGENT, 16384.0, NPC_PICK, "Choose NPC :", FALSE, FALSE);
377 else if ("sit.." == menu) startSensor(user, npc, "", ACTIVE | SCRIPTED, 1024.0, NPC_SIT, "Choose thing to sit on :", FALSE, TRUE);
378 else if ("stalk them.." == menu) startSensor(user, npc, "", AGENT, 1024.0, NPC_STALK, "Choose person to stalk :", TRUE, TRUE);
379 else if ("touch.." == menu) startSensor(user, npc, "", ACTIVE | SCRIPTED, 1024.0, NPC_TOUCH, "Choose thing to touch :", FALSE, TRUE);
380
381 // Misc.
382 else if (NPC_PICK == menu) npc = uuid;
383 else if ("start recording" == menu) llMessageLinked(LINK_SET, NPC_RECORD, "", NPCscriptKey);
384 else if ("stop recording" == menu) llMessageLinked(LINK_SET, NPC_RECORD_STOP, "", NPCscriptKey);
385 else if (NPC_CREATE == menu)
386 {
387 sendCommand(user, NPC_CREATE + " " + selection);
388 // Avoid the NPCmenu() below. An odd one out, coz the NPC wont exist yet, but we want their menu when they do exist.
389 return;
390 }
391 else if (NPC_DELETE == menu)
392 {
393 sendCommand(user, NPC_DELETE + " " + npc);
394 // An odd one out, the NPC wont exist, so return to the main menu.
395 npc = NULL_KEY;
396 }
397 else if ("backup NPCs" == menu)
398 {
399 list avatars = osGetAvatarList(); // Strided list, UUID, position, name.
400 list delete = [];
401 list backup = [];
402 integer length = llGetListLength(avatars);
403 integer i;
404
405 llRemoveInventory(NPC_BACKUP_CARD + NPC_SCRIPT_EXT);
406 for (i = 0; i < length; i++)
407 {
408 key this = (key) llList2String(avatars, i * 3);
409
410 if (osIsNpc(this))
411 {
412 string aName = llKey2Name(this);
413
414 osAgentSaveAppearance(this, aName + NPC_NPC_EXT);
415 delete += [NPC_DELETE + " " + aName];
416 backup += [NPC_CREATE + " " + aName + " " + llList2String(avatars, (i * 3) + 1)];
417 }
418 }
419 osMakeNotecard(NPC_BACKUP_CARD + NPC_SCRIPT_EXT,
420 ["script " + NPC_BACKUP_CARD + ".before" + NPC_SCRIPT_EXT]
421 + delete + backup
422 + ["script " + NPC_BACKUP_CARD + ".after" + NPC_SCRIPT_EXT]);
423 }
424
425 // If the menu name ends in "..", then it's expected that we are waiting on another menu, so don't show one now.
426 if (0 == osRegexIsMatch(menu, ".+\\.\\.$"))
427 NPCmenu(user, npc);
428 } // End of menu block.
429 }
430
431 no_sensor()
432 {
433 sensorInFlight = FALSE;
434 if (llGetListLength(sensorRequests))
435 {
436 sensorRequests = llDeleteSubList(sensorRequests, 0, 8);
437 nextSensor();
438 }
439 }
440
441 sensor(integer numberDetected)
442 {
443 sensorInFlight = FALSE;
444 if (llGetListLength(sensorRequests))
445 {
446 string name = llList2String(sensorRequests, 0);
447 integer type = llList2Integer(sensorRequests, 1);
448 float range = llList2Float(sensorRequests, 2);
449 string command = llList2String(sensorRequests, 3);
450 key user = llList2String(sensorRequests, 4);
451 key npc = llList2String(sensorRequests, 5);
452 string title = llList2String(sensorRequests, 6);
453 integer agents = llList2Integer(sensorRequests, 7);
454 integer every = llList2Integer(sensorRequests, 8);
455
456 sensorRequests = llDeleteSubList(sensorRequests, 0, 8);
457 if ("" == title)
458 sendCommand(user, command + " " + npc + " " + llDetectedKey(0));
459 else
460 {
461 integer i;
462 list menu = [];
463
464 if (agents)
465 menu += ["you|" + (string) user];
466 for (i = 0; i < numberDetected; i++)
467 {
468 key this = llDetectedKey(i);
469
470 if (!(agents && (this == user)))
471 if (every || osIsNpc(this))
472 menu += [llDetectedName(i) + "|" + (string) this];
473 }
474 if (llGetListLength(menu) > 0)
475 showMenu(user, (string) INVENTORY_NONE, title, menu, command + "|" + npc);
476 }
477 nextSensor();
478 }
479 }
480
481} \ No newline at end of file