aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/NPC_menu.lsl
blob: a7ccd15451f269178759ee4c123e03f93f4189c7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
// Onefang's NPC menu version 1.0.
// Requires NPC tool, and onefang's utilities scripts.

// Copyright (C) 2013 David Seikel (onefang rejected).
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies of the Software and its Copyright notices.  In addition publicly
// documented acknowledgment must be given that this software has been used if no
// source code of this software is made available publicly.  This includes
// acknowledgments in either Copyright notices, Manuals, Publicity and Marketing
// documents or any documentation provided with any product containing this
// software.  This License does not apply to any software that links to the
// libraries provided by this software (statically or dynamically), but only to
// the software provided.
//
// Please see the COPYING-PLAIN for a plain-english explanation of this notice
// and it's intent.
//
// The software is provided "as is", without warranty of any kind, express or
// implied, including but not limited to the warranties of merchantability,
// fitness for a particular purpose and noninfringement.  In no event shall
// the authors be liable for any claim, damages or other liability, whether
// in an action of contract, tort or otherwise, arising from, out of or in
// connection with the software or the use or other dealings in the software.

list chatters = [];   // npc key, relay channel.
integer CHAT_KEY     = 0;
integer CHAT_CHANNEL = 1;
integer CHAT_STRIDE  = 2;

list  sensorRequests = [];  // name to search for, type of search, range, command, user key, npc key, title for menu, agents flag, every flag
integer sensorInFlight = FALSE;

// NPC commands.
string NPC_ANIMATE  = "animate";    // NPC name, animation name
string NPC_ATTENTION= "attention";  // NPC name.
string NPC_CHANGE   = "change";     // NPC name, NPC (notecard) name.
string NPC_CLONE    = "clone";      // Avatar's name.
string NPC_COME     = "come";       // NPC name.
string NPC_COMPLETE = "complete";   // NPC name.
string NPC_CREATE   = "create";     // NPC (notecard) name, (optional) position vector.
string NPC_DELETE   = "delete";     // NPC name.
string NPC_DISMISSED= "dismissed";  // NPC name.
string NPC_FOLLOW   = "follow";     // NPC name, (optional) distance (float or vector).
string NPC_FLY      = "fly";        // NPC name, position vector or object/agent name/key.
string NPC_GO       = "go";         // NPC name, position vector or object/agent name/key.
string NPC_LAND     = "land";       // NPC name, position vector or object/agent name/key.
string NPC_LINK     = "link";       // Link number, number, string, (optional) key
string NPC_LISTEN   = "listen";     // new command channel
string NPC_LOCATE   = "locate";     // NPC name
string NPC_NUKE     = "nuke";       // No arguments.
string NPC_ROTATE   = "rotate";     // NPC name, Z rotation in degrees.
string NPC_SAY      = "say";        // NPC name, thing to say, or relay channel.
string NPC_SCRIPT   = "script";     // Script notecard name.
string NPC_SHOUT    = "shout";      // NPC name, thing to shout, or relay channel.
string NPC_SIT      = "sit";        // NPC name, object to sit on.
string NPC_SLEEP    = "sleep";      // Seconds.
string NPC_STALK    = "stalk";      // NPC name, avatar name, (optional) distance (float or vector).
string NPC_STAND    = "stand";      // NPC name.
string NPC_STOP     = "stop";       // NPC name.
string NPC_STOPANIM = "stopanim";   // NPC name, animation name
string NPC_TOUCH    = "touch";      // NPC name, object to touch.
string NPC_WHISPER  = "whisper";    // NPC name, thing to whisper, or relay channel.

string NPC_MAIN     = "Main";   // TODO - does not really matter what this is I think, so use it as the registered menu button.
string NPC_PICK     = "Pick";
string NPC_NPC      = "NPC";
string NPC_RELAY    = "Relay";

string NPC_NPC_EXT = ".avatar";
string NPC_SCRIPT_EXT = ".npc";
string NPC_BACKUP_CARD = "Restore";
string NPC_RECORD_CARD = "Recorded";

integer NPC_RECORD      = -100;
integer NPC_RECORD_STOP = -101;
integer NPC_NEW_NPC     = -102;
integer NPC_ADD_CHATTER = -103;
integer NPC_DEL_CHATTER = -104;


// Stuff for onefangs common utilities.
key NPCscriptKey;
key     scriptKey;

