From 9c9a333860debf85b403a79bd6ee93b4f6bc5ded Mon Sep 17 00:00:00 2001 From: David Walter Seikel Date: Sun, 2 Mar 2014 22:34:54 +1000 Subject: Initial import of everything. --- NPC_menu.lsl | 481 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 481 insertions(+) create mode 100644 NPC_menu.lsl (limited to 'NPC_menu.lsl') 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 @@ +// 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(); + } + } + +} \ No newline at end of file -- cgit v1.1