string  UTILITIES_SCRIPT_NAME       = "onefang's utilities";
string  LIST_SEP                    = "$!#";           // Used to seperate lists when sending them as strings.
integer UTILITIES_RESET             = -1;
integer UTILITIES_RESET_DONE        = -2;
integer UTILITIES_READ              = -3;
integer UTILITIES_READ_DONE         = -4;
integer UTILITIES_SUBSTITUTE        = -5;
integer UTILITIES_SUBSTITUTE_DONE   = -6;
integer UTILITIES_NEXT_WORD         = -7;
integer UTILITIES_NEXT_WORD_DONE    = -8;
integer UTILITIES_PAYMENT_MADE      = -9;
integer UTILITIES_PAYMENT_MADE_DONE = -10;
integer UTILITIES_MENU              = -11;
integer UTILITIES_MENU_DONE         = -12;
integer UTILITIES_WARP              = -13;
integer UTILITIES_WARP_DONE         = -14;
integer UTILITIES_AVATAR_KEY        = -15;
integer UTILITIES_AVATAR_KEY_DONE   = -16;
integer UTILITIES_CHAT              = -17;
integer UTILITIES_CHAT_DONE         = -18;
integer UTILITIES_CHAT_FAKE         = -19;
integer UTILITIES_CHAT_FAKE_DONE    = -20;


showMenu(key id, string type, string title, list buttons, string extra)
{
    llMessageLinked(LINK_SET, UTILITIES_MENU, llDumpList2String([id, type, title] + buttons, LIST_SEP), (key) scriptKey + "|" + extra);
}

NPCmenu(key id, key npc)
{
    string name = llKey2Name(npc);

    if ((0 == osIsUUID(npc)) || (npc == NULL_KEY) || ("" == name))
    {
        showMenu(id, (string) INVENTORY_NONE, "Main NPC menu.", 
            [
            "clone avatar..", "create NPC..", "run script..",
            "nearby NPCs..", "local NPCs..", "NPCs in sim..",
            "backup NPCs", "nuke NPCs", "restore NPCs",
            "start recording", "stop recording"
            ], NPC_MAIN);
    }
    else
    {
        integer found = llListFindList(chatters, [npc]);
        string chat = "";

        if (0 <= found)
            chat = "\nUse /" + llList2String(chatters, found + CHAT_CHANNEL) + " to relay chat to " + llKey2Name(npc);
        showMenu(id, (string) INVENTORY_NONE, "Play with " + llKey2Name(npc) + " :" + chat,
            [
            "change..", "chat relay..", "come here",
            "go to..", "fly to..", "land at..",
            "follow me", "stalk them..", "stop moving",
            "sit..", NPC_STAND,
            NPC_LOCATE, "take controls", "touch..",
            "start animation..", "stop animation..",
            NPC_DELETE
            ], NPC_NPC + "|" + npc);
    }
}

startSensor(key user, key npc, string name, integer type, float range, 
    string command, string title, integer agents, integer everything)
{
    if ((AGENT == type) && (range > 9999.9999))   // Assumes we are only looking for NPCs.
    {
        integer i;
        list menu = [];
        list avatars = osGetAvatarList();   // Strided list, UUID, position, name.
        integer length = llGetListLength(avatars);

        for (i = 0; i < length; i++)
        {
            key this = (key) llList2String(avatars, i * 3);

            if (osIsNpc(this))
                menu += [llKey2Name(this) + "|" + (string) this];
        }
        if (llGetListLength(menu) > 0)
            showMenu(user, (string) INVENTORY_NONE, "Choose NPC :", menu, NPC_PICK);
    }
    else
    {
        sensorRequests += [name, type, range, command, user, npc, title, agents, everything];
        nextSensor();
    }
}

nextSensor()
{
    if (sensorInFlight)
        return;
    if (0 < llGetListLength(sensorRequests))
    {
        string  name    = llList2String(sensorRequests, 0);
        integer type    = llList2Integer(sensorRequests, 1);
        float   range   = llList2Float(sensorRequests, 2);
        string  command = llList2String(sensorRequests, 3);
        key     user    = llList2String(sensorRequests, 4);
        key     npc     = llList2String(sensorRequests, 5);
        string  title   = llList2String(sensorRequests, 6);
        integer agents  = llList2Integer(sensorRequests, 7);
        integer every   = llList2Integer(sensorRequests, 8);

        sensorInFlight = TRUE;
        llSensor(name, "", type, range, TWO_PI);
    }
}

sendCommand(key user, string command)
{
    // Work around the other script getting reset later, with a fresh key
    NPCscriptKey = llGetInventoryKey("NPC tool");
    llMessageLinked(LINK_SET, UTILITIES_CHAT_FAKE, llDumpList2String([0, llKey2Name(user), user, command], LIST_SEP), NPCscriptKey);
}

init()
{
    scriptKey = llGetInventoryKey(llGetScriptName());
    // Register our interest in touch menus.
    llMessageLinked(LINK_SET, UTILITIES_MENU, llDumpList2String([NULL_KEY, INVENTORY_NONE, "NPC tool|" + llGetScriptName()], LIST_SEP), (key) scriptKey);
}


default
{
    state_entry()
    {
        init();
    }

    on_rez(integer param)
    {
        init();
    }

    attach(key attached)
    {
        init();
    }

    link_message(integer sender_num, integer num, string value, key id)
    {
        list keys = llParseStringKeepNulls((string) id, ["|"], []);
        string extra = llList2String(keys, 1);

        id = (key) llList2String(keys, 0);
        // Work around the other script getting reset later, with a fresh key
        NPCscriptKey = llGetInventoryKey("NPC tool");
//llSay(0, "id = " + (string) id + " extra = " + extra + " VALUE " + value);
        if (UTILITIES_RESET_DONE == num)
            init();
        else if ((NPC_NEW_NPC == num) && (NPCscriptKey == id))
        {
            list args = llParseString2List(value, ["|"], []);

            NPCmenu(llList2String(args, 0), llList2String(args, 1));
        }
        else if ((NPC_ADD_CHATTER == num) && (NPCscriptKey == id))
        {
            list args = llParseString2List(value, ["|"], []);
            key npc = llList2String(args, 0);
            integer channel = llList2Integer(args, 1);
            integer found = llListFindList(chatters, [npc]);

            if (0 <= found)
                chatters = llDeleteSubList(chatters, found, found + CHAT_STRIDE - 1);
            chatters += [npc, channel];
        }
        else if ((NPC_DEL_CHATTER == num) && (NPCscriptKey == id))
        {
            integer found = llListFindList(chatters, [value]);

            if (0 <= found)
                chatters = llDeleteSubList(chatters, found, found + CHAT_STRIDE - 1);
        }
        else if ((UTILITIES_CHAT_DONE == num) && (NPCscriptKey == id))
        {
            // incoming channel | incoming name | incoming key | incoming message | prefix | command | list of arguments | rest of message
            list    result        = llParseStringKeepNulls(value, [LIST_SEP], []);
            //integer inchannel    = llList2Integer(result, 0);
            //string  inName          = llList2String (result, 1);
            key     user        = llList2Key    (result, 2);
            //string  inMessage    = llList2String (result, 3);
            //string  prefix          = llList2String (result, 4);
            string  command        = llList2String (result, 5);
            list    arguments    = llList2List   (result, 6, -1);   // Includes "rest of message" as the last one.

            if (NPC_NUKE == command)
            {
                chatters = [];
                sensorRequests = [];
                sensorInFlight = FALSE;
            }
        }
        else if ((UTILITIES_MENU_DONE == num) && (scriptKey == id))    // Big menu button pushed
        {
            list    input = llParseStringKeepNulls(value, [LIST_SEP], []);
            key     user = (key) llList2String(input, 0);
            string  selection =  llList2String(input, 1);
            list    parts = llParseString2List(selection, ["|"], []);
            key     uuid = (key) llList2String(parts, 1);
            key     npc = (key) llList2String(keys, 2);
            list    details = llGetObjectDetails(uuid, [OBJECT_POS]);
            string  menu = extra;
//llSay(0, extra + " MENU " + value + "  KEYS " + llDumpList2String(keys, " "));

            // See if it was our top level menu requested via touch registration.
            if ("" == selection)
            {
                NPCmenu(user, NULL_KEY);
                return;
            }

            // Figure out what the user picked.
            if ((NPC_MAIN == extra) || (NPC_NPC == extra))
                menu = selection;
            // Make sure main menu items don't return to an NPC menu, by setting npc to null.
            if (NPC_MAIN == extra)
                npc = NULL_KEY;

            // Check if the NPC still exists.
            if (NPC_NPC == extra)
            {
                list npcDetails = llGetObjectDetails(npc, [OBJECT_POS]);

                if (llGetListLength(npcDetails) == 0)
                {
                    // Bail out if the NPC went AWOL.
                    npc = NULL_KEY;
                    selection = "Exit";
                }
            }

            if ("Exit" == selection)
            {
                if (NPC_NPC  == extra)  npc = NULL_KEY;
                if (NPC_MAIN == extra)  return;
            }
            // Commands.
            else if (NPC_ANIMATE       == menu)  sendCommand(user, NPC_ANIMATE  + " " + npc + " " + selection);
            else if (NPC_CHANGE        == menu)  sendCommand(user, NPC_CHANGE   + " " + npc + " " + selection);
            else if (NPC_CLONE         == menu)  sendCommand(user, NPC_CLONE    + " " + uuid);
            else if (NPC_FLY           == menu)  sendCommand(user, NPC_FLY      + " " + npc + " " + llList2String(details, 0));
            else if (NPC_GO            == menu)  sendCommand(user, NPC_GO       + " " + npc + " " + llList2String(details, 0));
            else if (NPC_LAND          == menu)  sendCommand(user, NPC_LAND     + " " + npc + " " + llList2String(details, 0));
            else if (NPC_LOCATE        == menu)  sendCommand(user, NPC_LOCATE   + " " + npc);
            else if (NPC_RELAY         == menu)  sendCommand(user, NPC_SAY      + " " + npc + " " + selection);
            else if (NPC_SCRIPT        == menu)  sendCommand(user, NPC_SCRIPT   + " " + selection);
            else if (NPC_SIT           == menu)  sendCommand(user, NPC_SIT      + " " + npc + " " + uuid);
            else if (NPC_STALK         == menu)  sendCommand(user, NPC_STALK    + " " + npc + " " + uuid);
            else if (NPC_STAND         == menu)  sendCommand(user, NPC_STAND    + " " + npc);
            else if (NPC_STOPANIM      == menu)  sendCommand(user, NPC_STOPANIM + " " + npc + " " + selection);
            else if (NPC_TOUCH         == menu)  sendCommand(user, NPC_TOUCH    + " " + npc + " " + uuid);
            else if ("come here"       == menu)  sendCommand(user, NPC_COME     + " " + npc);
            else if ("follow me"       == menu)  sendCommand(user, NPC_FOLLOW   + " " + npc);
            else if ("nuke NPCs"       == menu)  sendCommand(user, NPC_NUKE);
            else if ("restore NPCs"    == menu)  sendCommand(user, NPC_SCRIPT   + " " + NPC_BACKUP_CARD + NPC_SCRIPT_EXT);
            else if ("stop moving"     == menu)  sendCommand(user, NPC_STOP     + " " + npc);

            // Menus.
            else if ("change.."          == menu)  showMenu(user, ((string) INVENTORY_NOTECARD) + "|.+\\" + NPC_NPC_EXT,
                                                        "Choose an NPC to change to :",   [], NPC_CHANGE + "|" + npc);
            else if ("chat relay.."      == menu)  showMenu(user,  (string) INVENTORY_NONE,
                                                        "Choose a channel to relay chat from :", ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11"], NPC_RELAY + "|" + npc);
            else if ("create NPC.."      == menu)  showMenu(user, ((string) INVENTORY_NOTECARD) + "|.+\\" + NPC_NPC_EXT,
                                                        "Choose an NPC to create :",      [], NPC_CREATE);
            else if ("run script.."      == menu)  showMenu(user, ((string) INVENTORY_NOTECARD) + "|.+\\" + NPC_SCRIPT_EXT,
                                                        "Choose a script to run :",       [], NPC_SCRIPT);
            else if ("start animation.." == menu)  showMenu(user,  (string) INVENTORY_ANIMATION,
                                                        "Choose an animation to start :", [], NPC_ANIMATE  + "|" + npc);
            else if ("stop animation.."  == menu)  showMenu(user,  (string) INVENTORY_ANIMATION,
                                                        "Choose an animation to stop :",  [], NPC_STOPANIM + "|" + npc);

            // Sensor menus.
            else if ("clone avatar.."  == menu)  startSensor(user, "",  "", AGENT,              256.0, NPC_CLONE,  "Choose person to clone :",  TRUE,  TRUE);
            else if ("fly to.."        == menu)  startSensor(user, npc, "", ACTIVE | PASSIVE,  1024.0, NPC_FLY,    "Choose thing to fly to :",  FALSE, TRUE);
            else if ("go to.."         == menu)  startSensor(user, npc, "", ACTIVE | PASSIVE,  1024.0, NPC_GO,     "Choose thing to go to :",   FALSE, TRUE);
            else if ("land at.."       == menu)  startSensor(user, npc, "", ACTIVE | PASSIVE,  1024.0, NPC_LAND,   "Choose thing to land at :", FALSE, TRUE);
            else if ("local NPCs.."    == menu)  startSensor(user, "",  "", AGENT,              256.0, NPC_PICK,   "Choose NPC :",              FALSE, FALSE);
            else if ("nearby NPCs.."   == menu)  startSensor(user, "",  "", AGENT,               20.0, NPC_PICK,   "Choose NPC :",              FALSE, FALSE);
            else if ("NPCs in sim.."   == menu)  startSensor(user, "",  "", AGENT,            16384.0, NPC_PICK,   "Choose NPC :",              FALSE, FALSE);
            else if ("sit.."           == menu)  startSensor(user, npc, "", ACTIVE | SCRIPTED, 1024.0, NPC_SIT,    "Choose thing to sit on :",  FALSE, TRUE);
            else if ("stalk them.."    == menu)  startSensor(user, npc, "", AGENT,             1024.0, NPC_STALK,  "Choose person to stalk :",  TRUE,  TRUE);
            else if ("touch.."         == menu)  startSensor(user, npc, "", ACTIVE | SCRIPTED, 1024.0, NPC_TOUCH,  "Choose thing to touch :",   FALSE, TRUE);

            // Misc.
            else if (NPC_PICK          == menu)  npc = uuid;
            else if ("start recording" == menu)  llMessageLinked(LINK_SET, NPC_RECORD,      "", NPCscriptKey);
            else if ("stop recording"  == menu)  llMessageLinked(LINK_SET, NPC_RECORD_STOP, "", NPCscriptKey);
            else if (NPC_CREATE   == menu)
            {
                sendCommand(user, NPC_CREATE   + " " + selection);
                // Avoid the NPCmenu() below.  An odd one out, coz the NPC wont exist yet, but we want their menu when they do exist.
                return;
            }
            else if (NPC_DELETE    == menu)
            {
                sendCommand(user, NPC_DELETE + " " + npc);
                // An odd one out, the NPC wont exist, so return to the main menu.
                npc = NULL_KEY;
            }
            else if ("backup NPCs"     == menu)
            {
                list avatars = osGetAvatarList();   // Strided list, UUID, position, name.
                list delete = [];
                list backup = [];
                integer length = llGetListLength(avatars);
                integer i;

                llRemoveInventory(NPC_BACKUP_CARD + NPC_SCRIPT_EXT);
                for (i = 0; i < length; i++)
                {
                    key this = (key) llList2String(avatars, i * 3);

                    if (osIsNpc(this))
                    {
                        string aName = llKey2Name(this);

                        osAgentSaveAppearance(this, aName + NPC_NPC_EXT);
                        delete += [NPC_DELETE + " " + aName];
                        backup += [NPC_CREATE + " " + aName + " " + llList2String(avatars, (i * 3) + 1)];
                    }
                }
                osMakeNotecard(NPC_BACKUP_CARD + NPC_SCRIPT_EXT,
                      ["script " + NPC_BACKUP_CARD + ".before" + NPC_SCRIPT_EXT]
                    + delete + backup
                    + ["script " + NPC_BACKUP_CARD + ".after" + NPC_SCRIPT_EXT]);
            }

            // If the menu name ends in "..", then it's expected that we are waiting on another menu, so don't show one now.
            if (0 == osRegexIsMatch(menu, ".+\\.\\.$"))
                NPCmenu(user, npc);
        }   // End of menu block.
    }

    no_sensor()
    {
        sensorInFlight = FALSE;
        if (llGetListLength(sensorRequests))
        {
            sensorRequests = llDeleteSubList(sensorRequests, 0, 8);
            nextSensor();
        }
    }

    sensor(integer numberDetected)
    {
        sensorInFlight = FALSE;
        if (llGetListLength(sensorRequests))
        {
            string  name    = llList2String(sensorRequests, 0);
            integer type    = llList2Integer(sensorRequests, 1);
            float   range   = llList2Float(sensorRequests, 2);
            string  command = llList2String(sensorRequests, 3);
            key     user    = llList2String(sensorRequests, 4);
            key     npc     = llList2String(sensorRequests, 5);
            string  title   = llList2String(sensorRequests, 6);
            integer agents  = llList2Integer(sensorRequests, 7);
            integer every   = llList2Integer(sensorRequests, 8);

            sensorRequests = llDeleteSubList(sensorRequests, 0, 8);
            if ("" == title)
                sendCommand(user, command + " " + npc + " " + llDetectedKey(0));
            else
            {
                integer i;
                list menu = [];

                if (agents)
                    menu += ["you|" + (string) user];
                for (i = 0; i < numberDetected; i++)
                {
                    key this = llDetectedKey(i);

                    if (!(agents && (this == user)))
                        if (every || osIsNpc(this))
                            menu += [llDetectedName(i) + "|" + (string) this];
                }
                if (llGetListLength(menu) > 0)
                    showMenu(user, (string) INVENTORY_NONE, title, menu, command + "|" + npc);
            }
            nextSensor();
        }
    }

}