diff --git a/All In One NPC Recorder and Player/All In One NPC Recorder and Player/Object/NPC Recorder Script v 4.6.lsl b/All In One NPC Recorder and Player/All In One NPC Recorder and Player/Object/NPC Recorder Script v 4.6.lsl index 1fe37a0c..9449a25e 100644 --- a/All In One NPC Recorder and Player/All In One NPC Recorder and Player/Object/NPC Recorder Script v 4.6.lsl +++ b/All In One NPC Recorder and Player/All In One NPC Recorder and Player/Object/NPC Recorder Script v 4.6.lsl @@ -4,7 +4,7 @@ // :AUTHOR:Ferd Frederix // :KEYWORDS:NPC, Puppeteer // :CREATED:2015-10-12 18:07:40 -// :EDITED:2015-10-12 17:07:40 +// :EDITED:2015-12-05 19:25:52 // :ID:27 // :NUM:1832 // :REV:4.6 @@ -17,7 +17,7 @@ // Should be worn as a HUD to record. // Put it on the ground and click Sensor or Start NPC when done. // :CODE: -// This is Rev 4.5 09/26/2015 +// This is Rev 4.6 12/04/2015 // Revision History // Rev 1.1 10-2-2014 @Sit did not work. Minor tweaks to casting for lslEditor @@ -54,6 +54,7 @@ // @teleport works for relative and absolute modes // Rev 4.4 09-26-2015 if it could not find the (deleted) NPC, it could not restart // Rev 4.5 09-29-2015 remove wait for STATE == 0 +// Rev 4.6 12-4-2015 fixed wanderhold did not wander correctly. //*******************************************************************// // Instructions on how to use this are at http://www.outworldz.com/opensim/posts/NPC/ @@ -181,10 +182,10 @@ ////////////////////////////////////////////////////////// // DEBUG // ////////////////////////////////////////////////////////// -integer debug = FALSE; // set to TRUE or FALSE for debug chat on various actions +integer debug = TRUE; // set to TRUE or FALSE for debug chat on various actions integer LSLEditor = FALSE; // set to to TRUE to working in LSLEditor, FALSE for in-world. // you must also include the NPC commands found in the other script since LSLEditor does not support OpenSim -integer iTitleText = TRUE; // set to TRUE to see debug info in text above the controller +integer iTitleText = FALSE; // set to TRUE to see debug info in text above the controller ////////////////////////////////////////////////////////// // TUNABLE CONFIGURATION // @@ -192,7 +193,7 @@ integer iTitleText = TRUE; // set to TRUE to see debug info in text above the integer keyNum = -1; // (namaka) special number for link message to broadcast the NPC key integer allowListener = TRUE; // set to TRUE to anable a command listener. Usually, this is setto FALSE integer link_Channel = 4223; // some random number you want to talk to this gadget on. Best if large and negative -float TIMER = 2; // faster = less jerky stopping. How often the system checks the distance traveled. Fastest you can go is 0.5 seconds +float TIMER = 1; // faster = less jerky stopping. How often the system checks the distance traveled. Fastest you can go is 0.5 seconds float QUICK = 1; // when we need to move to the next state, we use a QUICK timer string Appearance = "!Appearance"; // The name of the recorded Appearance notecard string Notecard = "!Path"; // The name of the recorded routes @@ -1644,6 +1645,7 @@ default // Wandering requires us to re-wander when we reach a destination else if (WanderHold == STATE) { StateWander(); + return; } else if (DoProcess == STATE) { TimerEvent(QUICK); diff --git a/ChatBot/ChatBot/Object/chyatbot codes.jpg b/ChatBot/ChatBot/Object/chyatbot codes.jpg new file mode 100644 index 00000000..8ad303d2 Binary files /dev/null and b/ChatBot/ChatBot/Object/chyatbot codes.jpg differ diff --git a/Give all inventory items in a folder when close by/Give all inventory items in a folder when close by.sol b/Give all inventory items in a folder when close by/Give all inventory items in a folder when close by.sol new file mode 100644 index 00000000..4ccda0ee --- /dev/null +++ b/Give all inventory items in a folder when close by/Give all inventory items in a folder when close by.sol @@ -0,0 +1,3 @@ + + + diff --git a/Give all inventory items in a folder when close by/Give all inventory items in a folder when close by/Give all inventory items in a folder when close by.prj b/Give all inventory items in a folder when close by/Give all inventory items in a folder when close by/Give all inventory items in a folder when close by.prj new file mode 100644 index 00000000..941e7934 --- /dev/null +++ b/Give all inventory items in a folder when close by/Give all inventory items in a folder when close by/Give all inventory items in a folder when close by.prj @@ -0,0 +1,10 @@ + + + + + + + + + diff --git a/Give all inventory items in a folder when close by/Give all inventory items in a folder when close by/Object/Script for Board.lsl b/Give all inventory items in a folder when close by/Give all inventory items in a folder when close by/Object/Script for Board.lsl new file mode 100644 index 00000000..be368183 --- /dev/null +++ b/Give all inventory items in a folder when close by/Give all inventory items in a folder when close by/Object/Script for Board.lsl @@ -0,0 +1,171 @@ +// :SHOW: +// :CATEGORY:Presentation +// :NAME:Give all inventory items in a folder when close by +// :AUTHOR:Lum Pfohl +// :KEYWORDS: +// :CREATED:2015-11-24 20:25:32 +// :EDITED:2015-11-24 19:25:32 +// :ID:1086 +// :NUM:1833 +// :REV:1 +// :WORLD:Second Life +// :DESCRIPTION: +// LumVision-Presentation Script v 0.2 +// :CODE: +// Mods by Ferd Frederix to support GIFS +// +// LumVision-Presentation Script v 0.2 +// +// Written by Lum Pfohl December 11, 2007 +// +// January 02, 2008 - Added code to precache the next texture after a short time delay + + +// set to TRUE if you want debug messages written to chat screen +integer __debug = FALSE; +string __version_id = "Presentation Script v 0.3"; + +// global variables +integer interval; +integer currentTexture = 0; + +integer totalTextures = 0; +list textureList =[]; +integer messageChannel = 999888; +list dynMenu =["Back", "Version", "Forward", "Reset"]; + + +gif() +{ + string t = llList2String(textureList, currentTexture); + list gif = llParseString2List(t,[";"],[]); + string aname = llList2String(gif,0); + integer X = (integer) llList2String(gif,1); + integer Y = (integer) llList2String(gif,2); + float FPS = (float) llList2String(gif,3); + float product = X * Y; + + + + // Set the new prim texture + + llSetTexture(llList2String(textureList, currentTexture), 0); + + if (X) { + llSetTextureAnim( ANIM_ON | LOOP, ALL_SIDES, X, Y, 0.0, product, FPS); + } else { + llSetTextureAnim( LOOP, ALL_SIDES, 1,1, 0.0, 1 , FPS); + } + +} +default { +state_entry() { + + +// read in the textures in the prim and store it + integer typeCount = llGetInventoryNumber(INVENTORY_TEXTURE); + + integer j; + + for (j = 0; j < typeCount; ++j) { + string invName = llGetInventoryName(INVENTORY_TEXTURE, j); + if (__debug) { + llWhisper(0, "Inventory " + invName); + } + textureList += invName; + ++totalTextures; + } + + if (__debug) { + llWhisper(0, "Found " + (string) totalTextures + " textures"); + } + + llSetTexture(llList2String(textureList, 0), 0); + gif(); + + // initialize the channel on which the vendor will talk to the owner via dialog + messageChannel = (integer) llFrand(2000000000.0); + llListen(messageChannel, "", NULL_KEY, ""); + + currentTexture = 0; + } + +on_rez(integer start_param) { + llResetScript(); +} + + +touch_start(integer total_number) { + + if (llDetectedKey(0) == llGetOwner()) { + llDialog(llDetectedKey(0), "What do you want to do?", dynMenu, messageChannel); + } + +} + +// listen for for dialog box messages and respond to them as appropriate + +listen(integer channel, string name, key id, string message) { + + if (id != llGetOwner()) { + return; + } + + if (message == "Version") { + llWhisper(0, __version_id); + return; + } + + if (message == "Reset") { + llResetScript(); + } + + if (message == "Back" && currentTexture > 0) { + --currentTexture; + } else if (message == "Forward") { + ++currentTexture; + } else { + llDialog(llGetOwner(), "What do you want to do?", dynMenu, messageChannel); + return; + } + + // If there are textures to apply, do so now. Otherwise - quietly + // do nothing. + if (totalTextures > 0) { + + // Ensure that we do not go out of bounds with the index + if (currentTexture >= totalTextures) { + currentTexture = 0; + } + if (currentTexture < 0 ) { + currentTexture = 0; + } + + gif(); + + // set up so that in 3 seconds, we display the next image on a different face (hidden) + llSetTimerEvent(3.00); + + llDialog(llGetOwner(), "What do you want to do?", dynMenu, messageChannel); + } +} + + timer() { + // Cancel any further timer events + llSetTimerEvent(0.00); + + // set the next texture (as a pre-cache) on the reverse face + integer nextTexture = currentTexture + 1; + if (nextTexture >= totalTextures) { + nextTexture = 0; + } + llSetTexture(llList2String(textureList, nextTexture), 1); + } + + changed(integer what) + { + if (what & CHANGED_INVENTORY) + llResetScript(); + } + +} diff --git a/Give all inventory items in a folder when close by/Give all inventory items in a folder when close by/Object/Script.lsl b/Give all inventory items in a folder when close by/Give all inventory items in a folder when close by/Object/Script.lsl new file mode 100644 index 00000000..df37f0a1 --- /dev/null +++ b/Give all inventory items in a folder when close by/Give all inventory items in a folder when close by/Object/Script.lsl @@ -0,0 +1,12 @@ + +default +{ + state_entry() + { + llSay(0, "Hello, Avatar!"); + } + touch_start(integer total_number) + { + llSay(0, "Touched: "+(string)total_number); + } +} \ No newline at end of file diff --git a/Give all inventory items in a folder when close by/Give all inventory items in a folder when close by/Object/dragon lady5;2;5.jpg b/Give all inventory items in a folder when close by/Give all inventory items in a folder when close by/Object/dragon lady5;2;5.jpg new file mode 100644 index 00000000..bf6bfb24 Binary files /dev/null and b/Give all inventory items in a folder when close by/Give all inventory items in a folder when close by/Object/dragon lady5;2;5.jpg differ diff --git a/Give all inventory items in a folder when close by/Give all inventory items in a folder when close by/Object/dragon;4;4;5.jpg b/Give all inventory items in a folder when close by/Give all inventory items in a folder when close by/Object/dragon;4;4;5.jpg new file mode 100644 index 00000000..fe5f9be0 Binary files /dev/null and b/Give all inventory items in a folder when close by/Give all inventory items in a folder when close by/Object/dragon;4;4;5.jpg differ diff --git a/HyperGrid Story Nine/BackupHypergrid Story Nine/Collider/Collider.lsl b/HyperGrid Story Nine/BackupHypergrid Story Nine/Collider/Collider.lsl new file mode 100644 index 00000000..c4539ea7 --- /dev/null +++ b/HyperGrid Story Nine/BackupHypergrid Story Nine/Collider/Collider.lsl @@ -0,0 +1,39 @@ +// :SHOW:1 +// :CATEGORY:Gaming +// :NAME:Collider for All in One NPC Controller +// :AUTHOR:Ferd Frederix +// :KEYWORDS:Game, Collider +// :REV:2.0 +// :WORLD:OpenSim +// :DESCRIPTION: +// Triggers the NPC controller to play the Greet notecard when collided. +// :CODE: + +string message = "@notecard=Greet"; + +Reset() { + llSetStatus(STATUS_PHANTOM, FALSE); // rev 2.0 + llVolumeDetect(FALSE); + llSleep(0.1); + llVolumeDetect(TRUE); +} + +default{ + state_entry(){ + Reset(); + } + collision_start(integer n){ + if (osIsNpc(llDetectedKey(0))){ + return; + } + llMessageLinked(2,0,message,""); + } + on_rez(integer p){ + llResetScript(); + } + changed(integer what){ + if (what & CHANGED_REGION_START){ + llResetScript(); + } + } +} \ No newline at end of file diff --git a/HyperGrid Story Nine/BackupHypergrid Story Nine/Collider/NPC Recorder Script v 4.5.lsl b/HyperGrid Story Nine/BackupHypergrid Story Nine/Collider/NPC Recorder Script v 4.5.lsl new file mode 100644 index 00000000..2ec33d0a --- /dev/null +++ b/HyperGrid Story Nine/BackupHypergrid Story Nine/Collider/NPC Recorder Script v 4.5.lsl @@ -0,0 +1,1680 @@ +// :SHOW:1 +// :CATEGORY:NPC +// :NAME:All In One NPC Recorder and Player +// :AUTHOR:Ferd Frederix +// :KEYWORDS:NPC, Puppeteer +// :ID:27 +// :REV: 4.5 +// :WORLD:OpenSim +// :DESCRIPTION: +// All in one NPC recorder player. +// Supports both absolute and relative paths and many new commands +// Add animations named "Fly, Walk, Stand and Run" +// Click Prim to use. +// Should be worn as a HUD to record. +// Put it on the ground and click Sensor or Start NPC when done. +// :CODE: +// This is Rev 4.5 09/26/2015 + +// Revision History +// Rev 1.1 10-2-2014 @Sit did not work. Minor tweaks to casting for lslEditor +// Rev 1.2 10-14-2014 @ sit had wrong type. +// Rev 1.3 relative movement fixed for @fly +// Rev 1.4 4-3-2014 allow anyone to use this, non owners and non group members can only start and stop. +// Rev 1.5 5-17-2014 set sensor to auto start on reboot of sim +// Rev 1.6 5-24-2014 move menu so you can get it by touching, removed many of the KeyValues to RAM for efficiency +// Rev 1.7 CHANGED_REGION_START, not CHANGED_REGION_START (Opensim difference) +// Rev 1.8 tuned up Kill NPC, added more flexible upgrader +// Rev 1.9 Better script injection by link message// Rev 2.0 Added osSetSpeed so you can speed up or slow down an NPC. +// Rev 2.1 No laggy sensor used exept to sit on stuff +// Rev 2.2 Various sensor fixes +// Rev 2.3 Sets No Sensor in menu, must be started by hand +// Rev 2.4 - reserved for patches to 2.3 if needed +// Rev 3.0 Refactor out into subs, not states to make command injection easier +// New command: @appearance=Notecardname so you can switch to a new notecard on the fly +// New command: @speed=1.0 which slows up ( < 1 ) or speeds up ( > 1) +// Rev 3.1 Commands are not interruptible by Link Message +// Rev 3.2 Sensor patches for consistency in removing the NPC +// Rev 3.3 Added Touch command by Neo.Cortex@hbase42/hopto/org:8002 +// Added Menu 3 for notecard and appearance commands +// Rev 3.4 animation timer cannot be zero or it shuts off timer tweaked +// solves the NPC starting up when no sensor is set. +// Rev 3.5 fixes saving to !Path notecard +// Rev 3.6 08-11-2015 @delete acts like @stop. TYjhe NPC now rezzes after an @go back in where it was deleted +// Rev 3.7 08-11-2015 @attach command added to load an attachment from the inventory to the NPC +// Rev 3.8 08-17-2015 process queued commands one at a time without calling ProcessNPCLine on link message +// Rev 3.9 08-23-2011 Queued command fixes including @delete which were not always working +// Rev 4.0 09-15-2015 Fixes for Sensor functions which continually rezzed a NPC when no one was around. +// Rev 4.1 09-20-2015 Added a Listener so link messages are not needed +// Rev 4.2 09-23-2015 Added @teleport= +// Rev 4.3 09-24-2015 Added @reset to restart the NPC at the very start of the !Path notecard +// @teleport works for relative and absolute modes +// Rev 4.4 09-26-2015 if it could not find the (deleted) NPC, it could not restart +// Rev 4.5 09-29-2015 remove wait for STATE == 0 +//*******************************************************************// + +// Instructions on how to use this are at http://www.outworldz.com/opensim/posts/NPC/ +// This is an OpenSim-only script. +// Author: Ferd Frederix aka Fred Beckhusen - fred@mitsi.com + +//////////////////////////////////////////////////////////////////////////////////////////// +// Original code was Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // +/////////////////////////////////////////////////////////////////////////////////////////// +// Please see: http://www.gnu.org/licenses/gpl.html for legal details, // +// rights of fair usage, the disclaimer and warranty conditions. // +/////////////////////////////////////////////////////////////////////////////////////////// +// The original NPC controller was from http://was.fm/opensim:npc +// Extensive additions and bug fixes by Fred Beckhusen, aka Ferd Frederix +// llSensor had two params swapped +// @Wander would wander where it had rezzed, not where it was. +// There was no 'no_sensor' event in sit, so if a @sit failed, the NPC got stuck +// The animation and walks always stopped old, then started new. It should be start new, then stop old so the default stand would be suppressed. +// New code: +// Merged with new Route recorder and notecard writer +// If the NPC failed to reach a destination it never moved on. +// Added WAIT global to tune this +// Exposed many tunable variables and ported the code +// Added floating point to times in notecard. +// Added @sound, @randsound, @whisper, @shout, and @cmd controls. +// notecards integers are not floats for better control +// +// Link Messages may be used to perform external control by injecting @commands into the stream of actions +// Example: +// To chat something, such as with a chat robot +// llMessageLinked(LINK_SET,0,"@npc_say=Hello",""); + +// This script assumes that NPCs and OSSl scripting is enabled in the OpenSim configuration. +// In order to enable them, the following changes must be made in the OpenSim.ini configuration file: +// +// ; Turn on OSSL +// AllowOSFunctions = true +// OSFunctionThreatLevel = Severe + +//[NPC] +// ;# {Enabled} {} {Enable Non Player Character (NPC) facilities} {true false} +// Enabled = true +// +// and then the server has to be restarted. +// please note that there are better ways to enable NPC in the latest Opensim. + +// Commands: All commands begin with an @ sign. All other lines are ignored +// @commands may have optional parameters. The syntax is always: +// @cmd=parm1|parm2 +// NaN in the table below meand Not a Number. This means there is no parameter + +//Command First Parameter Second Parameter Description +//@spawn name location (vector) Rezzes an NPC with name at a location. +//@appearance NoteCardName NaN switch the NPC appearance to a new notecard +//@walk destination (vector) NaN Makes the NPC walk to destination. +//@fly destination (vector) NaN Makes the NPC fly to destination. +//@land destination (vector) NaN Makes the NPC land at destination. +//@say string NaN Makes the NPC speak a phrase. +//@whisper string NaN Makes the NPC whisper a phrase. +//@shout string NaN Makes the NPC shout a phrase. +//@pause seconds (float) NaN Makes the NPC wait for a multiple of seconds. +//@wander radius (float) cycles (integer) Makes the NPC wander in radius, for cycles seconds. +//@delete NaN NaN Removes the NPC. Requires a link message to continue +//@goto label (string) NaN Jump to the label label in the script. +//@animate animation (string) time (float) Makes the NPC trigger the animation animation for time seconds. +//@sound sound_name NaN plays a sound from inventory +//@randsound NaN NaN Plays a random sound from inventory +//@rotate degrees (float) NaN Rotate the NPC degrees around the Z axis. +//@sit primitive name NaN Sit on a primitive with a given name. +//@touch primitive name NaN Touch on a primitive with a given name. +//@stand NaN NaN If sitting on a primitive, stand up. +//@cmd channel (integer) string Says string on channel, for controlling external gadgets +//@stop NaN NaN Halts the NPC script indefinitely. Can be started with a link message +//@go NaN NaN Continues on next notecard line, for use in link messages +//@speed speed (float) NaN from 0 to N, where 1.0 ius a normal speed of an avatar. 0.2 is a turtle. +//@notecard notename (string) NaN load a new Path notecard +//@attach InventoryName attachmentPoint load an attachment from the inventory to the NPC onto point +//@teleport destination (vector) NaN Makes the NPC teleport to destination in the same sim. They cannot tp to another sim or across the HG +//@reset NaN NaN Deletes the NPC, starts the !Path notecard over. + +// Constant attachmentPoint Comment +// ATTACH_CHEST 1 chest/sternum +// ATTACH_HEAD 2 head +// ATTACH_LSHOULDER 3 left shoulder +// ATTACH_RSHOULDER 4 right shoulder +// ATTACH_LHAND 5 left hand +// ATTACH_RHAND 6 right hand +// ATTACH_LFOOT 7 left foot +// ATTACH_RFOOT 8 right foot +// ATTACH_BACK 9 back +// ATTACH_PELVIS 10 pelvis +// ATTACH_MOUTH 11 mouth +// ATTACH_CHIN 12 chin +// ATTACH_LEAR 13 left ear +// ATTACH_REAR 14 right ear +// ATTACH_LEYE 15 left eye +// ATTACH_REYE 16 right eye +// ATTACH_NOSE 17 nose +// ATTACH_RUARM 18 right upper arm +// ATTACH_RLARM 19 right lower arm +// ATTACH_LUARM 20 left upper arm +// ATTACH_LLARM 21 left lower arm +// ATTACH_RHIP 22 right hip +// ATTACH_RULEG 23 right upper leg +// ATTACH_RLLEG 24 right lower leg +// ATTACH_LHIP 25 left hip +// ATTACH_LULEG 26 left upper leg +// ATTACH_LLLEG 27 left lower leg +// ATTACH_BELLY 28 belly/stomach/tummy +// ATTACH_LEFT_PEC 29 left pectoral +// ATTACH_RIGHT_PEC 30 right pectoral +// ATTACH_HUD_CENTER_2 31 HUD Center 2 +// ATTACH_HUD_TOP_RIGHT 32 HUD Top Right +// ATTACH_HUD_TOP_CENTER 33 HUD Top +// ATTACH_HUD_TOP_LEFT 34 HUD Top Left +// ATTACH_HUD_CENTER_1 35 HUD Center +// ATTACH_HUD_BOTTOM_LEFT 36 HUD Bottom Left +// ATTACH_HUD_BOTTOM 37 HUD Bottom +// ATTACH_HUD_BOTTOM_RIGHT 38 HUD Bottom Right +// ATTACH_NECK 39 neck +// ATTACH_AVATAR_CENTER 40 avatar center/root + + + +////////////////////////////////////////////////////////// +// DEBUG // +////////////////////////////////////////////////////////// +integer debug = TRUE; // set to TRUE or FALSE for debug chat on various actions +integer LSLEditor = TRUE; // set to to TRUE to working in LSLEditor, FALSE for in-world. + // you must also include the NPC commands found in the other script since LSLEditor does not support OpenSim +integer iTitleText = FALSE; // set to TRUE to see debug info in text above the controller + +////////////////////////////////////////////////////////// +// TUNABLE CONFIGURATION // +////////////////////////////////////////////////////////// +integer allowListener = TRUE; // set to TRUE to anable a command listener. Usually, this is setto FALSE +integer link_Channel = 4223; // some random number you want to talk to this gadget on. Best if large and negative +float TIMER = 2; // faster = less jerky stopping. How often the system checks the distance traveled. Fastest you can go is 0.5 seconds +float QUICK = 1; // when we need to move to the next state, we use a QUICK timer +string Appearance = "!Appearance"; // The name of the recorded Appearance notecard +string Notecard = "!Path"; // The name of the recorded routes +integer allowUsers = FALSE; // If true, any user can get a Start NPC and Stop NPC menu. Only groups and owners can get all commands if TRUE, or FALSE +float MAXDIST = 2.0; // how close a NPC has to get to a dest pos to continue to next state. Do not lower this too much, as it may miss the target +integer WANDERRAND = TRUE; // set to TRUE and they will pause during wanders a random number of seconds +float WANDERTIME = 3.0; // how long they stand after each @wander,if WANDERRAND is FALSE. If WANDERRAND is TRUE, this is the max time +integer WAIT = 30; // wait for this number of seconds for the NPC to reach a destination (for safety). If it fails to reach a target, it will move on after this time. +float RANGE = 50; // 1 to N meters - anyone this close to the controller will start NPCS if Sensor button is clicked +float REZTIME = 2.0; // wait this long for NPC to rez in, then start the process +string STAND = "Stand"; // the name of the default Stand animation +string WALK = "Walk"; // the name of the default Walk animation +string FLY = "Fly"; // the name of the default Fly animation +string RUN = "Run"; // the name of the default Run animation +string LAND = "Land"; // the name of the default land animation ( for birds only) +float OffsetZ = 0.5; // appear 0.5 meter above ground, this is added to all destinations to keep them from sinking in. +float SPEEDMULT =0.5; // 1.0 = regular avatar speed. Smaller numbers slow down walks. Large numbers speed them up. +integer FLIGHT = 299; // For controlling wings. A channel that is shouted at when flight starts and ends. "flying" or "landing" + +// DESCRIPTIONS FIELDS HAVE TO SURVIVE A RESET +// These vars are stored by saving them with KeyValueSet +// "pr" is a 0 if it is set for Owner Only, 1 for Group control +// "se" is "on" if Started +// "co" = "R" or "A" for relative or absolute addressing mode +// "key" = NPC key + +// These Globals used to be stored in description. Moved to RAM in V1.6 +float RAMPause; // @pause param +float RAMwd ; // @wander distance +integer RAMwc; // @wander count +float RAMrot; // @rotate +string RAMsit; // @sit primname +string RAMtouch; // @touch primname +string RAManimationName; // @animate animation (string) time (float) +float RAManimationTime; + +// other globals section +integer iChannel; // a listen channel, randomly assigned +integer iHandle; // the handle to it + +// NPC controls +vector newDest ; // tmp storage for the walks +integer iWaitCounter ; // wait for this number of seconds for the NPC to reach a desrtination +string sNPCName; // the name of the NPC that may be in world. So we can remove it. +integer bNPC_STOP = FALSE; // boolean to reuse a listener +integer Stopped = FALSE; // set to TRUE by link messages so we do not remember them +float fTimerVal ; // how long we wait when wandering (calculated) +float NPCEnabled; // true if the NPC is suppodes to be running + +// OS_NPC_CREATOR_OWNED will create an 'owned' NPC that will only respond to osNpc* commands issued from scripts that have the same owner as the one that created the NPC. +// OS_NPC_NOT_OWNED will create an 'unowned' NPC that will respond to any script that has OSSL permissions to call osNpc* commands. +integer NPCOptions = OS_NPC_CREATOR_OWNED; // only yhe owner of this box can control this NPC. + +integer walkstate = 0; // helps us reshare the walk state for run, fly and land - a bit of a hack, but it saves RAM. Has to be done this way because some bits of NPCWalkOption are asserted as 0 + +integer NPCWalkOption; // Some notes for what happens to NPCWalkOption: +// OS_NPC_FLY - Fly the avatar to the given position. The avatar will not land unless the OS_NPC_LAND_AT_TARGET option is also given. +// OS_NPC_NO_FLY - Do not fly to the target. The NPC will attempt to walk to the location. If it's up in the air then the avatar will keep bouncing hopeless until another move target is given or the move is stopped +//OS_NPC_LAND_AT_TARGET - If given and the avatar is flying, then it will land when it reaches the target. If OS_NPC_NO_FLY is given then this option has no effect. +// OS_NPC_RUNNING - if given, NPC avatar moves at running/fast flying speed, otherwise moves at walking/slow flying speed. + +// menus +string mSensor="Sense is Off"; // Sensor or "No Sensor" + +list lAtButtons = ["Menu","-", ">>", "@run", "@walk", "@fly", "@land", "@wander", "@sit", "@stand","@animate","@rotate"]; +list lMenu2 = ["<<", "@comment", ">>>", "@stop", "@say", "@whisper","@shout","@sound","@randsound","@cmd", "@pause", "@delete"]; +list lMenu3 = ["<<<","@notecard","@appearance", "@touch", "@speed", "@attach", "@teleport","-", "-", "-", "-", "-" ]; + +string sCommand; // place to store a command for two-prompted ones +string sParam2; // place to store a prompt for two-prompted ones +string priPub = "Owner Only"; // Private or Group +key kUserKey; // the person who is controlling the avatar, not the Owner +// the command lists +list lCommands; // commands are stored here +list lNpcCommandList; // Storage for the NPC script. +string npcAction; // Storage for the next action. @cmd=0|hello, this becomes @cmd +string npcParams; // Storage for the param, @cmd=0|hello, this becomes 0|hello + +// misc vars +string sNotecard; // commands are stored here temporarily for dumping +vector vWanderPos; // a place to wander to +string lastANIM ; // last animation run +// Sensor +integer avatarPresent; // Sensor sets this flag when people are within Range. + +// Coordinate control +vector vInitialPos ; // Vector that will be filled by the script with the initial starting position in region coordinates. +vector vDestPos = ZERO_VECTOR; // Storage for destination position. +string relAbs = "Relative"; // absolute vs relative positioning + + +// STATES +integer MENU ; // processing a dialog box state, may be concurrent with STATE +integer STATE; // state storage +integer MakeNotecard = 1; // displaying a text box for NPC name +integer RecordPath = 2; // displaying a path notecard menu +integer NobodyHome = 3; // looking for an avatar +integer Spawning = 4; // spawning an avatar +integer Animate = 5; // animation timer needed +integer Walking = 6; // Hey! I am walking here! +integer Wander = 7; // Wandering around neeeds a timer, too +integer WanderHold = 8; // We reached a wander point +integer DoProcess = 9; // Set this to make it process a new command +integer Touch = 10; // Timer is busy sensing something to touch +integer Sit = 11; // Timer is busy sensing something to sit on +integer Paused = 12; // Timer is busy pausing + +key gNpcKey = NULL_KEY; // global key storage for the one NPC, to save CPU cycles +list Stack ; // a command stack from link message input + +integer SensorFunc = 0; // define which function shall be triggered inside the sensor function + // 0 means none, 1 sit, 2 touch +/////////////////////////////////////////////////////////////////////////// +// FUNCTIONS // +/////////////////////////////////////////////////////////////////////////// + + +TimerEvent(float timesent) +{ + if (LSLEditor) + timesent *= 5; // slow thinggs doen when the LSLEDITOR is in use + + DEBUG("Setting timer: " + (string) timesent); + llSetTimerEvent(timesent); +} + +// for 4.1 parse a message from a Listen or a Link message +ParseMsg(string str) { + DEBUG("Command In:" + str); + if (str=="@go") { + SetStop(FALSE); // Let's run the notecard + DEBUG("@go running"); + DoProcessNPCLine(); + } else { + Stack += [str]; // take anything, the controller will filter away non @ stuff + DoProcessNPCLine(); // v 4.5 remove wait for STATE == 0 + } +} + +SetStop(integer what) +{ + DEBUG("Stopped set to " + (string ) what); + Stopped = what; +} +// Do* functions are much like states from the old V2 scripts. + +// Save a Path notecard +DoSave() +{ + STATE = MakeNotecard; + makeText("Stand where you want the NPC to appear, and enter the NPC Name"); +} + +// This function is used to record the path for the NPC +// Each command can take 0, 1, or 2 params +DoMenuForCommands() { + makeMenu(lAtButtons); +} + + +// No one is here when sensors were on, so we kill off the NPC +DoNobodyHome() +{ + DEBUG("Nobody Home"); + STATE = NobodyHome; + if (NPCKey() != NULL_KEY) { + osNpcRemove(NPCKey()); + SaveKey(NULL_KEY); + } + TimerEvent(5); // keep ticking to sense avatars +} + +// Create a NPC +StateSpawn() { + DEBUG("state spawn"); + STATE = Spawning; + + + NPCEnabled = TRUE; // in world + // see if there is already one out there. + if (NPCKey() != NULL_KEY) { + DEBUG("Already living"); + return; + } + + + list name = llParseString2List(sNPCName, [" "], []); + + vector vRezPos = vInitialPos; + if (relAbs == "Relative"){ + vRezPos += llGetPos(); + } + + DEBUG("Rezzing the NPC:" + (string) vRezPos); + key aKey = osNpcCreate(llList2String(name, 0), llList2String(name, 1), vRezPos, Appearance, NPCOptions); + + SaveKey(aKey); // save in desceription and global, too + + osSetSpeed(aKey,SPEEDMULT); // 1.9 speed multiplier + TimerEvent(REZTIME); + NPCAnimate(STAND); +} + +DoRotate() { + DEBUG("@rotate=" + (string) RAMrot); + osNpcSetRot(NPCKey(), llEuler2Rot(<0,0,RAMrot> * DEG_TO_RAD)); +} + +StateSit() { + DEBUG ("state sit - looking for " + RAMsit); + STATE=Sit; + SensorFunc = 1; //triggers osNpcSit + llSensor(RAMsit, "", PASSIVE|ACTIVE|SCRIPTED, 96, PI); +} + +StateTouch() { + DEBUG ("state touch - looking for " + RAMtouch); + STATE = Touch; + SensorFunc = 2; //triggers osNpcTouch + llSensor(RAMtouch, "", PASSIVE|ACTIVE|SCRIPTED, 96, PI); +} + +DoStand() { + DEBUG("state stand"); + osNpcStand(NPCKey()); +} + + +StateAnimate() { + + DEBUG("state animate"); + STATE = Animate; + NPCAnimate(RAManimationName); + if (RAManimationTime <=0 ) // V 3.4 tweak + RAManimationTime = 1; + TimerEvent(RAManimationTime); +} + +StateWalk() { + + DEBUG("NPCWalkOption = " + (string) NPCWalkOption); + STATE = Walking; + + // walk, fly, run, land + if (walkstate == 1) { + NPCAnimate(WALK); + } else if (walkstate == 2) { + llShout(FLIGHT,"flying"); + NPCAnimate(FLY); + } else if (walkstate == 3) { + NPCAnimate(RUN); + } else if (walkstate == 4) { + NPCAnimate(LAND); + } + newDest = vDestPos ; + newDest.z += OffsetZ; + + // notecard is stored as offsets from this box with relative addressing. Convert to absolute + if (relAbs == "Relative"){ + newDest += llGetPos(); + } + + DEBUG("Moveto:" + (string) newDest); + osNpcMoveToTarget(NPCKey(), newDest, NPCWalkOption); + iWaitCounter = WAIT; // wait 60 seconds to get to a destination. + TimerEvent(TIMER); +} + + +StateWander(){ + DEBUG("state wander"); + STATE = Wander; + + vector point = CirclePoint(RAMwd); + DEBUG("CirclePoint:" + (string) point); + vWanderPos = vDestPos + point; + DEBUG("vWanderPos:" + (string) vWanderPos); + + fTimerVal = WANDERTIME; // default time to pause after each wander + if (WANDERRAND) + fTimerVal = llFrand(WANDERTIME) + 1; // override, they want random times + + NPCAnimate(WALK); + + DEBUG("Wander to:" + (string) vWanderPos); + + osNpcMoveToTarget(NPCKey(), vWanderPos, NPCWalkOption); + iWaitCounter = WAIT; // wait 60 seconds to get to a destination. + TimerEvent(TIMER); +} + +StateWanderhold() { + + DEBUG("Wander Hold"); + STATE = WanderHold; + + // now that we have reached a wander spot, slow the timer down to the desired value + TimerEvent(fTimerVal); +} + +// @pause=10 will do nothing for 10 seconds +DoPause() { + STATE =Paused; + if (RAMPause < 0.1) + RAMPause = 0.1; + DEBUG("@pause=" + (string)RAMPause); + TimerEvent(RAMPause); +} + + +// @stop makes the NPC stop moving in whatever state it is in. You have to linkmessage to get moving again +DoStop() { + DEBUG("NPC is Stopped"); + STATE = 0; // accept commands + SetStop(TRUE); // Link controlled - we mnust have a @go to continue with notecards + TimerEvent(0); + Stack = []; // v3.8 +} + +// @delete removes the NPC forever. Next command starts it up again at the beginning +DoDelete() { + DEBUG("state delete"); + STATE = 0; // accept commands + osNpcRemove(NPCKey()); + SaveKey(NULL_KEY); + + TimerEvent(0); + Stack = []; // v3.8 +} + +// change the appearance of the NPC +DoAppearance(string notecard) { + DEBUG("state appearance"); + if (llGetInventoryType(notecard) == INVENTORY_NOTECARD){ + DEBUG("Load appearance " + notecard); + osNpcLoadAppearance(NPCKey(),notecard); + } +} + +// Change the avatar speed +DoSpeed(string speed) { + float newspeed = (float) speed; + if (newspeed > 0.1 && newspeed < 5.0) {// sanity check + osSetSpeed(NPCKey(),newspeed); + } +} + +DoTeleport(string params) { + list Data = llParseString2List(params, ["|"], []); + string itemName = llList2String(Data, 0); + vector Dest = (vector) itemName; + if (Dest != ZERO_VECTOR) { + if (relAbs == "Relative"){ + Dest += llGetPos(); + } + osTeleportAgent( NPCKey(), llGetRegionName(), Dest, ZERO_VECTOR ); + + } else { + llSay(DEBUG_CHANNEL,"Attempt to teleport to <0,0,0> probably not what you intended: @teleport="); + } +} + + + +DoNewNote (string card) { + DEBUG("Load Notecard " + card); + NPCReadNoteCard(card); + SetStop(FALSE); +} +DoAttach(string params) { + + list Data = llParseString2List(params, ["|"], []); + string itemName = llList2String(Data, 0); + integer attachmentPoint = (integer) llList2String(Data, 1); + if (attachmentPoint > 0 + && attachmentPoint < 40 + && llGetInventoryType(itemName) == INVENTORY_OBJECT + ) + { + osForceAttachToOtherAvatarFromInventory(NPCKey(),itemName,attachmentPoint); + } +} + +// This loops over the notecard, processing each command +DoProcessNPCLine() { + DEBUG("ProcessNPCLine, stopped = " + (string) Stopped); + STATE = DoProcess; + + // auto load a notecard + if (! llGetListLength(lNpcCommandList)) { + DEBUG("Read Notecard"); + NPCReadNoteCard(Notecard); + SetStop(FALSE); + } + + // look for link messages on the stack + string next = llList2String(Stack,0); // lets see if there is anything from a link message + if (llStringLength(next)) + { + Stack = llDeleteSubList(Stack,0,0); + ProcessCmd(next); //lets do this command instead. + return; + } + + // @stop issued? + if (Stopped) { + TimerEvent(0); + DEBUG("Stopped, waiting for input"); + STATE = 0; + return; + } + + // No, we have an @go for liftoff + next = llList2String(lNpcCommandList, 0); // get the next command + DEBUG("Execute:" + next); + lNpcCommandList = llDeleteSubList(lNpcCommandList, 0, 0); // delete it + + if (llGetListLength(lNpcCommandList) == 0) { + DEBUG("EOF"); + } + ProcessCmd(next); +} + + + +ProcessCmd(string cmd) { + + DEBUG("ProcessCmd:" + cmd); + + if (llGetSubString(cmd, 0, 0) != "@") { + DEBUG("ignoring"); + TimerEvent(QUICK); // this is so we do not recurse the stack + STATE = 0; + return; + } + + list data = llParseString2List(cmd, ["="], []); + npcAction = llToLower(llStringTrim(llList2String(data, 0), STRING_TRIM)); + + DEBUG("Action:" + npcAction); + npcParams = llStringTrim(llList2String(data, 1), STRING_TRIM); + DEBUG("Params:" + npcParams); + + @commands; + + ProcessSensor(); + + if(npcAction == "@spawn" && avatarPresent) { + DEBUG("@spawn"); + list spawnData = llParseString2List(npcParams, ["|"], []); + sNPCName =llList2String(spawnData, 0); // V 1.6 name in RAM + + list spawnDest = llParseString2List(llList2String(spawnData, 1), ["<", ",", ">"], []); + vInitialPos.x = llList2Float(spawnDest, 0); + vInitialPos.y = llList2Float(spawnDest, 1); + vInitialPos.z = llList2Float(spawnDest, 2); + + DEBUG("Coords for NPC at " + (string) vInitialPos); + StateSpawn(); + return; + } + + if (! avatarPresent){ + DoNobodyHome(); + DEBUG("No avatar nearby"); + STATE = 0; + return; + } else { + if ( NPCKey() == NULL_KEY) { + StateSpawn(); + } + } + + + + + if(npcAction == "@stop") { + DoStop(); + STATE = 0; + return; + } + else if(npcAction == "@goto") { + DEBUG("goto"); + integer lastIdx = llGetListLength(lNpcCommandList)-1; + lNpcCommandList = llDeleteSubList(lNpcCommandList, lastIdx, lastIdx); + // Wind commands till goto label. + @wind; + string next1 = llList2String(lNpcCommandList, 0); + lNpcCommandList = llDeleteSubList(lNpcCommandList, 0, 0); + lNpcCommandList += next1; + if(next1 != npcParams) jump wind; + // Wind the label too. + next1 = llList2String(lNpcCommandList, 0); + lNpcCommandList = llDeleteSubList(lNpcCommandList, 0, 0); + lNpcCommandList += next1; + // Get next command. + list data1 = llParseString2List(next1, ["="], []); + npcAction = llToLower(llStringTrim(llList2String(data1, 0), STRING_TRIM)); + npcParams = llStringTrim(llList2String(data1, 1), STRING_TRIM); + // Reschedule. + jump commands; + } + else if(npcAction == "@sound") { + DEBUG("sound"); + llTriggerSound(npcParams,1.0); + } + else if(npcAction == "@randsound") { + DEBUG("@randsound"); + integer N = llGetInventoryNumber(INVENTORY_SOUND); + integer rand = llCeil(llFrand(N)) -1; // pick a random sound + string toPlay = llGetInventoryName(INVENTORY_SOUND,rand); + llTriggerSound(toPlay,1.0); + } + else if(npcAction == "@walk") { + DEBUG("@walk"); + GetDest(npcParams); + walkstate = 1;// walking + NPCWalkOption = OS_NPC_NO_FLY ; + StateWalk(); + return; + } + else if(npcAction == "@fly") { + GetDest(npcParams); + walkstate = 2;// flying + NPCWalkOption = OS_NPC_FLY ; + StateWalk(); + return; + } + else if(npcAction == "@run") { + DEBUG("@run"); + GetDest(npcParams); + walkstate = 3;// running + NPCWalkOption = OS_NPC_NO_FLY | OS_NPC_RUNNING; + StateWalk(); + return; + } + else if(npcAction == "@land") { + DEBUG("@land"); + GetDest(npcParams); + walkstate = 4;// landing + NPCWalkOption= OS_NPC_FLY | OS_NPC_LAND_AT_TARGET ; + StateWalk(); + return; + } + else if(npcAction == "@say") { + DEBUG("@say " + npcParams); + osNpcSay(NPCKey(), 0, npcParams); + } + else if(npcAction == "@shout") { + DEBUG("@shout"); + osNpcShout(NPCKey(),0, npcParams); + } + else if(npcAction == "@whisper") { + DEBUG("@whisper " + npcParams); + osNpcWhisper(NPCKey(),0, npcParams); + } + // speak a command on a channel, so you can open doors and control stuff. + else if(npcAction == "@cmd") { + DEBUG("@cmd"); + list dataToSpeak = llParseString2List(npcParams, ["|"], []); + string channel = llList2String(dataToSpeak,0); + DEBUG("Channel:"+(string) channel); + integer iChannel = (integer) channel; + string stringToSpeak = llList2String(dataToSpeak,1); + llSay(iChannel, stringToSpeak); + } + // stop everything + else if(npcAction == "@pause") { + RAMPause = (float) npcParams; + DoPause(); + return; + } + else if(npcAction == "@wander") { + list wanderData = llParseString2List(npcParams, ["|"], []); + RAMwd = (float) llList2String(wanderData, 0); + RAMwc = (integer) llList2String(wanderData, 1); + vDestPos = osNpcGetPos(NPCKey()); // set the wander start + DEBUG("Starting at " + (string) vDestPos); + StateWander(); + return; + } + else if(npcAction == "@rotate") { + RAMrot = (float) npcParams; + DoRotate(); + } + else if(npcAction == "@sit") { + RAMsit= npcParams; + StateSit(); + return; + } + else if(npcAction == "@touch") { + RAMtouch= npcParams; + StateTouch(); + return; + } + else if(npcAction == "@stand") { + DoStand(); + } + else if(npcAction == "@delete") { + DoDelete(); + SetStop(TRUE); // Link controlled - we mnust have a @go to continue with notecards + return; + } + else if(npcAction == "@animate") { + list animateData = llParseString2List(npcParams, ["|"], []); + RAManimationName = llList2String(animateData, 0); + RAManimationTime = (float) llList2String(animateData, 1); + StateAnimate(); + return; + } + else if(npcAction == "@appearance" ){ + DoAppearance(npcParams); + } + else if (npcAction =="@speed") { + DoSpeed(npcParams); + } + else if (npcAction =="@notecard") { + DoNewNote(npcParams); + Notecard = npcParams; + } + else if (npcAction == "@attach") + { + DoAttach(npcParams); + } + else if (npcAction == "@teleport") + { + DoTeleport(npcParams); + } + else if (npcAction == "@reset") + { + DoDelete(); + SetStop(FALSE); // a @resst will restart the original !Path after deleting the notecard. + } + + STATE = 0; + TimerEvent(QUICK); // yeah I know, not possible this fast, we just go as fast as we can go - this is so we do not recurse the stack +} + + + +/////////////////// UTILITY Functions, not state-like ////////////////// + +// DEBUG(string) will chat a string or display it as hovertext if debug == TRUE +DEBUG(string str) { + if (debug && ! LSLEditor) + llOwnerSay( str); // Send the owner debug info + if (debug && LSLEditor) + llSay(0, str); // Send to the Console in LSLEDitor + if (iTitleText) { + llSetText(str,<1.0,1.0,1.0>,1.0); // show hovertext + + } +} + +GetDest(string npcParams) { + list dest = llParseString2List(npcParams, ["<", ",", ">"], []); + vDestPos.x = llList2Float(dest, 0); + vDestPos.y = llList2Float(dest, 1); + vDestPos.z = llList2Float(dest, 2); +} + +NPCReadNoteCard(string Note) { + DEBUG("NPCReadNoteCard"); + lNpcCommandList = llParseString2List(osGetNotecard(Note), ["\n"], []); +} + +integer SenseAvatar() +{ + //Returns a strided list of the UUID, position, and name of each avatar in the region + list avatars = llGetAgentList(AGENT_LIST_REGION ,[]); + integer numOfAvatars = llGetListLength(avatars); + if (numOfAvatars == 0) + { + DEBUG("No people"); + return 0; + } + //DEBUG("Located " + (string)numOfAvatars + " avatars and NPC's"); + + integer nAvatars; + integer i; + for( i = 0;i < numOfAvatars; i++) { + key aviKey = llList2Key(avatars,i); + if (!osIsNpc(aviKey)) { + list detail = llGetObjectDetails(aviKey,[OBJECT_POS]); + vector pos = llList2Vector(detail,0); + float dist = llVecDist(pos, llGetPos()); + if (dist < RANGE) + { + nAvatars++; + DEBUG("In range:" + llKey2Name(aviKey)); + } + } + } + //DEBUG("Located " + (string) nAvatars + " avatars"); + return nAvatars; +} + +// return TRUE if the avatar is owner when private is set, or TRUE if the avatar is in the same group and GROUP is set. +integer checkPerms() { + + integer group = (integer) KeyValueGet("pr"); + if (! group) + priPub = "Owner Only"; + else + priPub = "Group"; + + + if (llDetectedKey(0) == llGetOwner()){ + kUserKey = llDetectedKey(0); + return TRUE; + } + + if ( group && llDetectedGroup(0)) { + kUserKey = llDetectedKey(0); + return TRUE; + } + kUserKey = llDetectedKey(0); + return FALSE; +} + + + +NPCAnimate(string anim) +{ + DEBUG("Start Anim: " + anim); + if (llGetInventoryType(anim) == INVENTORY_ANIMATION ) { + + if (lastANIM != anim) { + if(llStringLength(lastANIM)) { + osNpcStopAnimation(NPCKey(), lastANIM); + } + osNpcPlayAnimation(NPCKey(), anim); + lastANIM = anim; + } + } else { + llSay(DEBUG_CHANNEL, "No animation named " + anim); + } +} + +// Kill a NPC by Name +Kill(string param) +{ + integer count; + list avatars = osGetAvatarList(); // Returns a strided list of the UUID, position, and name of each avatar in the region except the owner.\ + integer i; + integer j = llGetListLength(avatars); + for (i=0 ; i <= j; i+=3){ + + string desired = llList2String(avatars,i+2); + desired = llStringTrim(desired,STRING_TRIM); // should not be needed but is needed + + if (desired == param){ + vector v = llList2Vector(avatars,i+1); + key target = llList2Key(avatars,i); // get the UUID of the avatar + osNpcRemove(target); + + llOwnerSay("Removed " + param+ " at location " + (string) v); + count++; + } + } + + NPCEnabled = FALSE; // not in world + SaveKey(NULL_KEY ); // Rev 4.4 + + if (count) + llOwnerSay("Removed " + (string) count + " NPC's"); + else + llOwnerSay("Could not locate " + param); +} + + +// return a String for the position we are at. Strings used as the caller wants strings +string Pos() +{ + vector where = llGetPos(); // find the box position + + where.z += OffsetZ; // use the ground position + an offset + + if (LSLEditor) + where = <128,128,31 + llFrand(1)>; // force center of sim when editing + + // if attached the height will be too high by 1/2 the agent size + if (llGetAttached()) { + vector size = llGetAgentSize(llGetOwner()); + float Z = size.z; + where.z -= Z/2; + } + + // DEBUG("Pos= " + (string) where); + return (string) where; +} + +// setup a menu with a timer for timeouts, called by all make*() +menu() +{ + llListenRemove(iHandle); + iChannel = llCeil(llFrand(100000) + 20000); + iHandle = llListen(iChannel,"","",""); + TimerEvent(30.0); + MENU = TRUE; +} + +// make a text box +makeText(string Param) +{ + menu(); + llTextBox(kUserKey, Param, iChannel); +} + +// top level menu +makeMainMenu() +{ + menu(); + list buttons = ["Appearance","Recording","Save","Help","-","Erase RAM", priPub,relAbs,"-","Stop NPC",mSensor,"Start NPC"]; + llDialog(kUserKey,(string) llGetListLength(lCommands) + " Records",buttons,iChannel); +} + + +// Rev 1.4 +// top level menu for non group/ non owners +makeUserMenu() +{ + if (!allowUsers) return; + + menu(); + list buttons = ["Start NPC","Stop NPC"]; + llDialog(kUserKey,"Choose",buttons,iChannel); +} + + + +// programmable menu for @commands +makeMenu(list buttons) +{ + menu(); + llDialog(kUserKey,(string) llGetListLength(lCommands) + " Record",buttons,iChannel); +} + + +// make one or two text boxes with prompts +Text(string cmd, string p1, string p2) +{ + sCommand = cmd; + sParam2 = ""; + if (llStringLength(p2)) + sParam2 = p2; + + makeText(p1); +} + +// Set the Avatar Present flag - if sensors are off and we are forece run, there will be one present. +ProcessSensor() +{ + integer SensorOn; + if ("on" == KeyValueGet("se")) + { + SensorOn = TRUE; // we need to scan for avatars + } else { + SensorOn = FALSE; // we need to scan for avatars + } + DEBUG("Sensor:" + (string) SensorOn); + + integer n = SenseAvatar(); + + DEBUG("Avatars:" + (string) n); + if (SensorOn && n) + avatarPresent = TRUE; // someone is here and we need to tell the system to run + else if (SensorOn && !n) + avatarPresent = FALSE; // someone is not here and we need to tell the system to stop + else { // sensor is off, lete see if there is a NPC. If so, we are ON + DEBUG("NPCEnabled:" + (string) NPCEnabled); + if (NPCEnabled) + avatarPresent = TRUE; + else + avatarPresent = FALSE; + } + + // start up from when when no one is near + if (avatarPresent && STATE == NobodyHome) + STATE = 0; + + DEBUG("Avatar Present: " + (string) avatarPresent); +} + +vector CirclePoint(float radius) { + float x = llFrand(radius *2) - radius; // +/- radius, randomized + float y = llFrand(radius *2) - radius; // +/- radius, randomized + return ; // so this should always happen +} + +string KeyValueGet(string var) { + list dVars = llParseString2List(llGetObjectDesc(), ["&"], []); + do { + list data = llParseString2List(llList2String(dVars, 0), ["="], []); + string k = llList2String(data, 0); + if(k != var) jump continue; + //DEBUG("got " + var + " = " + llList2String(data, 1)); + return llList2String(data, 1); + @continue; + dVars = llDeleteSubList(dVars, 0, 0); + } while(llGetListLength(dVars)); + return ""; +} + +KeyValueSet(string var, string val) { + + //DEBUG("set " + var + " = " + val); + list dVars = llParseString2List(llGetObjectDesc(), ["&"], []); + if(llGetListLength(dVars) == 0) + { + llSetObjectDesc(var + "=" + val); + return; + } + list result = []; + do { + list data = llParseString2List(llList2String(dVars, 0), ["="], []); + string k = llList2String(data, 0); + if(k == "") jump continue; + if(k == var && val == "") jump continue; + if(k == var) { + result += k + "=" + val; + val = ""; + jump continue; + } + string v = llList2String(data, 1); + if(v == "") jump continue; + result += k + "=" + v; + @continue; + dVars = llDeleteSubList(dVars, 0, 0); + } while(llGetListLength(dVars)); + if(val != "") result += var + "=" + val; + llSetObjectDesc(llDumpList2String(result, "&")); +} + + +// clear RAM +Clr() { + + lCommands = []; + llOwnerSay("RAM Memory cleared. Notecards, if any, are not modified."); + makeMainMenu(); +} + +integer checkNoteCards() +{ + // Check that they have saved an Appeaance and Path notecard + integer num = llGetInventoryNumber(INVENTORY_NOTECARD); // how many notecards overall + + integer i; + integer count; + for (; i < num; i++){ + if (llGetInventoryName(INVENTORY_NOTECARD,i) == Notecard) + count++; + if (llGetInventoryName(INVENTORY_NOTECARD,i) == Appearance) + count++; + } + DEBUG("Checked " + (string) count + " Notecards"); + // if we have both, run the NPC + return count; +} + +Update(string SName) { + + // delete all NPC* scripts except myself + integer i; + integer j = llGetInventoryNumber(INVENTORY_SCRIPT); + for (i = 0; i < j; i++) { + string targetName = llGetInventoryName(INVENTORY_SCRIPT,i); + string match = llGetSubString(targetName,0,2); + + if (match == SName && llGetScriptName() != targetName){ + llOwnerSay("Upgrading " + targetName); + if (! LSLEditor){ // lets not kill the editor + llRemoveInventory(targetName); + } + } + } +} + +// Get all default saved params from the Description +GetSwitches() +{ + string rA = KeyValueGet("co"); // Get the remembered menu setting for Abs Vs Relative + if (rA == "A") + relAbs = "Absolute"; + else if (rA == "R") + relAbs = "Relative"; + else + relAbs = "Absolute"; + + + // reenable NPC if sensor is on. + if ("on" == KeyValueGet("se")) + { + NPCEnabled = TRUE; + mSensor = "Sense is On"; + ProcessSensor(); // fake 1 avatar to get it rezzed + } else { + mSensor = "Sense is Off"; + } + } + + +SaveKey(key akey) +{ + DEBUG("Saving Key of " + (string) akey); + KeyValueSet("key", akey); + if (akey != (key) KeyValueGet("key") ) + { + DEBUG("Fatal error, cannot save key"); + } + gNpcKey = akey; +} + + +key NPCKey() +{ + key akey = gNpcKey; // from cached copy + // gNpcKey saves a lot of CPU processing by caching the key, if blank we get it from the description + if (gNpcKey == NULL_KEY) + { + //DEBUG("Get DKey"); + akey = KeyValueGet("key"); // from Description of the prim + } + // DEBUG("NPC KEY:" + (string) akey); + return akey; +} + + +/////////////////// CODE BEGINS ////////////////// + + +default +{ + changed(integer change) { + if(change & CHANGED_REGION_START) { + llResetScript(); + } + } + + on_rez(integer start_param) + { + llResetScript(); + } + + state_entry() { + + llSetText("",<1,1,1>,1.0); // clr all hovertext- we may not be using it. + DoDelete(); // kill any NPC that is out running + Update("NPC"); // If dragged and ropped into a prim with any script named "NPC...", this will replace it. + GetSwitches(); // Get all default saved params from the Description + + // 4.1 allow listeners to send us commands + if (allowListener) + llListen(link_Channel,"","",""); + TimerEvent(TIMER); + } + + + touch_start(integer n) + { // if touched, make a menu + + if (checkPerms()) { + if (RecordPath == STATE) { + makeMenu(lAtButtons); + } else { + makeMainMenu(); + } + } else { + makeUserMenu(); + } + } + + // menu listener + listen(integer iChannel, string name, key id, string message) { + + // process @commands that come in via the listener + if (iChannel == link_Channel) + { + ParseMsg(message); + return; + } + + if (MENU) { + llListenRemove(iHandle); + MENU = 0; // menu is off + iHandle = 0; + } + + if (message == "Stop NPC") + { + lNpcCommandList = []; // force reload of notecard + NPCEnabled = FALSE; + if (NPCKey() != NULL_KEY){ + Kill(sNPCName); + sNPCName = ""; + } else { + bNPC_STOP = TRUE; + makeText("Enter name of an NPC to stop"); + } + } + else if (message == "Menu" ) { + makeMainMenu(); + } + else if (message == "Erase RAM"){ + Clr(); + } + else if (message == "Relative"){ + relAbs = "Absolute"; + KeyValueSet("co","A"); // remember coordinates = A + Clr(); + } + else if (message == "Absolute"){ + relAbs = "Relative"; + KeyValueSet("co","R"); // remember coordinates = R + Clr(); + } + else if (message == "Recording"){ + DoMenuForCommands(); // show them the recording menu + } + else if (message == "Owner Only") { + priPub = "Group"; + KeyValueSet("pr","1"); + + llOwnerSay("Group members have control"); + makeMainMenu(); + } + else if (message == "Group") { + priPub = "Owner Only"; + KeyValueSet("pr","0"); + llOwnerSay("Only you have control"); + makeMainMenu(); + } + else if (message == "Sense is On") { + mSensor ="Sense is Off"; + KeyValueSet("se", "off"); + llOwnerSay(mSensor); + makeMainMenu(); + } + else if (message == "Sense is Off") { + mSensor ="Sense is On"; + llOwnerSay(mSensor); + KeyValueSet("se", "on"); + + NPCEnabled = FALSE; + + integer count = checkNoteCards(); + if (count >= 2) { + DEBUG("Notecards ok, DoProcessNPCLine"); + DoProcessNPCLine(); + return; + } + if (LSLEditor) { + DoProcessNPCLine(); + return; + } + + llOwnerSay("You have not saved a recording and/or appearance, so you cannot start a NPC"); + makeMainMenu(); + } + else if (message == "Appearance") { + llRemoveInventory(Appearance); // delete the notecard + osAgentSaveAppearance(kUserKey,Appearance); // make the ntecard + llOwnerSay("Your outfit has been saved"); + makeMainMenu(); + } + else if (message == "Save") { + if (llGetListLength(lCommands) == 0) { + llOwnerSay("Nothing recorded, you need to make a recording first"); + makeMainMenu(); + return; + } + DoSave(); + } + else if (message == "Help"){ + llLoadURL(kUserKey,"Click to view help","http://www.outworldz.com/opensim/posts/NPC/"); + makeMainMenu(); + } + else if (message == "Start NPC") { + integer count = checkNoteCards(); + + NPCEnabled = TRUE; + + if (LSLEditor) { + DoProcessNPCLine(); + return; + } + + if (count >= 2) { + DEBUG("Notecards approved , calling DoProcessNPCLine"); + SetStop(FALSE); // Let's run the notecard + DoProcessNPCLine(); + return; + } + + llOwnerSay("You have not saved a recording or maybe an appearance, so we cannot start a NPC"); + + } + else if (bNPC_STOP){ + bNPC_STOP = FALSE; + Kill(message); + } + else if (message == ">>"){ + makeMenu(lMenu2); + } + else if (message == ">>>"){ + makeMenu(lMenu3); + } + else if (message == "<<") { + makeMenu(lAtButtons); + } + else if (message == "<<<") { + makeMenu(lMenu2); + } + else if (message == "@comment"){ + Text("# ","Enter a comment",""); + } + else if (message == "@stop"){ + lCommands += "@stop"+ "\n"; + makeMenu(lAtButtons); + } + else if (message == "@run"){ + lCommands += "@run=" + Pos() + "\n"; + llOwnerSay("Recorded position: " + Pos()); + makeMenu(lAtButtons); + } + else if (message == "@fly"){ + lCommands += "@fly=" + Pos() + "\n"; + llOwnerSay("Recorded position: " + Pos()); + makeMenu(lAtButtons); + } + else if (message == "@land"){ + lCommands += "@land=" + Pos() + "\n"; + llOwnerSay("Recorded position: " + Pos()); + makeMenu(lAtButtons); + } + else if (message == "@walk") { + lCommands += "@walk=" + Pos() + "\n"; + llOwnerSay("Recorded position: " + Pos()); + makeMenu(lAtButtons); + } + else if (message == "@stop"){ + lCommands += "@stop"+ "\n"; + makeMenu(lAtButtons); + } + else if (message == "@sound"){ + Text("@sound=","Enter a sound name or UUID to trigger",""); + } + else if (message == "@randsound"){ + lCommands += "@randsound"+ "\n"; + makeMenu(lAtButtons); + } + else if (message == "@say") { + Text("@say=","Enter what the NPC will say",""); + } + else if (message == "@whisper"){ + Text("@whisper=","Enter what the NPC will whisper",""); + } + else if (message == "@shout"){ + Text("@shout=","Enter what the NPC will shout",""); + } + else if (message == "@wander") { + Text("@wander=","Enter radius to wander","Enter number of wanders"); + } + else if (message == "@pause") { + Text("@pause=","Enter time to pause",""); + } + else if (message == "@rotate") { + Text("@rotate=","Enter degrees to rotate",""); + } + else if (message == "@sit"){ + Text("@sit=","Enter name of object to sit on",""); + } + else if (message == "@teleport"){ + lCommands += "@teleport=" + Pos() + "\n"; + llOwnerSay("teleport to position: " + Pos()); + makeMenu(lMenu3); + } + else if (message == "@touch"){ + Text("@touch=","Enter name of object to touch",""); + } + else if (message == "@cmd"){ + Text("@cmd=","Enter cjhannel to speak on","Enter text to speak"); + } + else if (message == "@stand"){ + lCommands += "@stand\n"; + llOwnerSay("Stand Recorded"); + makeMenu(lAtButtons); + } + else if (message == "@animate"){ + Text("@animate=","Enter animation name to play","Enter time to play the animation"); + } + else if (message == "@attach"){ + Text("@animate=","Enter inventory name to attach","Enter number of the attachment point (1-40)"); + } + else if (message == "@speed"){ + Text("@speed=","Enter a speed for the NPC, 1=100% normal speed, 0.5=50% speed",""); + } + + + // Save NPC name + else if (MakeNotecard == STATE) { + sNPCName = message; // in case we need to kill it. + + vector vDest = (vector) Pos(); + + if (relAbs == "Relative") + { + vDest -= llGetPos(); // just an offset for relative + } + sNotecard = "@spawn=" + message + "|" + (string) vDest + "\n"; + integer i; + integer j = llGetListLength(lCommands); + for (; i < j; i++){ + // get the command to save to the notecard + string line = llList2String(lCommands,i); + if (relAbs == "Absolute") { + sNotecard += line; // add the un-modified string to the notecard + } else { + // since we have to record absolute coords since we do not know where the box goes until they press Save, + // we process the absolute to relative conversion for walks here + list parts = llParseString2List(line,["="],[]); //get the @command + + if (llList2String(parts,0) == "@walk") { + vector vec = (vector) llList2String(parts,1) - llGetPos(); + sNotecard += "@walk=" + (string) vec + "\n"; + } + else if (llList2String(parts,0) == "@fly") { + vector vec = (vector) llList2String(parts,1) - llGetPos(); + sNotecard += "@fly=" + (string) vec + "\n"; + } + else if (llList2String(parts,0) == "@run") { + vector vec = (vector) llList2String(parts,1) - llGetPos(); + sNotecard += "@run=" + (string) vec + "\n"; + } + else if (llList2String(parts,0) == "@land") { + vector vec = (vector) llList2String(parts,1) - llGetPos(); + sNotecard += "@land=" + (string) vec + "\n"; + } + else { + sNotecard += line; // add the un-modified string to the notecard + } + } + } + llRemoveInventory(Notecard); // delete the old notecard + osMakeNotecard(Notecard,sNotecard); // Makes the notecard. + llSay(0,sNotecard); + llOwnerSay("Commands notecard has been written"); + STATE = 0; + } // MakeNotecard + + else if (! llStringLength(sParam2)) { + lCommands += sCommand + message + "\n"; + llOwnerSay("Recorded"); + makeMenu(lAtButtons); + } + else if (llStringLength(sParam2)){ + sCommand = sCommand + message + "|"; + llOwnerSay("Recorded"); + makeText(sParam2); + sParam2 = ""; + } + + } + + + + timer(){ + // DEBUG("tick"); + + // if llDialog is up, kill the listener for the dialog box. + if (iHandle) { + llOwnerSay("Menu timed out"); + llListenRemove(iHandle); + iHandle = 0; + return; // ^^^^^^^^^^^^^^^^^^^^^^^ + } + // if NoBodyHome, we are sensing for an avatar + else if (NobodyHome == STATE) { + ProcessSensor(); + return; + } + // if we are spawning, we need time to rez the NPC, then start processing NPC Commands. + else if (Spawning == STATE) { + STATE = 0; + TimerEvent(TIMER); + } + // We end aniamtions with a timer + else if (Animate == STATE){ + NPCAnimate(STAND); + TimerEvent(TIMER); + } + + else if (Walking == STATE) { + if (--iWaitCounter) { + if (llVecDist(osNpcGetPos(NPCKey()), newDest) > MAXDIST) { + return; + } + } + + DEBUG("At Destination: " + (string) newDest); + + // walk, fly, run, land + if (walkstate == 1) { + NPCAnimate(STAND); + NPCWalkOption = OS_NPC_NO_FLY; + } else if (walkstate == 2) { + // nothing + } else if (walkstate == 3) { + NPCAnimate(STAND); + NPCWalkOption = OS_NPC_NO_FLY; + } else if (walkstate == 4) { + llShout(FLIGHT,"landing"); + NPCAnimate(STAND); + NPCWalkOption = OS_NPC_NO_FLY; + } + } + // Wandering timer + else if (Wander == STATE) { + if (--iWaitCounter) { // wait 60 seconds to get to a destination. + if (llVecDist(osNpcGetPos(NPCKey()), vWanderPos) > MAXDIST) + return; + } + + // see if wander counter == 0, if so, stop walking, go to stand and process next line + if(RAMwc == 0) { + NPCAnimate(STAND); + DEBUG("Wander ended, calling DoProcessNPCLine"); + STATE = 0; + DoProcessNPCLine(); + return; + } + // one less time to wander around + RAMwc--; + NPCAnimate(STAND); + TimerEvent(TIMER); + StateWanderhold(); + return; + } + // Wandering requires us to re-wander when we reach a destination + else if (WanderHold == STATE) { + StateWander(); + TimerEvent(TIMER); + return; + } + else if (DoProcess == STATE) { + TimerEvent(QUICK); + } + + + STATE = 0; + + // We always process a NPC line at end of timer. + DEBUG("Tick end, calling DoProcessNPCLine"); + DoProcessNPCLine(); + } + + // sensors are used for sitting on prims + // Neo Cortex: added different SensorFunc states to trigger sit or touch + sensor(integer num) { + if (SensorFunc == 1) { + osNpcSit(NPCKey(), llDetectedKey(0), OS_NPC_SIT_NOW); + DEBUG("Seated, calling DoProcessNPCLine"); + SensorFunc = 0; + } else if (SensorFunc == 2) { + osNpcTouch(NPCKey(), llDetectedKey(0), LINK_THIS); + DEBUG("Touched, calling DoProcessNPCLine"); + SensorFunc = 0; + } + DoProcessNPCLine(); + } + no_sensor(){ + DEBUG ("no target prim located, calling DoProcessNPCLine"); + SensorFunc = 0; + DoProcessNPCLine(); + } + + + link_message(integer sender, integer num, string str, key id){ + ParseMsg(str); + } + +} + + + + + + diff --git a/HyperGrid Story Nine/BackupHypergrid Story Nine/Console/Direction button.lsl b/HyperGrid Story Nine/BackupHypergrid Story Nine/Console/Direction button.lsl new file mode 100644 index 00000000..8f248e2d --- /dev/null +++ b/HyperGrid Story Nine/BackupHypergrid Story Nine/Console/Direction button.lsl @@ -0,0 +1,23 @@ +//:AUTHOR: Ferd Frederix +//:DESCRIPTION: +// Button Script for console +//:CODE: + +string E = "<1,0,0>"; +string NE = "<1,1,>"; +string N = "<0,1,0>"; +string NW = "<-1,1,0>"; +string W = "<-1,0,0>"; +string SW = "<-1,-1,0>"; +string S = "<0,-1,0>"; +string WE = "<1,-1,0>"; + +// chose a direction for one of 8 buttons. + +default +{ + touch_start(integer total_number) + { + llMessageLinked(LINK_SET,0,N,""); + } +} \ No newline at end of file diff --git a/HyperGrid Story Nine/BackupHypergrid Story Nine/Console/Doorway.lsl b/HyperGrid Story Nine/BackupHypergrid Story Nine/Console/Doorway.lsl new file mode 100644 index 00000000..0c60be5f --- /dev/null +++ b/HyperGrid Story Nine/BackupHypergrid Story Nine/Console/Doorway.lsl @@ -0,0 +1,39 @@ +// :SHOW:1 +// :CATEGORY:Gaming +// :NAME:Collider for All in One NPC Controller +// :AUTHOR:Ferd Frederix +// :KEYWORDS:Game, Collider +// :REV:2.0 +// :WORLD:OpenSim +// :DESCRIPTION: +// Triggers the NPC controller to play the Greet notecard when collided. +// :CODE: + +string message = "@notecard=!Path"; + +Reset() { + llSetStatus(STATUS_PHANTOM, FALSE); // rev 2.0 + llVolumeDetect(FALSE); + llSleep(0.1); + llVolumeDetect(TRUE); +} + +default{ + state_entry(){ + Reset(); + } + collision_start(integer n){ + if (osIsNpc(llDetectedKey(0))){ + return; + } + llMessageLinked(LINK_SET,1,message,""); // 1 ios for doorway only + } + on_rez(integer p){ + llResetScript(); + } + changed(integer what){ + if (what & CHANGED_REGION_START){ + llResetScript(); + } + } +} \ No newline at end of file diff --git a/HyperGrid Story Nine/BackupHypergrid Story Nine/Console/N&D controller.lsl b/HyperGrid Story Nine/BackupHypergrid Story Nine/Console/N&D controller.lsl new file mode 100644 index 00000000..2995332b --- /dev/null +++ b/HyperGrid Story Nine/BackupHypergrid Story Nine/Console/N&D controller.lsl @@ -0,0 +1,55 @@ +integer busy ; +integer Helmet_Channel = 576; +key NamakasKey; +float MaxBeam = 2.0; // how far the helmet beam happens +vector currPos(); // Namakas current position; +vector centerPoint = <128,128,32>; // the center of where Namaka can walk +list avatarPos = [<0,1,1>,<2,2,2>,<3,3,3> ]; // all 6 avatars are here + +default +{ + state_entry() + { + + } + + + link_message(integer sender_number, integer number, string message, key id) + { + // -1 is Namakas key + // 0 is for NPC direction commands + // 1 is for doorway + // 2 is the helment beam channel + + if (number == -1){ + NamakasKey = id; + + } else if (number == 0){ + if (! busy) { + llMessageLinked(LINK_SET,1,message); + } + } else if (number == 1) { + vector direction = (vector) message; + + + + } else if (number == 2){ + // GO + llShout(Helmet_Channel,"BOOM"); + + integer i; + integer j = llGetListLength(avatarPos); + for (i = 0; i < j; i++) + { + float dist = llVecDist(llList2Vector(avatarPos,i),centerPoint); + if (dist > MaxBeam) { + ChangeNPC(); + ToDo --; + if (ToDo == 0) { + Win(); + } + } + } + } + } +} \ No newline at end of file diff --git a/HyperGrid Story Nine/BackupHypergrid Story Nine/Console/Red Button.lsl b/HyperGrid Story Nine/BackupHypergrid Story Nine/Console/Red Button.lsl new file mode 100644 index 00000000..6df40625 --- /dev/null +++ b/HyperGrid Story Nine/BackupHypergrid Story Nine/Console/Red Button.lsl @@ -0,0 +1,14 @@ +//:AUTHOR: Ferd Frederix +//:DESCRIPTION: +// Button Script for console +//:CODE: + +// chose a direction for one of 8 buttons. + +default +{ + touch_start(integer total_number) + { + llMessageLinked(LINK_SET,2,"go",""); + } +} \ No newline at end of file diff --git a/HyperGrid Story Nine/BackupHypergrid Story Nine/Console/Sequence.txt b/HyperGrid Story Nine/BackupHypergrid Story Nine/Console/Sequence.txt new file mode 100644 index 00000000..48851f60 --- /dev/null +++ b/HyperGrid Story Nine/BackupHypergrid Story Nine/Console/Sequence.txt @@ -0,0 +1,10 @@ + +dylan|1|@animate=SwayInBreeze|2 +dylan|2|@animate=avatar_type|2 +dylan|8|@say=I think this is the place where we can transform back to our original selves + +namaka|2|@animate=avatar_type|3 +namaka|1|@say=you spoke too soon, my love, there is no exit from this area. + +dylan|1|@animate=avatar_type|10 +dylan|2|@say=We must ask you again to help us. Perhaps pressing that button will do something. diff --git a/HyperGrid Story Nine/BackupHypergrid Story Nine/Console/Sequencer.lsl b/HyperGrid Story Nine/BackupHypergrid Story Nine/Console/Sequencer.lsl new file mode 100644 index 00000000..c8aafe46 --- /dev/null +++ b/HyperGrid Story Nine/BackupHypergrid Story Nine/Console/Sequencer.lsl @@ -0,0 +1,224 @@ +// :SHOW:1 +// :CATEGORY:Gaming +// :NAME:Sequencer and Collider for All in One NPC Controller +// :AUTHOR:Ferd Frederix +// :KEYWORDS:Game, Collider +// :REV:1.0 +// :WORLD:Opensim +// :DESCRIPTION: +// Sequencer script for NPC animator for HyperGrid Story nine sim. It sequences multiple NPCs in order thru a scenarios +// each 'things' entry is the NPC number, a (float) time to take between sending commands ( 0 is not allowed, but a small number is() +///and a @command that is sent to the NPC. + +// :CODE: +// Notes: Link messages arte as follows + + +// Rev: 2 fixes the Linux bug for collisions. +// +integer debug = 1; // set to TRUE or FALSE for debug chat on various actions + +// list of NPCs and their linked prim numbers +list npcName = ["namaka",2,"dylan",3,"npc1",4,"npc2",5,"npc3",6,"npc4",7,"npc5",8,"npc6",9]; + +integer CommandChannel = 23; + +// DEBUG(string) will chat a string or display it as hovertext if debug == TRUE + +DEBUG(string str) { + if (debug ==1 ) + llSay(0, llGetScriptName() + ":" + str); + if (debug ==2 ) + llSetText(str,<1,1,1>,1.0); +} + +integer osIsNpc(key id){ + return FALSE; +} + +string osGetNotecard(string name) { + // sample notecard for testing + string str = "NPC #| seconds | Command\n" + +"npc1|0|@pause=1\n" + +"sylan|0|@animate=avatar_jumpforjoy|5\n" + +"namaka|0|@say=Hello! What happened to you two?\n" + +"Derf|0|@say=error\n" + +"dylan|2|@animate=avatar_type|2\n" + +"dylan|8|@say=I think we can transform back to our original selves, at last.\n"; + + return str; +} + + +integer SENSE = FALSE; // sensor for an avatar +float RATE = 5; // every 5 seconds +integer COLLIDE = TRUE; // if they collide, trigger the sequence +float RANGE = 96; // a very short range as this goes into a ring around the avatar +integer Rezzed; + +list things ; +// CODE follows + + +Send(string who, string cmd) +{ + DEBUG("Sending to " + who + "(" + (string) lookup(who) + ") the command " + cmd); + llMessageLinked(lookup(who),0,cmd,""); +} + +integer lookup(string Name) +{ + integer i = llListFindList(npcName,[Name]); + if (i != -1) + return llList2Integer(npcName,i+1); + return 0; +} + +// For rezzing in +RezIn() +{ + DEBUG("Rezz In = @reset"); + llMessageLinked(LINK_SET,0,"@reset",""); +} + + +DoIt() +{ + + string note = osGetNotecard("Sequence"); + list stuff= llParseString2List(note,["\n"],[]); + + DEBUG("stuff:" + llDumpList2String(stuff,":")); + integer i; + integer j = llGetListLength(stuff); + for (i=0; i < j; i++) + { + string noteline = llList2String(stuff,i); + DEBUG("noteline:" + noteline); + + list line = llParseString2List(noteline,["|"],[]); + string primName = llList2String(line,0); + float sleepTime = llList2Integer(line,1); + string atCmd = llList2String(line,2); + string param1= llList2String(line,3); + string param2= llList2String(line,4); + + DEBUG("prim:" + primName); + DEBUG("num:" + (string) lookup(primName)); + DEBUG("time:" + (string) sleepTime); + DEBUG("atCmd:" +atCmd); + if (lookup(primName) && llSubStringIndex(atCmd,"@") == 0) + { + things += lookup(primName); + things += sleepTime; + things += atCmd+"|" + param1 + "|" + param2; + } + } + DEBUG("Things:" + llDumpList2String(things,":")); +} + + + + +Speak() { + + integer prim = llList2Integer(things,0); + float time = llList2Float(things,1); + string msg = llList2String(things,2); + DEBUG("Prim:" + (string) prim + " time:" + (string) time + " Msg:" + msg); + if (llStringLength(msg)) { + things = llDeleteSubList(things,0,2); + DEBUG("LINK:" +(string) prim + ":" + msg); + llMessageLinked(prim,0, msg,""); + if (time > 0) { + llSetTimerEvent(time); + } else { + llSetTimerEvent(0.1); + } + } else { + DEBUG("Done"); + + Rezzed = 0; + + llSetTimerEvent(0); + if (SENSE) + llSensorRepeat("","",AGENT,RANGE,PI,RATE); + } +} + + + +default +{ + state_entry() + { + llSetText("",<1,1,1>,1.0); + + RezIn(); + + llSetTimerEvent(0); + if (SENSE) + llSensorRepeat("","",AGENT,RANGE,PI,RATE); + + llListen(CommandChannel,"","",""); + } + + touch_start(integer p) { + DEBUG("touched"); + if (llGetOwner() == llDetectedKey(0)) + { + Send("Claire","@say=touched"); + } + } + + listen(integer channel, string name, key id, string message) + { + DEBUG(message); + list vars = llParseString2List(message,["|"],[]); + string num = llList2String(vars,0); + string cmd = llList2String(vars,1); + + Send(num, cmd); + + } + + sensor(integer n) { + DEBUG("Sensed"); + + if (! osIsNpc(llDetectedKey(0))) { + DEBUG("Sensed avatar"); + llSensorRemove(); + RezIn(); + } + } + + + timer() + { + Speak(); + } + + collision_start(integer n) { + DEBUG("Collided with " + llKey2Name(llDetectedKey(0))); + + if (! osIsNpc(llDetectedKey(0))) + { + DoIt(); + Speak(); + } + + } + + on_rez(integer p) + { + llResetScript(); + } + + changed(integer what) + { + if (what & CHANGED_REGION_START) + { + llResetScript(); + } + } +} \ No newline at end of file diff --git a/HyperGrid Story Nine/BackupHypergrid Story Nine/Timer/Shins Script.lsl b/HyperGrid Story Nine/BackupHypergrid Story Nine/Timer/Shins Script.lsl new file mode 100644 index 00000000..90585c66 --- /dev/null +++ b/HyperGrid Story Nine/BackupHypergrid Story Nine/Timer/Shins Script.lsl @@ -0,0 +1,265 @@ +//HYPERGRID STORY NINE +// List NPC Listener channels +//sara=11 sammy=12 claire=13 jolinda=14 tiny=15 tiny2=16 fireworks=20 + +integer gListener; // Listener for handling different channels +integer gSimpleMenuChannel; // The channel used for the menu +float gTimerInterval = 1; // Keep this at 1 for your sanity. +list gTimeoutList; +float gTimeElapsed=0; +integer gAutofire=FALSE; +//============================================================================ +vector gFacecolor=<1.0,0.0,0.0>; //RED +integer gNewTime; +integer gOldTime; +//==Function that returns a random number for our menu handler channel +integer randomchannel() { + return((integer)(llFrand(99999.0)*-1)); + } + +menu(key id, integer channel, string title, list buttons) { + llListenRemove(gListener); + gListener = llListen(channel,"",id,""); + llDialog(id,title,buttons,channel); + // TimerEvent for killing the menu listener + settimeout("untouched", 30); //call untouched timeout + } + +simplemenu(key id) { + gSimpleMenuChannel = randomchannel(); + menu(id,gSimpleMenuChannel,"Select an option",["FIREWORKS","SARA","SAMMY","CLAIRE","JOLINDA","TINY","TINY2","LeadNPC","Finale!","CeaseFire"]); + } + +settimeout(string timereventid, float time) { + if(gTimeoutList == []) + llSetTimerEvent(gTimerInterval); + integer identifyerIndex = llListFindList(gTimeoutList, [timereventid]); + if (identifyerIndex != -1) + gTimeoutList = llDeleteSubList(gTimeoutList, identifyerIndex - 1, identifyerIndex); + if (time != 0) { + gTimeoutList += time + gTimeElapsed; + gTimeoutList += timereventid; + } + gTimeoutList = llListSort(gTimeoutList, 2, TRUE); + } + +timeout(string timereventid) +{ + if (timereventid == "untouched") + { + llSay(0, "I have been untouched for 30 seconds, killing menu listener!"); + llListenRemove(gListener); // kill the listener after 30 seconds inactivity + } + else if (timereventid == "countdown2execute") + { + float seconds2count = 1800; + llSay(0, "I have counted down " + (string)seconds2count + " seconds, repeat countdown and start the show"); + settimeout("countdown2execute", seconds2count); //call it again to loop. + } + else if (timereventid == "autofire") + { + if(gAutofire) + { + llSay(0,"should fire every 5 seconds."); + llRegionSay(20,"Go"); + settimeout("autofire", 5); + } + } + if (timereventid == "stopautofire") + { + gAutofire=FALSE; + } +} +timertick() +{ + gTimeElapsed += gTimerInterval; + integer i; + integer numTimers = llGetListLength(gTimeoutList); + for (i = 0; i < numTimers; i += 2) + { + float triggerTime = llList2Float(gTimeoutList, i); + if (triggerTime <= gTimeElapsed) + { + string timereventid = llList2String(gTimeoutList, i + 1); + gTimeoutList = llDeleteSubList(gTimeoutList, i, i + 1); + timeout(timereventid); + if (timereventid=="countdown2execute") + { + displaycountdowntext((integer)gTimeElapsed); + getdigit((integer)gTimeElapsed); + } + if (gTimeoutList == []) + llSetTimerEvent(0); + } + else + { + return; + } + } +} + +displaydigit(integer n, string d){ + list Facelist; //list depends on your mesh digital readout face number. + integer i; + if(n==0) Facelist=[ 1,1,1,1,0,1,1 ]; //index @0 | 7 faces per digit + if(n==1) Facelist=[ 0,0,1,0,0,1,0 ]; + if(n==2) Facelist=[ 1,1,0,1,1,1,0 ]; + if(n==3) Facelist=[ 1,1,1,0,1,1,0 ]; + if(n==4) Facelist=[ 0,0,1,0,1,1,1 ]; + if(n==5) Facelist=[ 1,1,1,0,1,0,1 ]; + if(n==6) Facelist=[ 0,1,1,1,1,0,1 ]; + if(n==7) Facelist=[ 1,0,1,0,0,1,0 ]; + if(n==8) Facelist=[ 1,1,1,1,1,1,1 ]; + if(n==9) Facelist=[ 1,0,1,0,1,1,1 ]; + + integer l = llGetLinkNumber() != 0; + integer x = llGetNumberOfPrims() + l; + + for (; l < x; ++l){ + if (llGetLinkName(l) == d){ + for (i=0;i<7;i++){ + llSetLinkPrimitiveParamsFast(l,[PRIM_COLOR,i,gFacecolor,llList2Float(Facelist,i)]); + } + } + } +} + +getdigit(integer time){ +string dOne; +string dTen; +string dHundred; +string dThousand; + +integer i; + + if (time<10){ + dOne=(string)time; + dTen="0"; + dHundred="0"; + dThousand="0"; // add your digits here + }else if(time<100){ + dOne=llGetSubString((string)time,-1,-1); + dTen=llGetSubString((string)time,0,0); + dHundred="0"; + dThousand="0"; + }else if(time<1000){ + dOne=llGetSubString((string)time,-1,-1); + dTen=llGetSubString((string)time,1,1); + dHundred=llGetSubString((string)time,0,0); + dThousand="0"; + }else if(time<10000){ + dOne=llGetSubString((string)time,-1,-1); + dTen=llGetSubString((string)time,2,2); + dHundred=llGetSubString((string)time,1,1); + dThousand=llGetSubString((string)time,0,0); + } + displaydigit((integer)dOne, "one"); + displaydigit((integer)dTen, "ten"); + displaydigit((integer)dHundred, "hundred"); + displaydigit((integer)dThousand, "thousand"); +} + +displaycountdowntext(integer time){ + gNewTime = time; + if (gOldTime < gNewTime){ + llSetText("Elapsed Ticker: " + (string)gNewTime + " ticks",<1,1,.6>,1.0); + gOldTime = gNewTime; + } +} + +default +{ + state_entry() + { + float seconds2count = 3600; + settimeout("countdown2execute", seconds2count); + + } + touch_start(integer num) + { + simplemenu(llDetectedKey(0)); + } + + listen (integer channel, string name, key id, string message) + { + if (message == "FIREWORKS") { + llSay(0,"You selected option FIREWORKS"); + llRegionSay(20,"Go"); + //do something else here + } + else if (message == "SARA") + { + llSay(0,"You selected option SARA"); + llRegionSay(11,"@go"); + //do something else here + } + else if (message == "SAMMY") + { + llSay(0,"You selected option SAMMY"); + llRegionSay(12,"@go"); + //do something else here + } + else if (message == "CLAIRE") + { + llSay(0,"You selected option CLAIRE"); + llRegionSay(13,"@go"); + //do something else here + } + else if (message == "JOLINDA") + { + llSay(0,"You selected option JOLINDA"); + llRegionSay(14,"@go"); + //do something else here + } + else if (message == "TINY") + { + llSay(0,"You selected option TINY"); + llRegionSay(15,"@go"); + //do something else here + } + else if (message == "TINY2") + { + llSay(0,"You selected option TINY2"); + llRegionSay(16,"@go"); + //do something else here + } + else if (message == "LeadNPC") + { + llSay(0,"You selected option LeadNPC"); + llRegionSay(10,"@go"); + //do something else here + } + else if (message == "Finale!") + { + llSay(0,"You selected option Finale!"); + gAutofire=TRUE; + settimeout("autofire", 1); + settimeout("stopautofire", 90); + //do something else here + } + else if (message == "CeaseFire") + { + llSay(0,"You selected option CeaseFire"); + gAutofire=FALSE; + //do something else here + } + simplemenu(id); // refresh menu + } + + timer() + { + timertick(); + integer MaxTicks = 1800; //Ticks to countdown from + //limit the ticker to maximum ticks + if(gTimeElapsed >= MaxTicks){ + gTimeElapsed=0; + } + integer CountDown = MaxTicks - (integer)gTimeElapsed; + integer mm = (CountDown / 60) * 100; + integer ss = (CountDown % 60); + integer cc = mm + ss; + displaycountdowntext((integer)gTimeElapsed); + getdigit((integer)cc); + + } +} \ No newline at end of file diff --git a/HyperGrid Story Nine/BackupHypergrid Story Nine/Timer/Timer.lsl b/HyperGrid Story Nine/BackupHypergrid Story Nine/Timer/Timer.lsl new file mode 100644 index 00000000..cdce881e --- /dev/null +++ b/HyperGrid Story Nine/BackupHypergrid Story Nine/Timer/Timer.lsl @@ -0,0 +1,395 @@ +//:Name: HYPERGRID STORY NINE +//:Author: Shin Ingen +//:REV:1.0 +//:DESCRIPTION: +// Timer controller +//:CODE: +// lots of mods by Ferd +// + + +integer debug = 2; + +// Link Numbers of the NPC controller +integer namaka =2; +integer dylan =3; +integer npc1 =4; +integer npc2 =5; +integer npc3 =6; +integer npc4 =7; +integer npc5 =8; +integer npc6 =9; + +// List of NPC Listener channels + +integer gFireworksChannel =20; +integer gTeleportChannel = 21; +integer CommandChannel = 23; +integer gSimpleMenuChannel = 999; // The channel used for the menu + +integer MaxTicks = 60; // Show length +// The repeat the show timer + +list kDance1 = ["Dance1",1]; // Dance +list kDance2 = ["Dance2",10]; // Dance +list kDance3 = ["Dance3",20]; // Dance +list kDance4 = ["Dance4",30]; // Dance +list kDance5 = ["Dance5",40]; // Dance +list kDance6 = ["Dance6",50]; // Dance + +list kFireworksRepRate = ["Shoot", 2]; // rep rate for finale + +// the following are calculated based on the current timer tick +list kFireworksStartsAt = ["StartFireworks", 99999]; // rep rate for finale +list kRaiseTeleport = ["BeamMeUp",99999]; // raise the teleports +list kStopAutoFire = ["EndFireworks",99999]; // and stops here + +SendMessageLinked(integer npc, string cmd) +{ + llRegionSay(CommandChannel,(string) npc + "|" + cmd); +} +DoCmd(string message, integer channel) +{ + if (channel == CommandChannel) { + Process(message); + } + + if (message == "ShowTime") { + + DEBUG("Running"); + ShowTime(); + } + else if (message == "Finale!") + { + kFireworksStartsAt = ["StartFireworks", gTimeElapsed+1]; // rep rate for finale + kRaiseTeleport = ["BeamMeUp",gTimeElapsed+25]; // raise the teleports + kStopAutoFire = ["EndFireworks",gTimeElapsed+30]; // and stops here + + settimeout(kFireworksStartsAt); + settimeout(kStopAutoFire); + settimeout(kRaiseTeleport); + } + else if (message == "Stop") + { + llSay(0,"You selected option Stop"); + gTimeoutList = []; + } + +} +// this is a list of all the cycling show bits +ShowTime() +{ + DEBUG("Starting Countdown"); + gTimeElapsed=0; + + settimeout(kDance1); + settimeout(kDance2); + settimeout(kDance3); + settimeout(kDance4); + settimeout(kDance5); + settimeout(kDance6); + + DEBUG(llDumpList2String(gTimeoutList,":")); +} + +Process(string timereventid) { + + if (llList2String(kDance1,0) == timereventid) + { + DEBUG("Dance1"); +; + SendMessageLinked(dylan,"@appearance=Dylan"); + SendMessageLinked(namaka,"@appearance=Namaka"); + + SendMessageLinked(npc1,"@appearance=Crispen Enpeacee"); + SendMessageLinked(npc2,"@appearance=Alessandra Enpeacee"); + SendMessageLinked(npc3,"@appearance=Sammy Enpeacee"); + SendMessageLinked(npc4,"@appearance=Jolinda Enpeacee"); + SendMessageLinked(npc5,"@appearance=Claire Enpeacee"); + SendMessageLinked(npc6,"@appearance=Marianne Enpeacee"); + + SendMessageLinked(LINK_SET,"@notecard=Dance1"); + } + else if (llList2String(kDance2,0) == timereventid) + { + DEBUG("Dance2"); + SendMessageLinked(LINK_SET,"@appearance=Alessandra Enpeacee"); + SendMessageLinked(LINK_SET,"@notecard=Dance2"); + } + else if (llList2String(kDance3,0) == timereventid) + { + DEBUG("Dance3"); + SendMessageLinked(LINK_SET,"@appearance=Sammy Enpeacee"); + SendMessageLinked(LINK_SET,"@notecard=Dance3"); + } + else if (llList2String(kDance4,0)== timereventid) + { + DEBUG("Dance4"); + SendMessageLinked(LINK_SET,"@appearance=Jolinda Enpeacee"); + SendMessageLinked(LINK_SET,"@notecard=Dance4"); + } + else if (llList2String(kDance5,0)== timereventid) + { + DEBUG("Dance5"); + SendMessageLinked(LINK_SET,"@appearance=Claire Enpeacee"); + SendMessageLinked(LINK_SET,"@notecard=Dance5"); + } + else if (llList2String(kDance6,0)== timereventid) + { + DEBUG("Dance6"); + SendMessageLinked(LINK_SET,"@appearance=MarianneEnpeacee"); + SendMessageLinked(LINK_SET,"@notecard=Dance6"); + } + else if (llList2String(kFireworksStartsAt,0) == timereventid) + { + DEBUG("Shoot!"); + SendMessageLinked(LINK_SET,"@appearance=Dragon"); + SendMessageLinked(LINK_SET,"@notecard=Fireworks"); + llRegionSay(gFireworksChannel,"Go"); + settimeout(kFireworksRepRate); + } + else if (llList2String(kFireworksRepRate,0) == timereventid) + { + DEBUG("Fireworks Start"); + llRegionSay(gFireworksChannel,"Go"); + + SendMessageLinked(npc1,"@appearance=Crispen Enpeacee"); + SendMessageLinked(npc2,"@appearance=Alessandra Enpeacee"); + SendMessageLinked(npc3,"@appearance=Sammy Enpeacee"); + SendMessageLinked(npc4,"@appearance=Jolinda Enpeacee"); + SendMessageLinked(npc5,"@appearance=Claire Enpeacee"); + SendMessageLinked(npc6,"@appearance=Marianne Enpeacee"); + + SendMessageLinked(dylan,"@appearance=Dylan"); + SendMessageLinked(namaka,"@appearance=Namaka"); + + SendMessageLinked(LINK_SET,"@notecard=Final"); + + settimeout(kFireworksRepRate); + } + else if (llList2String(kStopAutoFire,0) == timereventid) + { + + DEBUG("Fireworks End"); + deletetimeout(kFireworksRepRate); + settimeout(kRaiseTeleport); + } + else if (llList2String(kRaiseTeleport,0) == timereventid) + { + SendMessageLinked(LINK_SET,"@notecard=Teleport"); + DEBUG("Teleports Up"); + llRegionSay(gTeleportChannel,"Up"); + } +} + +integer busy; +integer gListener; // Listener for handling different channels +list gTimeoutList; +float gTimeElapsed=0; + +//============================================================================ +vector gFacecolor=<1.0,0.0,0.0>; //RED + + +DEBUG(string msg) +{ + if (debug ==1) + llSay(0,llGetScriptName() + ":" + msg); + if (debug ==2) + llSetText(msg, <1,0,0>,1.0); +} + + + + + +menu(key id, integer channel, string title, list buttons) { + llListenRemove(gListener); + gListener = llListen(channel,"",id,""); + llDialog(id,title,buttons,channel); +} + +simplemenu(key id) { + menu(id,gSimpleMenuChannel,"Select an option",["ShowTime","Finale","Stop"]); +} + +deletetimeout(list events) { + + string timereventid = llList2String(events,0); + + DEBUG("Deleteing timer event " + timereventid); + integer identifyerIndex = llListFindList(gTimeoutList, [timereventid]); + if (identifyerIndex != -1) + gTimeoutList = llDeleteSubList(gTimeoutList, identifyerIndex - 1, identifyerIndex); +} + +settimeout(list events) +{ + // unpack the list + string timereventid = llList2String(events,0); + integer time = llList2Integer(events,1); + + DEBUG("Adding " + timereventid); + integer identifyerIndex = llListFindList(gTimeoutList, [timereventid]); + if (identifyerIndex != -1) + gTimeoutList = llDeleteSubList(gTimeoutList, identifyerIndex - 1, identifyerIndex); + if (time != 0) { + gTimeoutList += time; + gTimeoutList += timereventid; + } + + llSetTimerEvent(1.0); + +} + +timertick() { + gTimeElapsed ++; + //DEBUG((string) gTimeElapsed ); + + integer i; + integer numTimers = llGetListLength(gTimeoutList); + + //DEBUG((string) (numTimers/2) + " timers"); + // scan over all queued timers + for (i = 0; i < numTimers; i += 2) + { + integer triggerTime = llList2Integer(gTimeoutList, i); + + //DEBUG("TriggerTime = " + (string)triggerTime); + if (triggerTime == gTimeElapsed) { + + string timereventid = llList2String(gTimeoutList, i + 1); + //DEBUG("matched " + timereventid); + + gTimeoutList = llDeleteSubList(gTimeoutList, i, i + 1); // they are one-shots, so delete the event that just happened. + + Process(timereventid); // process the callback + + + if (gTimeoutList == []) { + DEBUG("Show over! Starting back up"); + ShowTime(); + } + } + } +} + +displaydigit(integer n, string d){ + list Facelist; //list depends on your mesh digital readout face number. + integer i; + if(n==0) Facelist=[ 1,1,1,1,0,1,1 ]; //index @0 | 7 faces per digit + if(n==1) Facelist=[ 0,0,1,0,0,1,0 ]; + if(n==2) Facelist=[ 1,1,0,1,1,1,0 ]; + if(n==3) Facelist=[ 1,1,1,0,1,1,0 ]; + if(n==4) Facelist=[ 0,0,1,0,1,1,1 ]; + if(n==5) Facelist=[ 1,1,1,0,1,0,1 ]; + if(n==6) Facelist=[ 0,1,1,1,1,0,1 ]; + if(n==7) Facelist=[ 1,0,1,0,0,1,0 ]; + if(n==8) Facelist=[ 1,1,1,1,1,1,1 ]; + if(n==9) Facelist=[ 1,0,1,0,1,1,1 ]; + + integer l = llGetLinkNumber() != 0; + integer x = llGetNumberOfPrims() + l; + + for (; l < x; ++l){ + if (llGetLinkName(l) == d){ + for (i=0;i<7;i++){ + llSetLinkPrimitiveParamsFast(l,[PRIM_COLOR,i,gFacecolor,llList2Float(Facelist,i)]); + } + } + } +} + +getdigit(integer time){ + string dOne; + string dTen; + string dHundred; + string dThousand; + + if (time<10){ + dOne=(string)time; + dTen="0"; + dHundred="0"; + dThousand="0"; // add your digits here + }else if(time<100){ + dOne=llGetSubString((string)time,-1,-1); + dTen=llGetSubString((string)time,0,0); + dHundred="0"; + dThousand="0"; + }else if(time<1000){ + dOne=llGetSubString((string)time,-1,-1); + dTen=llGetSubString((string)time,1,1); + dHundred=llGetSubString((string)time,0,0); + dThousand="0"; + }else if(time<10000){ + dOne=llGetSubString((string)time,-1,-1); + dTen=llGetSubString((string)time,2,2); + dHundred=llGetSubString((string)time,1,1); + dThousand=llGetSubString((string)time,0,0); + } + displaydigit((integer)dOne, "one"); + displaydigit((integer)dTen, "ten"); + displaydigit((integer)dHundred, "hundred"); + displaydigit((integer)dThousand, "thousand"); + + //DEBUG(dThousand + dHundred + ":" + dTen + dOne); +} + +StopAll() +{ + DEBUG("Stopped"); + llSetTimerEvent(0); + +} + +default +{ + state_entry() + { + llListen(CommandChannel,"","",""); + getdigit(0); + } + + touch_start(integer num) + { + if ( llGetOwner() == llDetectedKey(0)) { + if (! busy) { + simplemenu(llDetectedKey(0)); + busy++; + } else { + busy = FALSE; + StopAll(); + } + } + } + + link_message(integer sender_number, integer number, string message, key id) + { + DEBUG("LINK MESSAGE\n" + message); + DoCmd(message, number); + } + + listen (integer channel, string name, key id, string message) + { + // !!! add parser for killing the NPC + DEBUG("LISTEN\n" + message); + DoCmd(message, channel); + } + + timer() + { + timertick(); + + //limit the ticker to maximum ticks + if(gTimeElapsed >= MaxTicks){ + gTimeElapsed=0; + } + integer CountDown = MaxTicks - (integer)gTimeElapsed; + integer mm = (CountDown / 60) * 100; + integer ss = (CountDown % 60); + integer cc = mm + ss; + + getdigit((integer)cc); + } + +} \ No newline at end of file diff --git a/HyperGrid Story Nine/HyperGrid Story Nine.sol b/HyperGrid Story Nine/HyperGrid Story Nine.sol new file mode 100644 index 00000000..a81c1041 --- /dev/null +++ b/HyperGrid Story Nine/HyperGrid Story Nine.sol @@ -0,0 +1,3 @@ + + + diff --git a/HyperGrid Story Nine/Nine/Collider/Collider.lsl b/HyperGrid Story Nine/Nine/Collider/Collider.lsl new file mode 100644 index 00000000..4701755a --- /dev/null +++ b/HyperGrid Story Nine/Nine/Collider/Collider.lsl @@ -0,0 +1,50 @@ +// :SHOW:1 +// :CATEGORY:Gaming +// :NAME:HyperGrid Story Nine +// :AUTHOR:Ferd Frederix +// :KEYWORDS:Game, Collider +// :CREATED:2015-11-24 20:25:33 +// :EDITED:2015-11-24 19:25:33 +// :ID:1087 +// :NUM:1834 +// :REV:2.0 +// :WORLD:OpenSim +// :DESCRIPTION: +// Triggers the NPC controller to play the Greet notecard when collided. +// :CODE: + +string message = "@notecard=Greet"; + +Reset() { + llSetStatus(STATUS_PHANTOM, FALSE); // rev 2.0 + llVolumeDetect(FALSE); + llSleep(0.1); + llVolumeDetect(TRUE); +} + +default{ + state_entry(){ + Reset(); + } + collision_start(integer n){ + if (osIsNpc(llDetectedKey(0))){ + return; + } + llMessageLinked(2,0,message,""); + + llSetTimerEvent(10); + } + timer() + { + Reset(); + llSetTimerEvent(0); + } + on_rez(integer p){ + llResetScript(); + } + changed(integer what){ + if (what & CHANGED_REGION_START){ + llResetScript(); + } + } +} diff --git a/HyperGrid Story Nine/Nine/Dancer Ball/Effect.lsl b/HyperGrid Story Nine/Nine/Dancer Ball/Effect.lsl new file mode 100644 index 00000000..073f9bd3 --- /dev/null +++ b/HyperGrid Story Nine/Nine/Dancer Ball/Effect.lsl @@ -0,0 +1,103 @@ +// :SHOW:1 +// :CATEGORY:NPC +// :NAME:HyperGrid Story Nine +// :AUTHOR:Ferd Frederix +// :KEYWORDS:NPC, controller, cyber helmet +// :CREATED:2015-11-24 20:25:33 +// :EDITED:2015-11-24 19:25:33 +// :ID:1087 +// :NUM:1836 +// :REV:1.0 +// :WORLD:OpenSim +// :DESCRIPTION: +// NPC helmet controller +// Accepts a single link message to make the cyber being helmet flash for a second or two. +// worn by the NPC + +// :CODE: + +// TUNABLES +integer debug = 0; + + + + +// GLOBALS +integer counter = 0; + + +// FUNCTIONS +DEBUG(string msg) +{ + if (debug & 1) + llSay(0,llGetScriptName() + ":" + msg); + if (debug & 2) + llSetText(msg, <1,0,0>,1.0); +} +NpcSaySomething(){ + + list sayings = ["The helmet begins to glow","A beam of light appears","The helmet begins to hum","The cyberbeing helmet is activated", "bzzz", "Zap","KaPow!"]; + key NpcKey = llGetOwner(); + DEBUG("Owner Key = " + (string) NpcKey); + + llSay(0, llList2String(sayings,counter++)); + if (counter > llGetListLength(sayings)) { + counter = 0; + } +} + +Go() +{ + NpcSaySomething(); + llSetLinkPrimitiveParamsFast(llGetLinkNumber(),[PRIM_GLOW,ALL_SIDES,.2, PRIM_SIZE, <2,2,2>,PRIM_COLOR,ALL_SIDES,<1,1,1>,0.5 ]); + llSetTimerEvent(2); + +} + +integer Helmet_Channel = 576; + +default +{ + on_rez(integer p) + { + llResetScript(); + } + + changed(integer what) + { + if (what & CHANGED_REGION_START) + { + llResetScript(); + } + } + + + state_entry() + { + llSetText("", <1,0,0>,1.0); + llListenRemove(listener); + + llSetTextureAnim(ANIM_ON | SMOOTH | LOOP , ALL_SIDES, 1, 1, 1.0, 1.0, 1.0); + } + + link_message(integer total_number, integer Num, string text, key id) + { + if ( text =="BOOM") + Go(); + } + + + timer() + { + llSetLinkPrimitiveParamsFast(llGetLinkNumber(),[PRIM_GLOW,ALL_SIDES,0.2, PRIM_SIZE, <1.5,1.5,1> ] ); + llSleep(0.25); + llSetLinkPrimitiveParamsFast(llGetLinkNumber(),[PRIM_GLOW,ALL_SIDES,0.2, PRIM_SIZE, <1.2,1.2,1> ] ); + + llSetLinkPrimitiveParamsFast(llGetLinkNumber(),[PRIM_GLOW,ALL_SIDES,0, PRIM_SIZE, <1,1,1> ] ); + llSleep(0.25); + llSetLinkPrimitiveParamsFast(llGetLinkNumber(),[PRIM_GLOW,ALL_SIDES,0, PRIM_SIZE, <1,1,1>,PRIM_COLOR,ALL_SIDES,<1,1,1>,0 ] ); + llSetTimerEvent(0); + + } + +} diff --git a/HyperGrid Story Nine/Nine/Dancer Ball/NPC 9.3.lsl b/HyperGrid Story Nine/Nine/Dancer Ball/NPC 9.3.lsl new file mode 100644 index 00000000..3f73059a --- /dev/null +++ b/HyperGrid Story Nine/Nine/Dancer Ball/NPC 9.3.lsl @@ -0,0 +1,1696 @@ +// :SHOW:1 +// :CATEGORY:NPC +// :NAME:HyperGrid Story Nine +// :AUTHOR:Ferd Frederix +// :KEYWORDS:NPC, Puppeteer +// :CREATED:2015-11-24 20:25:33 +// :EDITED:2015-11-24 19:25:33 +// :ID:1087 +// :NUM:1835 +// :REV:9.3 +// :WORLD:OpenSim +// :DESCRIPTION: +// All in one NPC recorder player. +// Supports both absolute and relative paths and many new commands +// Add animations named "Fly, Walk, Stand and Run" +// Click Prim to use. +// Should be worn as a HUD to record. +// Put it on the ground and click Sensor or Start NPC when done. +// :CODE: +// This is Rev 9.3, based on rev 4.6 - skipped way up because of custom tweaks. + +// Revision History +// Rev 1.1 10-2-2014 @Sit did not work. Minor tweaks to casting for lslEditor +// Rev 1.2 10-14-2014 @ sit had wrong type. +// Rev 1.3 relative movement fixed for @fly +// Rev 1.4 4-3-2014 allow anyone to use this, non owners and non group members can only start and stop. +// Rev 1.5 5-17-2014 set sensor to auto start on reboot of sim +// Rev 1.6 5-24-2014 move menu so you can get it by touching, removed many of the KeyValues to RAM for efficiency +// Rev 1.7 CHANGED_REGION_START, not CHANGED_REGION_START (Opensim difference) +// Rev 1.8 tuned up Kill NPC, added more flexible upgrader +// Rev 1.9 Better script injection by link message// Rev 2.0 Added osSetSpeed so you can speed up or slow down an NPC. +// Rev 2.1 No laggy sensor used exept to sit on stuff +// Rev 2.2 Various sensor fixes +// Rev 2.3 Sets No Sensor in menu, must be started by hand +// Rev 2.4 - reserved for patches to 2.3 if needed +// Rev 3.0 Refactor out into subs, not states to make command injection easier +// New command: @appearance=Notecardname so you can switch to a new notecard on the fly +// New command: @speed=1.0 which slows up ( < 1 ) or speeds up ( > 1) +// Rev 3.1 Commands are not interruptible by Link Message +// Rev 3.2 Sensor patches for consistency in removing the NPC +// Rev 3.3 Added Touch command by Neo.Cortex@hbase42/hopto/org:8002 +// Added Menu 3 for notecard and appearance commands +// Rev 3.4 animation timer cannot be zero or it shuts off timer tweaked +// solves the NPC starting up when no sensor is set. +// Rev 3.5 fixes saving to !Path notecard +// Rev 3.6 08-11-2015 @delete acts like @stop. TYjhe NPC now rezzes after an @go back in where it was deleted +// Rev 3.7 08-11-2015 @attach command added to load an attachment from the inventory to the NPC +// Rev 3.8 08-17-2015 process queued commands one at a time without calling ProcessNPCLine on link message +// Rev 3.9 08-23-2011 Queued command fixes including @delete which were not always working +// Rev 4.0 09-15-2015 Fixes for Sensor functions which continually rezzed a NPC when no one was around. +// Rev 4.1 09-20-2015 Added a Listener so link messages are not needed +// Rev 4.2 09-23-2015 Added @teleport= +// Rev 4.3 09-24-2015 Added @reset to restart the NPC at the very start of the !Path notecard +// @teleport works for relative and absolute modes +// Rev 4.4 09-26-2015 if it could not find the (deleted) NPC, it could not restart +// Rev 4.5 09-29-2015 remove wait for STATE == 0 +//*******************************************************************// + +// Instructions on how to use this are at http://www.outworldz.com/opensim/posts/NPC/ +// This is an OpenSim-only script. +// Author: Ferd Frederix aka Fred Beckhusen - fred@mitsi.com + +//////////////////////////////////////////////////////////////////////////////////////////// +// Original code was Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // +/////////////////////////////////////////////////////////////////////////////////////////// +// Please see: http://www.gnu.org/licenses/gpl.html for legal details, // +// rights of fair usage, the disclaimer and warranty conditions. // +/////////////////////////////////////////////////////////////////////////////////////////// +// The original NPC controller was from http://was.fm/opensim:npc +// Extensive additions and bug fixes by Fred Beckhusen, aka Ferd Frederix +// llSensor had two params swapped +// @Wander would wander where it had rezzed, not where it was. +// There was no 'no_sensor' event in sit, so if a @sit failed, the NPC got stuck +// The animation and walks always stopped old, then started new. It should be start new, then stop old so the default stand would be suppressed. +// New code: +// Merged with new Route recorder and notecard writer +// If the NPC failed to reach a destination it never moved on. +// Added WAIT global to tune this +// Exposed many tunable variables and ported the code +// Added floating point to times in notecard. +// Added @sound, @randsound, @whisper, @shout, and @cmd controls. +// notecards integers are not floats for better control +// +// Link Messages may be used to perform external control by injecting @commands into the stream of actions +// Example: +// To chat something, such as with a chat robot +// llMessageLinked(LINK_SET,0,"@npc_say=Hello",""); + +// This script assumes that NPCs and OSSl scripting is enabled in the OpenSim configuration. +// In order to enable them, the following changes must be made in the OpenSim.ini configuration file: +// +// ; Turn on OSSL +// AllowOSFunctions = true +// OSFunctionThreatLevel = Severe + +//[NPC] +// ;# {Enabled} {} {Enable Non Player Character (NPC) facilities} {true false} +// Enabled = true +// +// and then the server has to be restarted. +// please note that there are better ways to enable NPC in the latest Opensim. + +// Commands: All commands begin with an @ sign. All other lines are ignored +// @commands may have optional parameters. The syntax is always: +// @cmd=parm1|parm2 +// NaN in the table below meand Not a Number. This means there is no parameter + +//Command First Parameter Second Parameter Description +//@spawn name location (vector) Rezzes an NPC with name at a location. +//@appearance NoteCardName NaN switch the NPC appearance to a new notecard +//@walk destination (vector) NaN Makes the NPC walk to destination. +//@fly destination (vector) NaN Makes the NPC fly to destination. +//@land destination (vector) NaN Makes the NPC land at destination. +//@say string NaN Makes the NPC speak a phrase. +//@whisper string NaN Makes the NPC whisper a phrase. +//@shout string NaN Makes the NPC shout a phrase. +//@pause seconds (float) NaN Makes the NPC wait for a multiple of seconds. +//@wander radius (float) cycles (integer) Makes the NPC wander in radius, for cycles seconds. +//@delete NaN NaN Removes the NPC. Requires a link message to continue +//@goto label (string) NaN Jump to the label label in the script. +//@animate animation (string) time (float) Makes the NPC trigger the animation animation for time seconds. +//@sound sound_name NaN plays a sound from inventory +//@randsound NaN NaN Plays a random sound from inventory +//@rotate degrees (float) NaN Rotate the NPC degrees around the Z axis. +//@sit primitive name NaN Sit on a primitive with a given name. +//@touch primitive name NaN Touch on a primitive with a given name. +//@stand NaN NaN If sitting on a primitive, stand up. +//@cmd channel (integer) string Says string on channel, for controlling external gadgets +//@stop NaN NaN Halts the NPC script indefinitely. Can be started with a link message +//@go NaN NaN Continues on next notecard line, for use in link messages +//@speed speed (float) NaN from 0 to N, where 1.0 ius a normal speed of an avatar. 0.2 is a turtle. +//@notecard notename (string) NaN load a new Path notecard +//@attach InventoryName attachmentPoint load an attachment from the inventory to the NPC onto point +//@teleport destination (vector) NaN Makes the NPC teleport to destination in the same sim. They cannot tp to another sim or across the HG +//@reset NaN NaN Deletes the NPC, starts the !Path notecard over. + +// Constant attachmentPoint Comment +// ATTACH_CHEST 1 chest/sternum +// ATTACH_HEAD 2 head +// ATTACH_LSHOULDER 3 left shoulder +// ATTACH_RSHOULDER 4 right shoulder +// ATTACH_LHAND 5 left hand +// ATTACH_RHAND 6 right hand +// ATTACH_LFOOT 7 left foot +// ATTACH_RFOOT 8 right foot +// ATTACH_BACK 9 back +// ATTACH_PELVIS 10 pelvis +// ATTACH_MOUTH 11 mouth +// ATTACH_CHIN 12 chin +// ATTACH_LEAR 13 left ear +// ATTACH_REAR 14 right ear +// ATTACH_LEYE 15 left eye +// ATTACH_REYE 16 right eye +// ATTACH_NOSE 17 nose +// ATTACH_RUARM 18 right upper arm +// ATTACH_RLARM 19 right lower arm +// ATTACH_LUARM 20 left upper arm +// ATTACH_LLARM 21 left lower arm +// ATTACH_RHIP 22 right hip +// ATTACH_RULEG 23 right upper leg +// ATTACH_RLLEG 24 right lower leg +// ATTACH_LHIP 25 left hip +// ATTACH_LULEG 26 left upper leg +// ATTACH_LLLEG 27 left lower leg +// ATTACH_BELLY 28 belly/stomach/tummy +// ATTACH_LEFT_PEC 29 left pectoral +// ATTACH_RIGHT_PEC 30 right pectoral +// ATTACH_HUD_CENTER_2 31 HUD Center 2 +// ATTACH_HUD_TOP_RIGHT 32 HUD Top Right +// ATTACH_HUD_TOP_CENTER 33 HUD Top +// ATTACH_HUD_TOP_LEFT 34 HUD Top Left +// ATTACH_HUD_CENTER_1 35 HUD Center +// ATTACH_HUD_BOTTOM_LEFT 36 HUD Bottom Left +// ATTACH_HUD_BOTTOM 37 HUD Bottom +// ATTACH_HUD_BOTTOM_RIGHT 38 HUD Bottom Right +// ATTACH_NECK 39 neck +// ATTACH_AVATAR_CENTER 40 avatar center/root + + + +////////////////////////////////////////////////////////// +// DEBUG // +////////////////////////////////////////////////////////// +integer debug = FALSE; // set to TRUE or FALSE for debug chat on various actions +integer LSLEditor = FALSE; // set to to TRUE to working in LSLEditor, FALSE for in-world. + // you must also include the NPC commands found in the other script since LSLEditor does not support OpenSim +integer iTitleText = FALSE; // set to TRUE to see debug info in text above the controller + +////////////////////////////////////////////////////////// +// TUNABLE CONFIGURATION // +////////////////////////////////////////////////////////// +integer allowListener = TRUE; // set to TRUE to anable a command listener. Usually, this is setto FALSE +integer link_Channel = 4223; // some random number you want to talk to this gadget on. Best if large and negative +float TIMER = 2; // faster = less jerky stopping. How often the system checks the distance traveled. Fastest you can go is 0.5 seconds +float QUICK = 1; // when we need to move to the next state, we use a QUICK timer +string Appearance = "!Appearance"; // The name of the recorded Appearance notecard +string Notecard = "!Path"; // The name of the recorded routes +integer allowUsers = FALSE; // If true, any user can get a Start NPC and Stop NPC menu. Only groups and owners can get all commands if TRUE, or FALSE +float MAXDIST = 2.0; // how close a NPC has to get to a dest pos to continue to next state. Do not lower this too much, as it may miss the target +integer WANDERRAND = TRUE; // set to TRUE and they will pause during wanders a random number of seconds +float WANDERTIME = 3.0; // how long they stand after each @wander,if WANDERRAND is FALSE. If WANDERRAND is TRUE, this is the max time +integer WAIT = 30; // wait for this number of seconds for the NPC to reach a destination (for safety). If it fails to reach a target, it will move on after this time. +float RANGE = 150; // 1 to N meters - anyone this close to the controller will start NPCS if Sensor button is clicked +float REZTIME = 2.0; // wait this long for NPC to rez in, then start the process +string STAND = "Stand"; // the name of the default Stand animation +string WALK = "Walk"; // the name of the default Walk animation +string FLY = "Fly"; // the name of the default Fly animation +string RUN = "Run"; // the name of the default Run animation +string LAND = "Land"; // the name of the default land animation ( for birds only) +float OffsetZ = 0.5; // appear 0.5 meter above ground, this is added to all destinations to keep them from sinking in. +float SPEEDMULT =0.8; // 1.0 = regular avatar speed. Smaller numbers slow down walks. Large numbers speed them up. +integer FLIGHT = 299; // For controlling wings. A channel that is shouted at when flight starts and ends. "flying" or "landing" + +// DESCRIPTIONS FIELDS HAVE TO SURVIVE A RESET +// These vars are stored by saving them with KeyValueSet +// "pr" is a 0 if it is set for Owner Only, 1 for Group control +// "se" is "on" if Started +// "co" = "R" or "A" for relative or absolute addressing mode +// "key" = NPC key + +// These Globals used to be stored in description. Moved to RAM in V1.6 +float RAMPause; // @pause param +float RAMwd ; // @wander distance +integer RAMwc; // @wander count +float RAMrot; // @rotate +string RAMsit; // @sit primname +string RAMtouch; // @touch primname +string RAManimationName; // @animate animation (string) time (float) +float RAManimationTime; + +// other globals section +integer iChannel; // a listen channel, randomly assigned +integer iHandle; // the handle to it + +// NPC controls +vector newDest ; // tmp storage for the walks +integer iWaitCounter ; // wait for this number of seconds for the NPC to reach a desrtination +string sNPCName; // the name of the NPC that may be in world. So we can remove it. +integer bNPC_STOP = FALSE; // boolean to reuse a listener +integer Stopped = FALSE; // set to TRUE by link messages so we do not remember them +float fTimerVal ; // how long we wait when wandering (calculated) +float NPCEnabled; // true if the NPC is suppodes to be running + +// OS_NPC_CREATOR_OWNED will create an 'owned' NPC that will only respond to osNpc* commands issued from scripts that have the same owner as the one that created the NPC. +// OS_NPC_NOT_OWNED will create an 'unowned' NPC that will respond to any script that has OSSL permissions to call osNpc* commands. +integer NPCOptions = OS_NPC_CREATOR_OWNED; // only yhe owner of this box can control this NPC. + +integer walkstate = 0; // helps us reshare the walk state for run, fly and land - a bit of a hack, but it saves RAM. Has to be done this way because some bits of NPCWalkOption are asserted as 0 + +integer NPCWalkOption; // Some notes for what happens to NPCWalkOption: +// OS_NPC_FLY - Fly the avatar to the given position. The avatar will not land unless the OS_NPC_LAND_AT_TARGET option is also given. +// OS_NPC_NO_FLY - Do not fly to the target. The NPC will attempt to walk to the location. If it's up in the air then the avatar will keep bouncing hopeless until another move target is given or the move is stopped +//OS_NPC_LAND_AT_TARGET - If given and the avatar is flying, then it will land when it reaches the target. If OS_NPC_NO_FLY is given then this option has no effect. +// OS_NPC_RUNNING - if given, NPC avatar moves at running/fast flying speed, otherwise moves at walking/slow flying speed. + +// menus +string mSensor="Sense is Off"; // Sensor or "No Sensor" + +list lAtButtons = ["Menu","-", ">>", "@run", "@walk", "@fly", "@land", "@wander", "@sit", "@stand","@animate","@rotate"]; +list lMenu2 = ["<<", "@comment", ">>>", "@stop", "@say", "@whisper","@shout","@sound","@randsound","@cmd", "@pause", "@delete"]; +list lMenu3 = ["<<<","@notecard","@appearance", "@touch", "@speed", "@attach", "@teleport","-", "-", "-", "-", "-" ]; + +string sCommand; // place to store a command for two-prompted ones +string sParam2; // place to store a prompt for two-prompted ones +string priPub = "Owner Only"; // Private or Group +key kUserKey; // the person who is controlling the avatar, not the Owner +// the command lists +list lCommands; // commands are stored here +list lNpcCommandList; // Storage for the NPC script. +string npcAction; // Storage for the next action. @cmd=0|hello, this becomes @cmd +string npcParams; // Storage for the param, @cmd=0|hello, this becomes 0|hello + +// misc vars +string sNotecard; // commands are stored here temporarily for dumping +vector vWanderPos; // a place to wander to +string lastANIM ; // last animation run +// Sensor +integer avatarPresent; // Sensor sets this flag when people are within Range. + +// Coordinate control +vector vInitialPos ; // Vector that will be filled by the script with the initial starting position in region coordinates. +vector vDestPos = ZERO_VECTOR; // Storage for destination position. +string relAbs = "Relative"; // absolute vs relative positioning + + +// STATES +integer MENU ; // processing a dialog box state, may be concurrent with STATE +integer STATE; // state storage +integer NULL = 0; // the null state +integer MakeNotecard = 1; // displaying a text box for NPC name +integer RecordPath = 2; // displaying a path notecard menu +integer NobodyHome = 3; // looking for an avatar +integer Spawning = 4; // spawning an avatar +integer Animate = 5; // animation timer needed +integer Walking = 6; // Hey! I am walking here! +integer Wander = 7; // Wandering around neeeds a timer, too +integer WanderHold = 8; // We reached a wander point +integer DoProcess = 9; // Set this to make it process a new command +integer Touch = 10; // Timer is busy sensing something to touch +integer Sit = 11; // Timer is busy sensing something to sit on +integer Paused = 12; // Timer is busy pausing + +key gNpcKey = NULL_KEY; // global key storage for the one NPC, to save CPU cycles +list Stack ; // a command stack from link message input + +/////////////////////////////////////////////////////////////////////////// +// FUNCTIONS // +/////////////////////////////////////////////////////////////////////////// + + +TimerEvent(float timesent) +{ + if (LSLEditor) + timesent *= 5; // slow thinggs doen when the LSLEDITOR is in use + + DEBUG("Setting timer: " + (string) timesent); + llSetTimerEvent(timesent); +} + +// for 4.1 parse a message from a Listen or a Link message +ParseMsg(string str) { + DEBUG("Command In:" + str); + if (str=="@go") { + SetStop(FALSE); // Let's run the notecard + DEBUG("@go running"); + DoProcessNPCLine(); + } else { + Stack += [str]; // take anything, the controller will filter away non @ stuff + if (STATE == NULL) + DoProcessNPCLine(); // v 4.5 remove wait for STATE == 0 + } +} + +SetStop(integer what) +{ + DEBUG("Stopped set to " + (string ) what); + Stopped = what; +} +// Do* functions are much like states from the old V2 scripts. + +// Save a Path notecard +DoSave() +{ + STATE = MakeNotecard; + makeText("Stand where you want the NPC to appear, and enter the NPC Name"); +} + +// This function is used to record the path for the NPC +// Each command can take 0, 1, or 2 params +DoMenuForCommands() { + makeMenu(lAtButtons); +} + + +// No one is here when sensors were on, so we kill off the NPC +DoNobodyHome() +{ + DEBUG("Nobody Home"); + STATE = NobodyHome; + if (NPCKey() != NULL_KEY) { + osNpcRemove(NPCKey()); + SaveKey(NULL_KEY); + } + TimerEvent(5); // keep ticking to sense avatars +} + + +/////////////////////// STATELIKE BEHAVIOUR ///////////// +// these StateXX functions need to wait on a timer to fire. + +// Create a NPC +StateSpawn() { + DEBUG("state spawn " + sNPCName); + + NPCEnabled = TRUE; // in world + // see if there is already one out there. + if (NPCKey() != NULL_KEY) { + DEBUG("Already living"); + return; + } + + STATE = Spawning; + list name = llParseString2List(sNPCName, [" "], []); + + vector vRezPos = vInitialPos; + if (relAbs == "Relative"){ + vRezPos += llGetPos(); + } + + // llSay(0,llDumpList2String(name,",")); + + DEBUG("Rezzing NPC name " +llList2String(name, 0)+ llList2String(name, 1) + " at "+ (string) vRezPos); + key aKey = osNpcCreate(llList2String(name, 0), llList2String(name, 1), vRezPos, Appearance, NPCOptions); + + llMessageLinked(LINK_SET,-10,(string) llGetLinkNumber() + "|" + (string) llGetPos() ,aKey); // broadcast the key on num = -1 + SaveKey(aKey); // save in description and global, too + + osSetSpeed(aKey,SPEEDMULT); // 1.9 speed multiplier + TimerEvent(REZTIME); + NPCAnimate(STAND); +} + +StateSit() { + DEBUG ("state sit - looking for " + RAMsit); + STATE=Sit; + llSensor(RAMsit, "", PASSIVE|ACTIVE|SCRIPTED, 96, PI); +} + +StateTouch() { + DEBUG ("state touch - looking for " + RAMtouch); + STATE = Touch; + llSensor(RAMtouch, "", PASSIVE|ACTIVE|SCRIPTED, 96, PI); +} + +DoStand() { + DEBUG("state stand"); + osNpcStand(NPCKey()); +} + + +StateAnimate() { + + DEBUG("state animate"); + STATE = Animate; + NPCAnimate(RAManimationName); + if (RAManimationTime <=0 ) // V 3.4 tweak + RAManimationTime = 1; + TimerEvent(RAManimationTime); +} + +StateWalk() { + + DEBUG("Start Walk"); + //DEBUG("NPCWalkOption = " + (string) NPCWalkOption); + STATE = Walking; + + // walk, fly, run, land + if (walkstate == 1) { + NPCAnimate(WALK); + } else if (walkstate == 2) { + llShout(FLIGHT,"flying"); + NPCAnimate(FLY); + } else if (walkstate == 3) { + NPCAnimate(RUN); + } else if (walkstate == 4) { + NPCAnimate(LAND); + } + newDest = vDestPos ; + newDest.z += OffsetZ; + + // notecard is stored as offsets from this box with relative addressing. Convert to absolute + if (relAbs == "Relative"){ + newDest += llGetPos(); + } + + DEBUG("Moveto:" + (string) newDest); + osNpcMoveToTarget(NPCKey(), newDest, NPCWalkOption); + iWaitCounter = WAIT; // wait 60 seconds to get to a destination. + TimerEvent(TIMER); +} + + +StateWander(){ + DEBUG("state wander"); + STATE = Wander; + + vector point = CirclePoint(RAMwd); + DEBUG("CirclePoint:" + (string) point); + vWanderPos = vDestPos + point; + DEBUG("vWanderPos:" + (string) vWanderPos); + + fTimerVal = WANDERTIME; // default time to pause after each wander + if (WANDERRAND) + fTimerVal = llFrand(WANDERTIME) + 1; // override, they want random times + + NPCAnimate(WALK); + + DEBUG("Wander to:" + (string) vWanderPos); + + osNpcMoveToTarget(NPCKey(), vWanderPos, NPCWalkOption); + iWaitCounter = WAIT; // wait 60 seconds to get to a destination. + TimerEvent(TIMER); +} + +StateWanderhold() { + + DEBUG("Wander Hold"); + STATE = WanderHold; + + // now that we have reached a wander spot, slow the timer down to the desired value + TimerEvent(fTimerVal); +} + + + +DoRotate() { + DEBUG("@rotate=" + (string) RAMrot); + osNpcSetRot(NPCKey(), llEuler2Rot(<0,0,RAMrot> * DEG_TO_RAD)); +} + + + +// @pause=10 will do nothing for 10 seconds +DoPause() { + STATE = Paused; + if (RAMPause < 0.1) + RAMPause = 0.1; + DEBUG("@pause=" + (string)RAMPause); + TimerEvent(RAMPause); +} + + +// @stop makes the NPC stop moving in whatever state it is in. You have to linkmessage to get moving again +DoStop() { + DEBUG("NPC is Stopped"); + STATE = 0; // accept commands + SetStop(TRUE); // Link controlled - we mnust have a @go to continue with notecards + TimerEvent(0); + Stack = []; // v3.8 +} + +// @delete removes the NPC forever. Next command starts it up again at the beginning +DoDelete() { + DEBUG("state delete"); + osNpcRemove(NPCKey()); + SaveKey(NULL_KEY); + TimerEvent(0); + Stack = []; // v3.8 + STATE = NULL; // accept commands +} + +// change the appearance of the NPC +DoAppearance(string notecard) { + DEBUG("state appearance"); + if (llGetInventoryType(notecard) == INVENTORY_NOTECARD){ + DEBUG("Load appearance " + notecard); + osNpcLoadAppearance(NPCKey(),notecard); + } + STATE = NULL; // accept commands +} + +// Change the avatar speed +DoSpeed(string speed) { + float newspeed = (float) speed; + if (newspeed > 0.1 && newspeed < 5.0) {// sanity check + osSetSpeed(NPCKey(),newspeed); + } + STATE = NULL; // accept commands +} + +DoTeleport(string params) { + list Data = llParseString2List(params, ["|"], []); + string itemName = llList2String(Data, 0); + vector Dest = (vector) itemName; + if (Dest != ZERO_VECTOR) { + if (relAbs == "Relative"){ + Dest += llGetPos(); + } + osTeleportAgent( NPCKey(), llGetRegionName(), Dest, ZERO_VECTOR ); + + } else { + llSay(DEBUG_CHANNEL,"Attempt to teleport to <0,0,0> probably not what you intended: @teleport="); + } + STATE = NULL; // accept commands +} + + + +DoNewNote (string card) { + DEBUG("Load Notecard " + card); + NPCReadNoteCard(card); + SetStop(FALSE); + STATE = NULL; // accept commands +} +DoAttach(string params) { + + list Data = llParseString2List(params, ["|"], []); + string itemName = llList2String(Data, 0); + integer attachmentPoint = (integer) llList2String(Data, 1); + if (attachmentPoint > 0 + && attachmentPoint < 40 + && llGetInventoryType(itemName) == INVENTORY_OBJECT + ) + { + osForceAttachToOtherAvatarFromInventory(NPCKey(),itemName,attachmentPoint); + } + STATE = NULL; // accept commands +} + +// This loops over the notecard, processing each command +DoProcessNPCLine() { + DEBUG("ProcessNPCLine, stopped = " + (string) Stopped); + + STATE = DoProcess; + + // auto load a notecard + if (! llGetListLength(lNpcCommandList)) { + DEBUG("Read Notecard"); + NPCReadNoteCard(Notecard); + } + + // look for link messages on the stack + string next = llList2String(Stack,0); // lets see if there is anything from a link message + if (llStringLength(next)) + { + Stack = llDeleteSubList(Stack,0,0); + ProcessCmd(next); //lets do this command instead. + return; + } + + // @stop issued? + if (Stopped) { + TimerEvent(0); + DEBUG("Stopped, waiting for input"); + STATE = NULL; + return; + } + + // No, we have an @go for liftoff + next = llList2String(lNpcCommandList, 0); // get the next command + DEBUG("Execute:" + next); + lNpcCommandList = llDeleteSubList(lNpcCommandList, 0, 0); // delete it + + if (llGetListLength(lNpcCommandList) == 0) { + DEBUG("EOF"); + } + ProcessCmd(next); +} + + + +ProcessCmd(string cmd) { + + DEBUG("ProcessCmd:" + cmd); + + if (llGetSubString(cmd, 0, 0) != "@") { + DEBUG("ignoring"); + TimerEvent(QUICK); // this is so we do not recurse the stack + STATE = NULL; + return; + } + + list data = llParseString2List(cmd, ["="], []); + npcAction = llToLower(llStringTrim(llList2String(data, 0), STRING_TRIM)); + + DEBUG("Action:" + npcAction); + npcParams = llStringTrim(llList2String(data, 1), STRING_TRIM); + DEBUG("Params:" + npcParams); + + @commands; + + ProcessSensor(); + + if(npcAction == "@spawn") { + DEBUG("@spawn npcParams "); + list spawnData = llParseString2List(npcParams, ["|"], []); + sNPCName =llList2String(spawnData, 0); // V 1.6 name in RAM + + vInitialPos = (vector) llList2String(spawnData, 1); + DEBUG("Coords for NPC at " + (string) vInitialPos); + StateSpawn(); + return; + } + + if (! avatarPresent){ + DoNobodyHome(); + DEBUG("No avatar nearby"); + STATE = NULL; + return; + } else { + if ( NPCKey() == NULL_KEY) { + StateSpawn(); + } + } + + + + + if(npcAction == "@stop") { + DoStop(); + STATE = NULL; + return; + } + else if(npcAction == "@goto") { + DEBUG("goto"); + integer lastIdx = llGetListLength(lNpcCommandList)-1; + lNpcCommandList = llDeleteSubList(lNpcCommandList, lastIdx, lastIdx); + // Wind commands till goto label. + @wind; + string next1 = llList2String(lNpcCommandList, 0); + lNpcCommandList = llDeleteSubList(lNpcCommandList, 0, 0); + lNpcCommandList += next1; + if(next1 != npcParams) jump wind; + // Wind the label too. + next1 = llList2String(lNpcCommandList, 0); + lNpcCommandList = llDeleteSubList(lNpcCommandList, 0, 0); + lNpcCommandList += next1; + // Get next command. + list data1 = llParseString2List(next1, ["="], []); + npcAction = llToLower(llStringTrim(llList2String(data1, 0), STRING_TRIM)); + npcParams = llStringTrim(llList2String(data1, 1), STRING_TRIM); + // Reschedule. + jump commands; + } + else if(npcAction == "@sound") { + DEBUG("sound"); + llTriggerSound(npcParams,1.0); + } + else if(npcAction == "@randsound") { + DEBUG("@randsound"); + integer N = llGetInventoryNumber(INVENTORY_SOUND); + integer rand = llCeil(llFrand(N)) -1; // pick a random sound + string toPlay = llGetInventoryName(INVENTORY_SOUND,rand); + llTriggerSound(toPlay,1.0); + } + else if(npcAction == "@walk") { + DEBUG("@walk"); + GetDest(npcParams); + walkstate = 1;// walking + NPCWalkOption = OS_NPC_NO_FLY ; + StateWalk(); + return; + } + else if(npcAction == "@fly") { + GetDest(npcParams); + walkstate = 2;// flying + NPCWalkOption = OS_NPC_FLY ; + StateWalk(); + return; + } + else if(npcAction == "@run") { + DEBUG("@run"); + GetDest(npcParams); + walkstate = 3;// running + NPCWalkOption = OS_NPC_NO_FLY | OS_NPC_RUNNING; + StateWalk(); + return; + } + else if(npcAction == "@land") { + DEBUG("@land"); + GetDest(npcParams); + walkstate = 4;// landing + NPCWalkOption= OS_NPC_FLY | OS_NPC_LAND_AT_TARGET ; + StateWalk(); + return; + } + else if(npcAction == "@say") { + DEBUG("@say " + npcParams); + osNpcSay(NPCKey(), 0, npcParams); + } + else if(npcAction == "@shout") { + DEBUG("@shout"); + osNpcShout(NPCKey(),0, npcParams); + } + else if(npcAction == "@whisper") { + DEBUG("@whisper " + npcParams); + osNpcWhisper(NPCKey(),0, npcParams); + } + // speak a command on a channel, so you can open doors and control stuff. + else if(npcAction == "@cmd") { + DEBUG("@cmd"); + list dataToSpeak = llParseString2List(npcParams, ["|"], []); + string channel = llList2String(dataToSpeak,0); + DEBUG("Channel:"+(string) channel); + integer iChannel = (integer) channel; + string stringToSpeak = llList2String(dataToSpeak,1); + llSay(iChannel, stringToSpeak); + } + // stop everything + else if(npcAction == "@pause") { + RAMPause = (float) npcParams; + DoPause(); + return; + } + else if(npcAction == "@wander") { + list wanderData = llParseString2List(npcParams, ["|"], []); + RAMwd = (float) llList2String(wanderData, 0); + RAMwc = (integer) llList2String(wanderData, 1); + vDestPos = osNpcGetPos(NPCKey()); // set the wander start + DEBUG("Starting at " + (string) vDestPos); + StateWander(); + return; + } + else if(npcAction == "@rotate") { + RAMrot = (float) npcParams; + DoRotate(); + } + else if(npcAction == "@sit") { + RAMsit= npcParams; + StateSit(); + return; + } + else if(npcAction == "@touch") { + RAMtouch= npcParams; + StateTouch(); + return; + } + else if(npcAction == "@stand") { + DoStand(); + } + else if(npcAction == "@delete") { + DoDelete(); + SetStop(TRUE); // Link controlled - we mnust have a @go to continue with notecards + return; + } + else if(npcAction == "@animate") { + list animateData = llParseString2List(npcParams, ["|"], []); + RAManimationName = llList2String(animateData, 0); + RAManimationTime = (float) llList2String(animateData, 1); + StateAnimate(); + return; + } + else if(npcAction == "@appearance" ){ + DoAppearance(npcParams); + } + else if (npcAction =="@speed") { + DoSpeed(npcParams); + } + else if (npcAction =="@notecard") { + DoNewNote(npcParams); + Notecard = npcParams; + } + else if (npcAction == "@attach") + { + DoAttach(npcParams); + } + else if (npcAction == "@teleport") + { + DoTeleport(npcParams); + } + else if (npcAction == "@reset") + { + DoDelete(); + SetStop(FALSE); // a @resst will restart the original !Path after deleting the notecard. + } + + STATE = NULL; + TimerEvent(QUICK); // yeah I know, not possible this fast, we just go as fast as we can go - this is so we do not recurse the stack +} + + + +/////////////////// UTILITY Functions, not state-like ////////////////// + +// DEBUG(string) will chat a string or display it as hovertext if debug == TRUE +DEBUG(string str) { + if (debug && ! LSLEditor) + llOwnerSay( str); // Send the owner debug info + if (debug && LSLEditor) + llSay(0, str); // Send to the Console in LSLEDitor + if (iTitleText) { + llSetText(str,<1.0,1.0,1.0>,1.0); // show hovertext + + } +} + +GetDest(string npcParams) { + list dest = llParseString2List(npcParams, ["<", ",", ">"], []); + vDestPos.x = llList2Float(dest, 0); + vDestPos.y = llList2Float(dest, 1); + vDestPos.z = llList2Float(dest, 2); +} + +NPCReadNoteCard(string Note) { + DEBUG("NPCReadNoteCard"); + lNpcCommandList = llParseString2List(osGetNotecard(Note), ["\n"], []); +} + +integer SenseAvatar() +{ + //Returns a strided list of the UUID, position, and name of each avatar in the region + list avatars = llGetAgentList(AGENT_LIST_REGION ,[]); + integer numOfAvatars = llGetListLength(avatars); + if (numOfAvatars == 0) + { + DEBUG("No people"); + return 0; + } + //DEBUG("Located " + (string)numOfAvatars + " avatars and NPC's"); + + integer nAvatars; + integer i; + for( i = 0;i < numOfAvatars; i++) { + key aviKey = llList2Key(avatars,i); + if (!osIsNpc(aviKey)) { + list detail = llGetObjectDetails(aviKey,[OBJECT_POS]); + vector pos = llList2Vector(detail,0); + float dist = llVecDist(pos, llGetPos()); + if (dist < RANGE) + { + nAvatars++; + DEBUG("In range:" + llKey2Name(aviKey)); + } + } + } + //DEBUG("Located " + (string) nAvatars + " avatars"); + return nAvatars; +} + +// return TRUE if the avatar is owner when private is set, or TRUE if the avatar is in the same group and GROUP is set. +integer checkPerms() { + + integer group = (integer) KeyValueGet("pr"); + if (! group) + priPub = "Owner Only"; + else + priPub = "Group"; + + + if (llDetectedKey(0) == llGetOwner()){ + kUserKey = llDetectedKey(0); + return TRUE; + } + + if ( group && llDetectedGroup(0)) { + kUserKey = llDetectedKey(0); + return TRUE; + } + kUserKey = llDetectedKey(0); + return FALSE; +} + + + +NPCAnimate(string anim) +{ + DEBUG("Start Anim: " + anim); + if (llGetInventoryType(anim) == INVENTORY_ANIMATION ) { + + if (lastANIM != anim) { + if(llStringLength(lastANIM)) { + osNpcStopAnimation(NPCKey(), lastANIM); + } + osNpcPlayAnimation(NPCKey(), anim); + lastANIM = anim; + } + } else { + llSay(DEBUG_CHANNEL, "No animation named " + anim); + } +} + +// Kill a NPC by Name +Kill(string param) +{ + integer count; + list avatars = osGetAvatarList(); // Returns a strided list of the UUID, position, and name of each avatar in the region except the owner.\ + integer i; + integer j = llGetListLength(avatars); + for (i=0 ; i <= j; i+=3){ + + string desired = llList2String(avatars,i+2); + desired = llStringTrim(desired,STRING_TRIM); // should not be needed but is needed + + if (desired == param){ + vector v = llList2Vector(avatars,i+1); + key target = llList2Key(avatars,i); // get the UUID of the avatar + osNpcRemove(target); + + llOwnerSay("Removed " + param+ " at location " + (string) v); + count++; + } + } + + NPCEnabled = FALSE; // not in world + SaveKey(NULL_KEY ); // Rev 4.4 + + if (count) + llOwnerSay("Removed " + (string) count + " NPC's"); + else + llOwnerSay("Could not locate " + param); +} + + +// return a String for the position we are at. Strings used as the caller wants strings +string Pos() +{ + vector where = llGetPos(); // find the box position + + where.z += OffsetZ; // use the ground position + an offset + + if (LSLEditor) + where = <128,128,31 + llFrand(1)>; // force center of sim when editing + + // if attached the height will be too high by 1/2 the agent size + if (llGetAttached()) { + vector size = llGetAgentSize(llGetOwner()); + float Z = size.z; + where.z -= Z/2; + } + + // DEBUG("Pos= " + (string) where); + return (string) where; +} + +// setup a menu with a timer for timeouts, called by all make*() +menu() +{ + llListenRemove(iHandle); + iChannel = llCeil(llFrand(100000) + 20000); + iHandle = llListen(iChannel,"","",""); + TimerEvent(30.0); + MENU = TRUE; +} + +// make a text box +makeText(string Param) +{ + menu(); + llTextBox(kUserKey, Param, iChannel); +} + +// top level menu +makeMainMenu() +{ + menu(); + list buttons = ["Appearance","Recording","Save","Help","-","Erase RAM", priPub,relAbs,"-","Stop NPC",mSensor,"Start NPC"]; + llDialog(kUserKey,(string) llGetListLength(lCommands) + " Records",buttons,iChannel); +} + + +// Rev 1.4 +// top level menu for non group/ non owners +makeUserMenu() +{ + if (!allowUsers) return; + + menu(); + list buttons = ["Start NPC","Stop NPC"]; + llDialog(kUserKey,"Choose",buttons,iChannel); +} + + + +// programmable menu for @commands +makeMenu(list buttons) +{ + menu(); + llDialog(kUserKey,(string) llGetListLength(lCommands) + " Record",buttons,iChannel); +} + + +// make one or two text boxes with prompts +Text(string cmd, string p1, string p2) +{ + sCommand = cmd; + sParam2 = ""; + if (llStringLength(p2)) + sParam2 = p2; + + makeText(p1); +} + +// Set the Avatar Present flag - if sensors are off and we are force run, there will be one present. +ProcessSensor() +{ + integer SensorOn; + if ("on" == KeyValueGet("se")) + { + SensorOn = TRUE; // we need to scan for avatars + } else { + SensorOn = FALSE; // we need to scan for avatars + } + DEBUG("Sensor:" + (string) SensorOn); + + integer n = SenseAvatar(); + + DEBUG("Avatars:" + (string) n); + if (SensorOn && n) + avatarPresent = TRUE; // someone is here and we need to tell the system to run + else if (SensorOn && !n) + avatarPresent = FALSE; // someone is not here and we need to tell the system to stop + else { // sensor is off, lete see if there is a NPC. If so, we are ON + // DEBUG("NPCEnabled:" + (string) NPCEnabled); + + if (NPCEnabled) + avatarPresent = TRUE; + else + avatarPresent = FALSE; + } + + // start up from when when no one is near + if (avatarPresent && STATE == NobodyHome) + STATE = NULL; + + DEBUG("Avatar Present: " + (string) avatarPresent); +} + +vector CirclePoint(float radius) { + float x = llFrand(radius *2) - radius; // +/- radius, randomized + float y = llFrand(radius *2) - radius; // +/- radius, randomized + return ; // so this should always happen +} + +string KeyValueGet(string var) { + list dVars = llParseString2List(llGetObjectDesc(), ["&"], []); + do { + list data = llParseString2List(llList2String(dVars, 0), ["="], []); + string k = llList2String(data, 0); + if(k != var) jump continue; + //DEBUG("got " + var + " = " + llList2String(data, 1)); + return llList2String(data, 1); + @continue; + dVars = llDeleteSubList(dVars, 0, 0); + } while(llGetListLength(dVars)); + return ""; +} + +KeyValueSet(string var, string val) { + + //DEBUG("set " + var + " = " + val); + list dVars = llParseString2List(llGetObjectDesc(), ["&"], []); + if(llGetListLength(dVars) == 0) + { + llSetObjectDesc(var + "=" + val); + return; + } + list result = []; + do { + list data = llParseString2List(llList2String(dVars, 0), ["="], []); + string k = llList2String(data, 0); + if(k == "") jump continue; + if(k == var && val == "") jump continue; + if(k == var) { + result += k + "=" + val; + val = ""; + jump continue; + } + string v = llList2String(data, 1); + if(v == "") jump continue; + result += k + "=" + v; + @continue; + dVars = llDeleteSubList(dVars, 0, 0); + } while(llGetListLength(dVars)); + if(val != "") result += var + "=" + val; + llSetObjectDesc(llDumpList2String(result, "&")); +} + + +// clear RAM +Clr() { + + lCommands = []; + llOwnerSay("RAM Memory cleared. Notecards, if any, are not modified."); + makeMainMenu(); +} + +integer checkNoteCards() +{ + // Check that they have saved an Appeaance and Path notecard + integer num = llGetInventoryNumber(INVENTORY_NOTECARD); // how many notecards overall + + integer i; + integer count; + for (; i < num; i++){ + if (llGetInventoryName(INVENTORY_NOTECARD,i) == Notecard) + count++; + if (llGetInventoryName(INVENTORY_NOTECARD,i) == Appearance) + count++; + } + DEBUG("Checked " + (string) count + " Notecards"); + // if we have both, run the NPC + return count; +} + +Update(string SName) { + + // delete all NPC* scripts except myself + integer i; + integer j = llGetInventoryNumber(INVENTORY_SCRIPT); + for (i = 0; i < j; i++) { + string targetName = llGetInventoryName(INVENTORY_SCRIPT,i); + string match = llGetSubString(targetName,0,2); + + if (match == SName && llGetScriptName() != targetName){ + llOwnerSay("Upgrading " + targetName); + if (! LSLEditor){ // lets not kill the editor + llRemoveInventory(targetName); + } + } + } +} + +// Get all default saved params from the Description +GetSwitches() +{ + string rA = KeyValueGet("co"); // Get the remembered menu setting for Abs Vs Relative + if (rA == "A") + relAbs = "Absolute"; + else if (rA == "R") + relAbs = "Relative"; + else + relAbs = "Absolute"; + + + // reenable NPC if sensor is on. + if ("on" == KeyValueGet("se")) + { + NPCEnabled = TRUE; + mSensor = "Sense is On"; + ProcessSensor(); // fake 1 avatar to get it rezzed + } else { + mSensor = "Sense is Off"; + } + } + + +SaveKey(key akey) +{ + DEBUG("Saving Key of " + (string) akey); + KeyValueSet("key", akey); + if (akey != (key) KeyValueGet("key") ) + { + DEBUG("Fatal error, cannot save key"); + } + gNpcKey = akey; +} + + +key NPCKey() +{ + key akey = gNpcKey; // from cached copy + // gNpcKey saves a lot of CPU processing by caching the key, if blank we get it from the description + if (gNpcKey == NULL_KEY) + { + //DEBUG("Get DKey"); + akey = KeyValueGet("key"); // from Description of the prim + } + // DEBUG("NPC KEY:" + (string) akey); + return akey; +} + + +/////////////////// CODE BEGINS ////////////////// + + +default +{ + changed(integer change) { + if(change & CHANGED_REGION_START) { + llResetScript(); + } + } + + on_rez(integer start_param) + { + llResetScript(); + } + + state_entry() { + + llSetText("",<1,1,1>,1.0); // clr all hovertext- we may not be using it. + DoDelete(); // kill any NPC that is out running + Update("NPC"); // If dragged and ropped into a prim with any script named "NPC...", this will replace it. + GetSwitches(); // Get all default saved params from the Description + + // 4.1 allow listeners to send us commands + if (allowListener) + llListen(link_Channel,"","",""); + TimerEvent(TIMER); + } + + + touch_start(integer n) + { // if touched, make a menu + + if (checkPerms()) { + if (RecordPath == STATE) { + makeMenu(lAtButtons); + } else { + makeMainMenu(); + } + } else { + makeUserMenu(); + } + } + + // menu listener + listen(integer iChannel, string name, key id, string message) { + + // process @commands that come in via the listener + if (iChannel == link_Channel) + { + ParseMsg(message); + return; + } + + if (MENU) { + llListenRemove(iHandle); + MENU = 0; // menu is off + iHandle = 0; + } + + if (message == "Stop NPC") + { + lNpcCommandList = []; // force reload of notecard + NPCEnabled = FALSE; + if (NPCKey() != NULL_KEY){ + Kill(sNPCName); + sNPCName = ""; + } else { + bNPC_STOP = TRUE; + makeText("Enter name of an NPC to stop"); + } + } + else if (message == "Menu" ) { + makeMainMenu(); + } + else if (message == "Erase RAM"){ + Clr(); + } + else if (message == "Relative"){ + relAbs = "Absolute"; + KeyValueSet("co","A"); // remember coordinates = A + Clr(); + } + else if (message == "Absolute"){ + relAbs = "Relative"; + KeyValueSet("co","R"); // remember coordinates = R + Clr(); + } + else if (message == "Recording"){ + DoMenuForCommands(); // show them the recording menu + } + else if (message == "Owner Only") { + priPub = "Group"; + KeyValueSet("pr","1"); + + llOwnerSay("Group members have control"); + makeMainMenu(); + } + else if (message == "Group") { + priPub = "Owner Only"; + KeyValueSet("pr","0"); + llOwnerSay("Only you have control"); + makeMainMenu(); + } + else if (message == "Sense is On") { + mSensor ="Sense is Off"; + KeyValueSet("se", "off"); + llOwnerSay(mSensor); + makeMainMenu(); + } + else if (message == "Sense is Off") { + mSensor ="Sense is On"; + llOwnerSay(mSensor); + KeyValueSet("se", "on"); + + NPCEnabled = TRUE; + + integer count = checkNoteCards(); + if (count >= 2) { + DEBUG("Notecards ok, DoProcessNPCLine"); + DoProcessNPCLine(); + return; + } + if (LSLEditor) { + DoProcessNPCLine(); + return; + } + + llOwnerSay("You have not saved a recording and/or appearance, so you cannot start a NPC"); + makeMainMenu(); + } + else if (message == "Appearance") { + llRemoveInventory(Appearance); // delete the notecard + osAgentSaveAppearance(kUserKey,Appearance); // make the ntecard + llOwnerSay("Your outfit has been saved"); + makeMainMenu(); + } + else if (message == "Save") { + if (llGetListLength(lCommands) == 0) { + llOwnerSay("Nothing recorded, you need to make a recording first"); + makeMainMenu(); + return; + } + DoSave(); + } + else if (message == "Help"){ + llLoadURL(kUserKey,"Click to view help","http://www.outworldz.com/opensim/posts/NPC/"); + makeMainMenu(); + } + else if (message == "Start NPC") { + integer count = checkNoteCards(); + + NPCEnabled = TRUE; + + if (LSLEditor) { + DoProcessNPCLine(); + return; + } + + if (count >= 2) { + DEBUG("Notecards approved , calling DoProcessNPCLine"); + SetStop(FALSE); // Let's run the notecard + DoProcessNPCLine(); + return; + } + + llOwnerSay("You have not saved a recording or maybe an appearance, so we cannot start a NPC"); + + } + else if (bNPC_STOP){ + bNPC_STOP = FALSE; + Kill(message); + } + else if (message == ">>"){ + makeMenu(lMenu2); + } + else if (message == ">>>"){ + makeMenu(lMenu3); + } + else if (message == "<<") { + makeMenu(lAtButtons); + } + else if (message == "<<<") { + makeMenu(lMenu2); + } + else if (message == "@comment"){ + Text("# ","Enter a comment",""); + } + else if (message == "@stop"){ + lCommands += "@stop"+ "\n"; + makeMenu(lAtButtons); + } + else if (message == "@run"){ + lCommands += "@run=" + Pos() + "\n"; + llOwnerSay("Recorded position: " + Pos()); + makeMenu(lAtButtons); + } + else if (message == "@fly"){ + lCommands += "@fly=" + Pos() + "\n"; + llOwnerSay("Recorded position: " + Pos()); + makeMenu(lAtButtons); + } + else if (message == "@land"){ + lCommands += "@land=" + Pos() + "\n"; + llOwnerSay("Recorded position: " + Pos()); + makeMenu(lAtButtons); + } + else if (message == "@walk") { + lCommands += "@walk=" + Pos() + "\n"; + llOwnerSay("Recorded position: " + Pos()); + makeMenu(lAtButtons); + } + else if (message == "@stop"){ + lCommands += "@stop"+ "\n"; + makeMenu(lAtButtons); + } + else if (message == "@sound"){ + Text("@sound=","Enter a sound name or UUID to trigger",""); + } + else if (message == "@randsound"){ + lCommands += "@randsound"+ "\n"; + makeMenu(lAtButtons); + } + else if (message == "@say") { + Text("@say=","Enter what the NPC will say",""); + } + else if (message == "@whisper"){ + Text("@whisper=","Enter what the NPC will whisper",""); + } + else if (message == "@shout"){ + Text("@shout=","Enter what the NPC will shout",""); + } + else if (message == "@wander") { + Text("@wander=","Enter radius to wander","Enter number of wanders"); + } + else if (message == "@pause") { + Text("@pause=","Enter time to pause",""); + } + else if (message == "@rotate") { + Text("@rotate=","Enter degrees to rotate",""); + } + else if (message == "@sit"){ + Text("@sit=","Enter name of object to sit on",""); + } + else if (message == "@teleport"){ + lCommands += "@teleport=" + Pos() + "\n"; + llOwnerSay("teleport to position: " + Pos()); + makeMenu(lMenu3); + } + else if (message == "@touch"){ + Text("@touch=","Enter name of object to touch",""); + } + else if (message == "@cmd"){ + Text("@cmd=","Enter cjhannel to speak on","Enter text to speak"); + } + else if (message == "@stand"){ + lCommands += "@stand\n"; + llOwnerSay("Stand Recorded"); + makeMenu(lAtButtons); + } + else if (message == "@animate"){ + Text("@animate=","Enter animation name to play","Enter time to play the animation"); + } + else if (message == "@attach"){ + Text("@animate=","Enter inventory name to attach","Enter number of the attachment point (1-40)"); + } + else if (message == "@speed"){ + Text("@speed=","Enter a speed for the NPC, 1=100% normal speed, 0.5=50% speed",""); + } + + + // Save NPC name + else if (MakeNotecard == STATE) { + sNPCName = message; // in case we need to kill it. + + vector vDest = (vector) Pos(); + + if (relAbs == "Relative") + { + vDest -= llGetPos(); // just an offset for relative + } + sNotecard = "@spawn=" + message + "|" + (string) vDest + "\n"; + integer i; + integer j = llGetListLength(lCommands); + for (; i < j; i++){ + // get the command to save to the notecard + string line = llList2String(lCommands,i); + if (relAbs == "Absolute") { + sNotecard += line; // add the un-modified string to the notecard + } else { + // since we have to record absolute coords since we do not know where the box goes until they press Save, + // we process the absolute to relative conversion for walks here + list parts = llParseString2List(line,["="],[]); //get the @command + + if (llList2String(parts,0) == "@walk") { + vector vec = (vector) llList2String(parts,1) - llGetPos(); + sNotecard += "@walk=" + (string) vec + "\n"; + } + else if (llList2String(parts,0) == "@fly") { + vector vec = (vector) llList2String(parts,1) - llGetPos(); + sNotecard += "@fly=" + (string) vec + "\n"; + } + else if (llList2String(parts,0) == "@run") { + vector vec = (vector) llList2String(parts,1) - llGetPos(); + sNotecard += "@run=" + (string) vec + "\n"; + } + else if (llList2String(parts,0) == "@land") { + vector vec = (vector) llList2String(parts,1) - llGetPos(); + sNotecard += "@land=" + (string) vec + "\n"; + } + else { + sNotecard += line; // add the un-modified string to the notecard + } + } + } + llRemoveInventory(Notecard); // delete the old notecard + osMakeNotecard(Notecard,sNotecard); // Makes the notecard. + llSay(0,sNotecard); + llOwnerSay("Commands notecard has been written"); + STATE = NULL; + } // MakeNotecard + + else if (! llStringLength(sParam2)) { + lCommands += sCommand + message + "\n"; + llOwnerSay("Recorded"); + makeMenu(lAtButtons); + } + else if (llStringLength(sParam2)){ + sCommand = sCommand + message + "|"; + llOwnerSay("Recorded"); + makeText(sParam2); + sParam2 = ""; + } + + } + + + + timer(){ + // DEBUG("tick"); + + // if llDialog is up, kill the listener for the dialog box. + if (iHandle) { + llOwnerSay("Menu timed out"); + llListenRemove(iHandle); + iHandle = 0; + return; // ^^^^^^^^^^^^^^^^^^^^^^^ + } + // if NoBodyHome, we are sensing for an avatar + else if (NobodyHome == STATE) { + ProcessSensor(); + return; + } + // if we are spawning, we need time to rez the NPC, then start processing NPC Commands. + else if (Spawning == STATE) { + STATE = NULL; + TimerEvent(TIMER); + } + // We end aniamtions with a timer + else if (Animate == STATE){ + NPCAnimate(STAND); + TimerEvent(TIMER); + } + + else if (Walking == STATE) { + if (--iWaitCounter) { + DEBUG("still walking..."); + if (llVecDist(osNpcGetPos(NPCKey()), newDest) > MAXDIST) { + return; + } + } + + DEBUG("At Destination: " + (string) newDest); + + // walk, fly, run, land + if (walkstate == 1) { + NPCAnimate(STAND); + NPCWalkOption = OS_NPC_NO_FLY; + } else if (walkstate == 2) { + // nothing + } else if (walkstate == 3) { + NPCAnimate(STAND); + NPCWalkOption = OS_NPC_NO_FLY; + } else if (walkstate == 4) { + llShout(FLIGHT,"landing"); + NPCAnimate(STAND); + NPCWalkOption = OS_NPC_NO_FLY; + } + } + // Wandering timer + else if (Wander == STATE) { + if (--iWaitCounter) { // wait 60 seconds to get to a destination. + if (llVecDist(osNpcGetPos(NPCKey()), vWanderPos) > MAXDIST) + return; + } + + // see if wander counter == 0, if so, stop walking, go to stand and process next line + if(RAMwc == 0) { + NPCAnimate(STAND); + DEBUG("Wander ended, calling DoProcessNPCLine"); + STATE = NULL; + return; + } + // one less time to wander around + RAMwc--; + NPCAnimate(STAND); + TimerEvent(TIMER); + StateWanderhold(); + return; + } + // Wandering requires us to re-wander when we reach a destination + else if (WanderHold == STATE) { + StateWander(); + } + else if (DoProcess == STATE) { + TimerEvent(QUICK); + } + + STATE = NULL; + + // We always process a NPC line at end of timer. + DEBUG("Tick end, calling DoProcessNPCLine"); + DoProcessNPCLine(); + } + + // sensors are used for sitting on prims + // Neo Cortex: added different states to trigger sit or touch + sensor(integer num) { + if (Sit == STATE ) { + osNpcSit(NPCKey(), llDetectedKey(0), OS_NPC_SIT_NOW); + DEBUG("Seated, calling DoProcessNPCLine"); + + STATE = 0; + } else if (Touch == STATE) { + osNpcTouch(NPCKey(), llDetectedKey(0), LINK_THIS); + DEBUG("Touched, calling DoProcessNPCLine"); + STATE = 0; + } + DoProcessNPCLine(); + } + no_sensor(){ + DEBUG ("no target prim located, calling DoProcessNPCLine"); + DoProcessNPCLine(); + STATE = NULL; + } + + + link_message(integer sender, integer num, string str, key id){ + + if (num == 0) { + ParseMsg(str); + llMessageLinked(LINK_SET,-10,(string) llGetLinkNumber() + "|" + (string) llGetPos() ,NPCKey()); // broadcast the key on num = -1 + } + if (num == 99) + llResetScript(); + } + +} + +// __ END__ + + + + diff --git a/HyperGrid Story Nine/Nine/Dylan/NPC Custom for Dylan.lsl b/HyperGrid Story Nine/Nine/Dylan/NPC Custom for Dylan.lsl new file mode 100644 index 00000000..6cbed2dd --- /dev/null +++ b/HyperGrid Story Nine/Nine/Dylan/NPC Custom for Dylan.lsl @@ -0,0 +1,1694 @@ +// :SHOW:1 +// :CATEGORY:NPC +// :NAME:HyperGrid Story Nine +// :AUTHOR:Ferd Frederix +// :KEYWORDS:NPC, Puppeteer +// :CREATED:2015-11-24 20:25:33 +// :EDITED:2015-11-24 19:25:33 +// :ID:1087 +// :NUM:1837 +// :REV:9.3 +// :WORLD:OpenSim +// :DESCRIPTION: +// All in one NPC recorder player. +// Supports both absolute and relative paths and many new commands +// Add animations named "Fly, Walk, Stand and Run" +// Click Prim to use. +// Should be worn as a HUD to record. +// Put it on the ground and click Sensor or Start NPC when done. +// :CODE: +// This is Rev 4.5 09/26/2015 + +// Revision History +// Rev 1.1 10-2-2014 @Sit did not work. Minor tweaks to casting for lslEditor +// Rev 1.2 10-14-2014 @ sit had wrong type. +// Rev 1.3 relative movement fixed for @fly +// Rev 1.4 4-3-2014 allow anyone to use this, non owners and non group members can only start and stop. +// Rev 1.5 5-17-2014 set sensor to auto start on reboot of sim +// Rev 1.6 5-24-2014 move menu so you can get it by touching, removed many of the KeyValues to RAM for efficiency +// Rev 1.7 CHANGED_REGION_START, not CHANGED_REGION_START (Opensim difference) +// Rev 1.8 tuned up Kill NPC, added more flexible upgrader +// Rev 1.9 Better script injection by link message// Rev 2.0 Added osSetSpeed so you can speed up or slow down an NPC. +// Rev 2.1 No laggy sensor used exept to sit on stuff +// Rev 2.2 Various sensor fixes +// Rev 2.3 Sets No Sensor in menu, must be started by hand +// Rev 2.4 - reserved for patches to 2.3 if needed +// Rev 3.0 Refactor out into subs, not states to make command injection easier +// New command: @appearance=Notecardname so you can switch to a new notecard on the fly +// New command: @speed=1.0 which slows up ( < 1 ) or speeds up ( > 1) +// Rev 3.1 Commands are not interruptible by Link Message +// Rev 3.2 Sensor patches for consistency in removing the NPC +// Rev 3.3 Added Touch command by Neo.Cortex@hbase42/hopto/org:8002 +// Added Menu 3 for notecard and appearance commands +// Rev 3.4 animation timer cannot be zero or it shuts off timer tweaked +// solves the NPC starting up when no sensor is set. +// Rev 3.5 fixes saving to !Path notecard +// Rev 3.6 08-11-2015 @delete acts like @stop. TYjhe NPC now rezzes after an @go back in where it was deleted +// Rev 3.7 08-11-2015 @attach command added to load an attachment from the inventory to the NPC +// Rev 3.8 08-17-2015 process queued commands one at a time without calling ProcessNPCLine on link message +// Rev 3.9 08-23-2011 Queued command fixes including @delete which were not always working +// Rev 4.0 09-15-2015 Fixes for Sensor functions which continually rezzed a NPC when no one was around. +// Rev 4.1 09-20-2015 Added a Listener so link messages are not needed +// Rev 4.2 09-23-2015 Added @teleport= +// Rev 4.3 09-24-2015 Added @reset to restart the NPC at the very start of the !Path notecard +// @teleport works for relative and absolute modes +// Rev 4.4 09-26-2015 if it could not find the (deleted) NPC, it could not restart +// Rev 4.5 09-29-2015 remove wait for STATE == 0 +//*******************************************************************// + +// Instructions on how to use this are at http://www.outworldz.com/opensim/posts/NPC/ +// This is an OpenSim-only script. +// Author: Ferd Frederix aka Fred Beckhusen - fred@mitsi.com + +//////////////////////////////////////////////////////////////////////////////////////////// +// Original code was Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // +/////////////////////////////////////////////////////////////////////////////////////////// +// Please see: http://www.gnu.org/licenses/gpl.html for legal details, // +// rights of fair usage, the disclaimer and warranty conditions. // +/////////////////////////////////////////////////////////////////////////////////////////// +// The original NPC controller was from http://was.fm/opensim:npc +// Extensive additions and bug fixes by Fred Beckhusen, aka Ferd Frederix +// llSensor had two params swapped +// @Wander would wander where it had rezzed, not where it was. +// There was no 'no_sensor' event in sit, so if a @sit failed, the NPC got stuck +// The animation and walks always stopped old, then started new. It should be start new, then stop old so the default stand would be suppressed. +// New code: +// Merged with new Route recorder and notecard writer +// If the NPC failed to reach a destination it never moved on. +// Added WAIT global to tune this +// Exposed many tunable variables and ported the code +// Added floating point to times in notecard. +// Added @sound, @randsound, @whisper, @shout, and @cmd controls. +// notecards integers are not floats for better control +// +// Link Messages may be used to perform external control by injecting @commands into the stream of actions +// Example: +// To chat something, such as with a chat robot +// llMessageLinked(LINK_SET,0,"@npc_say=Hello",""); + +// This script assumes that NPCs and OSSl scripting is enabled in the OpenSim configuration. +// In order to enable them, the following changes must be made in the OpenSim.ini configuration file: +// +// ; Turn on OSSL +// AllowOSFunctions = true +// OSFunctionThreatLevel = Severe + +//[NPC] +// ;# {Enabled} {} {Enable Non Player Character (NPC) facilities} {true false} +// Enabled = true +// +// and then the server has to be restarted. +// please note that there are better ways to enable NPC in the latest Opensim. + +// Commands: All commands begin with an @ sign. All other lines are ignored +// @commands may have optional parameters. The syntax is always: +// @cmd=parm1|parm2 +// NaN in the table below meand Not a Number. This means there is no parameter + +//Command First Parameter Second Parameter Description +//@spawn name location (vector) Rezzes an NPC with name at a location. +//@appearance NoteCardName NaN switch the NPC appearance to a new notecard +//@walk destination (vector) NaN Makes the NPC walk to destination. +//@fly destination (vector) NaN Makes the NPC fly to destination. +//@land destination (vector) NaN Makes the NPC land at destination. +//@say string NaN Makes the NPC speak a phrase. +//@whisper string NaN Makes the NPC whisper a phrase. +//@shout string NaN Makes the NPC shout a phrase. +//@pause seconds (float) NaN Makes the NPC wait for a multiple of seconds. +//@wander radius (float) cycles (integer) Makes the NPC wander in radius, for cycles seconds. +//@delete NaN NaN Removes the NPC. Requires a link message to continue +//@goto label (string) NaN Jump to the label label in the script. +//@animate animation (string) time (float) Makes the NPC trigger the animation animation for time seconds. +//@sound sound_name NaN plays a sound from inventory +//@randsound NaN NaN Plays a random sound from inventory +//@rotate degrees (float) NaN Rotate the NPC degrees around the Z axis. +//@sit primitive name NaN Sit on a primitive with a given name. +//@touch primitive name NaN Touch on a primitive with a given name. +//@stand NaN NaN If sitting on a primitive, stand up. +//@cmd channel (integer) string Says string on channel, for controlling external gadgets +//@stop NaN NaN Halts the NPC script indefinitely. Can be started with a link message +//@go NaN NaN Continues on next notecard line, for use in link messages +//@speed speed (float) NaN from 0 to N, where 1.0 ius a normal speed of an avatar. 0.2 is a turtle. +//@notecard notename (string) NaN load a new Path notecard +//@attach InventoryName attachmentPoint load an attachment from the inventory to the NPC onto point +//@teleport destination (vector) NaN Makes the NPC teleport to destination in the same sim. They cannot tp to another sim or across the HG +//@reset NaN NaN Deletes the NPC, starts the !Path notecard over. + +// Constant attachmentPoint Comment +// ATTACH_CHEST 1 chest/sternum +// ATTACH_HEAD 2 head +// ATTACH_LSHOULDER 3 left shoulder +// ATTACH_RSHOULDER 4 right shoulder +// ATTACH_LHAND 5 left hand +// ATTACH_RHAND 6 right hand +// ATTACH_LFOOT 7 left foot +// ATTACH_RFOOT 8 right foot +// ATTACH_BACK 9 back +// ATTACH_PELVIS 10 pelvis +// ATTACH_MOUTH 11 mouth +// ATTACH_CHIN 12 chin +// ATTACH_LEAR 13 left ear +// ATTACH_REAR 14 right ear +// ATTACH_LEYE 15 left eye +// ATTACH_REYE 16 right eye +// ATTACH_NOSE 17 nose +// ATTACH_RUARM 18 right upper arm +// ATTACH_RLARM 19 right lower arm +// ATTACH_LUARM 20 left upper arm +// ATTACH_LLARM 21 left lower arm +// ATTACH_RHIP 22 right hip +// ATTACH_RULEG 23 right upper leg +// ATTACH_RLLEG 24 right lower leg +// ATTACH_LHIP 25 left hip +// ATTACH_LULEG 26 left upper leg +// ATTACH_LLLEG 27 left lower leg +// ATTACH_BELLY 28 belly/stomach/tummy +// ATTACH_LEFT_PEC 29 left pectoral +// ATTACH_RIGHT_PEC 30 right pectoral +// ATTACH_HUD_CENTER_2 31 HUD Center 2 +// ATTACH_HUD_TOP_RIGHT 32 HUD Top Right +// ATTACH_HUD_TOP_CENTER 33 HUD Top +// ATTACH_HUD_TOP_LEFT 34 HUD Top Left +// ATTACH_HUD_CENTER_1 35 HUD Center +// ATTACH_HUD_BOTTOM_LEFT 36 HUD Bottom Left +// ATTACH_HUD_BOTTOM 37 HUD Bottom +// ATTACH_HUD_BOTTOM_RIGHT 38 HUD Bottom Right +// ATTACH_NECK 39 neck +// ATTACH_AVATAR_CENTER 40 avatar center/root + + + +////////////////////////////////////////////////////////// +// DEBUG // +////////////////////////////////////////////////////////// +integer debug = FALSE; // set to TRUE or FALSE for debug chat on various actions +integer LSLEditor = FALSE; // set to to TRUE to working in LSLEditor, FALSE for in-world. + // you must also include the NPC commands found in the other script since LSLEditor does not support OpenSim +integer iTitleText = FALSE; // set to TRUE to see debug info in text above the controller + +////////////////////////////////////////////////////////// +// TUNABLE CONFIGURATION // +////////////////////////////////////////////////////////// +integer keyNum = -2; // (dylan) special number for link message to broadcast the NPC key +integer allowListener = TRUE; // set to TRUE to anable a command listener. Usually, this is setto FALSE +integer link_Channel = 4223; // some random number you want to talk to this gadget on. Best if large and negative +float TIMER = 2; // faster = less jerky stopping. How often the system checks the distance traveled. Fastest you can go is 0.5 seconds +float QUICK = 1; // when we need to move to the next state, we use a QUICK timer +string Appearance = "!Appearance"; // The name of the recorded Appearance notecard +string Notecard = "!Path"; // The name of the recorded routes +integer allowUsers = FALSE; // If true, any user can get a Start NPC and Stop NPC menu. Only groups and owners can get all commands if TRUE, or FALSE +float MAXDIST = 2.0; // how close a NPC has to get to a dest pos to continue to next state. Do not lower this too much, as it may miss the target +integer WANDERRAND = TRUE; // set to TRUE and they will pause during wanders a random number of seconds +float WANDERTIME = 3.0; // how long they stand after each @wander,if WANDERRAND is FALSE. If WANDERRAND is TRUE, this is the max time +integer WAIT = 30; // wait for this number of seconds for the NPC to reach a destination (for safety). If it fails to reach a target, it will move on after this time. +float RANGE = 150; // 1 to N meters - anyone this close to the controller will start NPCS if Sensor button is clicked +float REZTIME = 2.0; // wait this long for NPC to rez in, then start the process +string STAND = "Stand"; // the name of the default Stand animation +string WALK = "Walk"; // the name of the default Walk animation +string FLY = "Fly"; // the name of the default Fly animation +string RUN = "Run"; // the name of the default Run animation +string LAND = "Land"; // the name of the default land animation ( for birds only) +float OffsetZ = 0.5; // appear 0.5 meter above ground, this is added to all destinations to keep them from sinking in. +float SPEEDMULT =0.8; // 1.0 = regular avatar speed. Smaller numbers slow down walks. Large numbers speed them up. +integer FLIGHT = 299; // For controlling wings. A channel that is shouted at when flight starts and ends. "flying" or "landing" + +// DESCRIPTIONS FIELDS HAVE TO SURVIVE A RESET +// These vars are stored by saving them with KeyValueSet +// "pr" is a 0 if it is set for Owner Only, 1 for Group control +// "se" is "on" if Started +// "co" = "R" or "A" for relative or absolute addressing mode +// "key" = NPC key + +// These Globals used to be stored in description. Moved to RAM in V1.6 +float RAMPause; // @pause param +float RAMwd ; // @wander distance +integer RAMwc; // @wander count +float RAMrot; // @rotate +string RAMsit; // @sit primname +string RAMtouch; // @touch primname +string RAManimationName; // @animate animation (string) time (float) +float RAManimationTime; + +// other globals section +integer iChannel; // a listen channel, randomly assigned +integer iHandle; // the handle to it + +// NPC controls +vector newDest ; // tmp storage for the walks +integer iWaitCounter ; // wait for this number of seconds for the NPC to reach a desrtination +string sNPCName; // the name of the NPC that may be in world. So we can remove it. +integer bNPC_STOP = FALSE; // boolean to reuse a listener +integer Stopped = FALSE; // set to TRUE by link messages so we do not remember them +float fTimerVal ; // how long we wait when wandering (calculated) +float NPCEnabled; // true if the NPC is suppodes to be running + +// OS_NPC_CREATOR_OWNED will create an 'owned' NPC that will only respond to osNpc* commands issued from scripts that have the same owner as the one that created the NPC. +// OS_NPC_NOT_OWNED will create an 'unowned' NPC that will respond to any script that has OSSL permissions to call osNpc* commands. +integer NPCOptions = OS_NPC_CREATOR_OWNED; // only yhe owner of this box can control this NPC. + +integer walkstate = 0; // helps us reshare the walk state for run, fly and land - a bit of a hack, but it saves RAM. Has to be done this way because some bits of NPCWalkOption are asserted as 0 + +integer NPCWalkOption; // Some notes for what happens to NPCWalkOption: +// OS_NPC_FLY - Fly the avatar to the given position. The avatar will not land unless the OS_NPC_LAND_AT_TARGET option is also given. +// OS_NPC_NO_FLY - Do not fly to the target. The NPC will attempt to walk to the location. If it's up in the air then the avatar will keep bouncing hopeless until another move target is given or the move is stopped +//OS_NPC_LAND_AT_TARGET - If given and the avatar is flying, then it will land when it reaches the target. If OS_NPC_NO_FLY is given then this option has no effect. +// OS_NPC_RUNNING - if given, NPC avatar moves at running/fast flying speed, otherwise moves at walking/slow flying speed. + +// menus +string mSensor="Sense is Off"; // Sensor or "No Sensor" + +list lAtButtons = ["Menu","-", ">>", "@run", "@walk", "@fly", "@land", "@wander", "@sit", "@stand","@animate","@rotate"]; +list lMenu2 = ["<<", "@comment", ">>>", "@stop", "@say", "@whisper","@shout","@sound","@randsound","@cmd", "@pause", "@delete"]; +list lMenu3 = ["<<<","@notecard","@appearance", "@touch", "@speed", "@attach", "@teleport","-", "-", "-", "-", "-" ]; + +string sCommand; // place to store a command for two-prompted ones +string sParam2; // place to store a prompt for two-prompted ones +string priPub = "Owner Only"; // Private or Group +key kUserKey; // the person who is controlling the avatar, not the Owner +// the command lists +list lCommands; // commands are stored here +list lNpcCommandList; // Storage for the NPC script. +string npcAction; // Storage for the next action. @cmd=0|hello, this becomes @cmd +string npcParams; // Storage for the param, @cmd=0|hello, this becomes 0|hello + +// misc vars +string sNotecard; // commands are stored here temporarily for dumping +vector vWanderPos; // a place to wander to +string lastANIM ; // last animation run +// Sensor +integer avatarPresent; // Sensor sets this flag when people are within Range. + +// Coordinate control +vector vInitialPos ; // Vector that will be filled by the script with the initial starting position in region coordinates. +vector vDestPos = ZERO_VECTOR; // Storage for destination position. +string relAbs = "Relative"; // absolute vs relative positioning + + +// STATES +integer MENU ; // processing a dialog box state, may be concurrent with STATE +integer STATE; // state storage +integer NULL = 0; // the null state +integer MakeNotecard = 1; // displaying a text box for NPC name +integer RecordPath = 2; // displaying a path notecard menu +integer NobodyHome = 3; // looking for an avatar +integer Spawning = 4; // spawning an avatar +integer Animate = 5; // animation timer needed +integer Walking = 6; // Hey! I am walking here! +integer Wander = 7; // Wandering around neeeds a timer, too +integer WanderHold = 8; // We reached a wander point +integer DoProcess = 9; // Set this to make it process a new command +integer Touch = 10; // Timer is busy sensing something to touch +integer Sit = 11; // Timer is busy sensing something to sit on +integer Paused = 12; // Timer is busy pausing + +key gNpcKey = NULL_KEY; // global key storage for the one NPC, to save CPU cycles +list Stack ; // a command stack from link message input + +/////////////////////////////////////////////////////////////////////////// +// FUNCTIONS // +/////////////////////////////////////////////////////////////////////////// + + +TimerEvent(float timesent) +{ + if (LSLEditor) + timesent *= 5; // slow thinggs doen when the LSLEDITOR is in use + + DEBUG("Setting timer: " + (string) timesent); + llSetTimerEvent(timesent); +} + +// for 4.1 parse a message from a Listen or a Link message +ParseMsg(string str) { + DEBUG("Command In:" + str); + if (str=="@go") { + SetStop(FALSE); // Let's run the notecard + DEBUG("@go running"); + DoProcessNPCLine(); + } else { + Stack += [str]; // take anything, the controller will filter away non @ stuff + if (STATE == NULL) + DoProcessNPCLine(); // v 4.5 remove wait for STATE == 0 + } +} + +SetStop(integer what) +{ + DEBUG("Stopped set to " + (string ) what); + Stopped = what; +} +// Do* functions are much like states from the old V2 scripts. + +// Save a Path notecard +DoSave() +{ + STATE = MakeNotecard; + makeText("Stand where you want the NPC to appear, and enter the NPC Name"); +} + +// This function is used to record the path for the NPC +// Each command can take 0, 1, or 2 params +DoMenuForCommands() { + makeMenu(lAtButtons); +} + + +// No one is here when sensors were on, so we kill off the NPC +DoNobodyHome() +{ + DEBUG("Nobody Home"); + STATE = NobodyHome; + if (NPCKey() != NULL_KEY) { + osNpcRemove(NPCKey()); + SaveKey(NULL_KEY); + } + TimerEvent(5); // keep ticking to sense avatars +} + + +/////////////////////// STATELIKE BEHAVIOUR ///////////// +// these StateXX functions need to wait on a timer to fire. + +// Create a NPC +StateSpawn() { + DEBUG("state spawn " + sNPCName); + + NPCEnabled = TRUE; // in world + // see if there is already one out there. + if (NPCKey() != NULL_KEY) { + DEBUG("Already living"); + return; + } + + STATE = Spawning; + list name = llParseString2List(sNPCName, [" "], []); + + vector vRezPos = vInitialPos; + if (relAbs == "Relative"){ + vRezPos += llGetPos(); + } + + // llSay(0,llDumpList2String(name,",")); + + DEBUG("Rezzing NPC name " +llList2String(name, 0)+ llList2String(name, 1) + " at "+ (string) vRezPos); + key aKey = osNpcCreate(llList2String(name, 0), llList2String(name, 1), vRezPos, Appearance, NPCOptions); + + llMessageLinked(LINK_SET,keyNum,"",aKey); // bboradcast the key on num = -1 + SaveKey(aKey); // save in description and global, too + + osSetSpeed(aKey,SPEEDMULT); // 1.9 speed multiplier + TimerEvent(REZTIME); + NPCAnimate(STAND); +} + +StateSit() { + DEBUG ("state sit - looking for " + RAMsit); + STATE=Sit; + llSensor(RAMsit, "", PASSIVE|ACTIVE|SCRIPTED, 96, PI); +} + +StateTouch() { + DEBUG ("state touch - looking for " + RAMtouch); + STATE = Touch; + llSensor(RAMtouch, "", PASSIVE|ACTIVE|SCRIPTED, 96, PI); +} + +DoStand() { + DEBUG("state stand"); + osNpcStand(NPCKey()); +} + + +StateAnimate() { + + DEBUG("state animate"); + STATE = Animate; + NPCAnimate(RAManimationName); + if (RAManimationTime <=0 ) // V 3.4 tweak + RAManimationTime = 1; + TimerEvent(RAManimationTime); +} + +StateWalk() { + + DEBUG("Start Walk"); + //DEBUG("NPCWalkOption = " + (string) NPCWalkOption); + STATE = Walking; + + // walk, fly, run, land + if (walkstate == 1) { + NPCAnimate(WALK); + } else if (walkstate == 2) { + llShout(FLIGHT,"flying"); + NPCAnimate(FLY); + } else if (walkstate == 3) { + NPCAnimate(RUN); + } else if (walkstate == 4) { + NPCAnimate(LAND); + } + newDest = vDestPos ; + newDest.z += OffsetZ; + + // notecard is stored as offsets from this box with relative addressing. Convert to absolute + if (relAbs == "Relative"){ + newDest += llGetPos(); + } + + DEBUG("Moveto:" + (string) newDest); + osNpcMoveToTarget(NPCKey(), newDest, NPCWalkOption); + iWaitCounter = WAIT; // wait 60 seconds to get to a destination. + TimerEvent(TIMER); +} + + +StateWander(){ + DEBUG("state wander"); + STATE = Wander; + + vector point = CirclePoint(RAMwd); + DEBUG("CirclePoint:" + (string) point); + vWanderPos = vDestPos + point; + DEBUG("vWanderPos:" + (string) vWanderPos); + + fTimerVal = WANDERTIME; // default time to pause after each wander + if (WANDERRAND) + fTimerVal = llFrand(WANDERTIME) + 1; // override, they want random times + + NPCAnimate(WALK); + + DEBUG("Wander to:" + (string) vWanderPos); + + osNpcMoveToTarget(NPCKey(), vWanderPos, NPCWalkOption); + iWaitCounter = WAIT; // wait 60 seconds to get to a destination. + TimerEvent(TIMER); +} + +StateWanderhold() { + + DEBUG("Wander Hold"); + STATE = WanderHold; + + // now that we have reached a wander spot, slow the timer down to the desired value + TimerEvent(fTimerVal); +} + + + +DoRotate() { + DEBUG("@rotate=" + (string) RAMrot); + osNpcSetRot(NPCKey(), llEuler2Rot(<0,0,RAMrot> * DEG_TO_RAD)); +} + + + +// @pause=10 will do nothing for 10 seconds +DoPause() { + STATE = Paused; + if (RAMPause < 0.1) + RAMPause = 0.1; + DEBUG("@pause=" + (string)RAMPause); + TimerEvent(RAMPause); +} + + +// @stop makes the NPC stop moving in whatever state it is in. You have to linkmessage to get moving again +DoStop() { + DEBUG("NPC is Stopped"); + STATE = 0; // accept commands + SetStop(TRUE); // Link controlled - we mnust have a @go to continue with notecards + TimerEvent(0); + Stack = []; // v3.8 +} + +// @delete removes the NPC forever. Next command starts it up again at the beginning +DoDelete() { + DEBUG("state delete"); + osNpcRemove(NPCKey()); + SaveKey(NULL_KEY); + TimerEvent(0); + Stack = []; // v3.8 + STATE = NULL; // accept commands +} + +// change the appearance of the NPC +DoAppearance(string notecard) { + DEBUG("state appearance"); + if (llGetInventoryType(notecard) == INVENTORY_NOTECARD){ + DEBUG("Load appearance " + notecard); + osNpcLoadAppearance(NPCKey(),notecard); + } + STATE = NULL; // accept commands +} + +// Change the avatar speed +DoSpeed(string speed) { + float newspeed = (float) speed; + if (newspeed > 0.1 && newspeed < 5.0) {// sanity check + osSetSpeed(NPCKey(),newspeed); + } + STATE = NULL; // accept commands +} + +DoTeleport(string params) { + list Data = llParseString2List(params, ["|"], []); + string itemName = llList2String(Data, 0); + vector Dest = (vector) itemName; + if (Dest != ZERO_VECTOR) { + if (relAbs == "Relative"){ + Dest += llGetPos(); + } + osTeleportAgent( NPCKey(), llGetRegionName(), Dest, ZERO_VECTOR ); + + } else { + llSay(DEBUG_CHANNEL,"Attempt to teleport to <0,0,0> probably not what you intended: @teleport="); + } + STATE = NULL; // accept commands +} + + + +DoNewNote (string card) { + DEBUG("Load Notecard " + card); + NPCReadNoteCard(card); + SetStop(FALSE); + STATE = NULL; // accept commands +} +DoAttach(string params) { + + list Data = llParseString2List(params, ["|"], []); + string itemName = llList2String(Data, 0); + integer attachmentPoint = (integer) llList2String(Data, 1); + if (attachmentPoint > 0 + && attachmentPoint < 40 + && llGetInventoryType(itemName) == INVENTORY_OBJECT + ) + { + osForceAttachToOtherAvatarFromInventory(NPCKey(),itemName,attachmentPoint); + } + STATE = NULL; // accept commands +} + +// This loops over the notecard, processing each command +DoProcessNPCLine() { + DEBUG("ProcessNPCLine, stopped = " + (string) Stopped); + + STATE = DoProcess; + + // auto load a notecard + if (! llGetListLength(lNpcCommandList)) { + DEBUG("Read Notecard"); + NPCReadNoteCard(Notecard); + } + + // look for link messages on the stack + string next = llList2String(Stack,0); // lets see if there is anything from a link message + if (llStringLength(next)) + { + Stack = llDeleteSubList(Stack,0,0); + ProcessCmd(next); //lets do this command instead. + return; + } + + // @stop issued? + if (Stopped) { + TimerEvent(0); + DEBUG("Stopped, waiting for input"); + STATE = NULL; + return; + } + + // No, we have an @go for liftoff + next = llList2String(lNpcCommandList, 0); // get the next command + DEBUG("Execute:" + next); + lNpcCommandList = llDeleteSubList(lNpcCommandList, 0, 0); // delete it + + if (llGetListLength(lNpcCommandList) == 0) { + DEBUG("EOF"); + } + ProcessCmd(next); +} + + + +ProcessCmd(string cmd) { + + DEBUG("ProcessCmd:" + cmd); + llMessageLinked(LINK_SET,keyNum,"",NPCKey()); // bboradcast the key on num = -1 + if (llGetSubString(cmd, 0, 0) != "@") { + DEBUG("ignoring"); + TimerEvent(QUICK); // this is so we do not recurse the stack + STATE = NULL; + return; + } + + list data = llParseString2List(cmd, ["="], []); + npcAction = llToLower(llStringTrim(llList2String(data, 0), STRING_TRIM)); + + DEBUG("Action:" + npcAction); + npcParams = llStringTrim(llList2String(data, 1), STRING_TRIM); + DEBUG("Params:" + npcParams); + + @commands; + + ProcessSensor(); + + if(npcAction == "@spawn") { + DEBUG("@spawn npcParams "); + list spawnData = llParseString2List(npcParams, ["|"], []); + sNPCName =llList2String(spawnData, 0); // V 1.6 name in RAM + + vInitialPos = (vector) llList2String(spawnData, 1); + DEBUG("Coords for NPC at " + (string) vInitialPos); + StateSpawn(); + return; + } + + if (! avatarPresent){ + DoNobodyHome(); + DEBUG("No avatar nearby"); + STATE = NULL; + return; + } else { + if ( NPCKey() == NULL_KEY) { + StateSpawn(); + } + } + + + + + if(npcAction == "@stop") { + DoStop(); + STATE = NULL; + return; + } + else if(npcAction == "@goto") { + DEBUG("goto"); + integer lastIdx = llGetListLength(lNpcCommandList)-1; + lNpcCommandList = llDeleteSubList(lNpcCommandList, lastIdx, lastIdx); + // Wind commands till goto label. + @wind; + string next1 = llList2String(lNpcCommandList, 0); + lNpcCommandList = llDeleteSubList(lNpcCommandList, 0, 0); + lNpcCommandList += next1; + if(next1 != npcParams) jump wind; + // Wind the label too. + next1 = llList2String(lNpcCommandList, 0); + lNpcCommandList = llDeleteSubList(lNpcCommandList, 0, 0); + lNpcCommandList += next1; + // Get next command. + list data1 = llParseString2List(next1, ["="], []); + npcAction = llToLower(llStringTrim(llList2String(data1, 0), STRING_TRIM)); + npcParams = llStringTrim(llList2String(data1, 1), STRING_TRIM); + // Reschedule. + jump commands; + } + else if(npcAction == "@sound") { + DEBUG("sound"); + llTriggerSound(npcParams,1.0); + } + else if(npcAction == "@randsound") { + DEBUG("@randsound"); + integer N = llGetInventoryNumber(INVENTORY_SOUND); + integer rand = llCeil(llFrand(N)) -1; // pick a random sound + string toPlay = llGetInventoryName(INVENTORY_SOUND,rand); + llTriggerSound(toPlay,1.0); + } + else if(npcAction == "@walk") { + DEBUG("@walk"); + GetDest(npcParams); + walkstate = 1;// walking + NPCWalkOption = OS_NPC_NO_FLY ; + StateWalk(); + return; + } + else if(npcAction == "@fly") { + GetDest(npcParams); + walkstate = 2;// flying + NPCWalkOption = OS_NPC_FLY ; + StateWalk(); + return; + } + else if(npcAction == "@run") { + DEBUG("@run"); + GetDest(npcParams); + walkstate = 3;// running + NPCWalkOption = OS_NPC_NO_FLY | OS_NPC_RUNNING; + StateWalk(); + return; + } + else if(npcAction == "@land") { + DEBUG("@land"); + GetDest(npcParams); + walkstate = 4;// landing + NPCWalkOption= OS_NPC_FLY | OS_NPC_LAND_AT_TARGET ; + StateWalk(); + return; + } + else if(npcAction == "@say") { + DEBUG("@say " + npcParams); + osNpcSay(NPCKey(), 0, npcParams); + } + else if(npcAction == "@shout") { + DEBUG("@shout"); + osNpcShout(NPCKey(),0, npcParams); + } + else if(npcAction == "@whisper") { + DEBUG("@whisper " + npcParams); + osNpcWhisper(NPCKey(),0, npcParams); + } + // speak a command on a channel, so you can open doors and control stuff. + else if(npcAction == "@cmd") { + DEBUG("@cmd"); + list dataToSpeak = llParseString2List(npcParams, ["|"], []); + string channel = llList2String(dataToSpeak,0); + DEBUG("Channel:"+(string) channel); + integer iChannel = (integer) channel; + string stringToSpeak = llList2String(dataToSpeak,1); + llSay(iChannel, stringToSpeak); + } + // stop everything + else if(npcAction == "@pause") { + RAMPause = (float) npcParams; + DoPause(); + return; + } + else if(npcAction == "@wander") { + list wanderData = llParseString2List(npcParams, ["|"], []); + RAMwd = (float) llList2String(wanderData, 0); + RAMwc = (integer) llList2String(wanderData, 1); + vDestPos = osNpcGetPos(NPCKey()); // set the wander start + DEBUG("Starting at " + (string) vDestPos); + StateWander(); + return; + } + else if(npcAction == "@rotate") { + RAMrot = (float) npcParams; + DoRotate(); + } + else if(npcAction == "@sit") { + RAMsit= npcParams; + StateSit(); + return; + } + else if(npcAction == "@touch") { + RAMtouch= npcParams; + StateTouch(); + return; + } + else if(npcAction == "@stand") { + DoStand(); + } + else if(npcAction == "@delete") { + DoDelete(); + SetStop(TRUE); // Link controlled - we mnust have a @go to continue with notecards + return; + } + else if(npcAction == "@animate") { + list animateData = llParseString2List(npcParams, ["|"], []); + RAManimationName = llList2String(animateData, 0); + RAManimationTime = (float) llList2String(animateData, 1); + StateAnimate(); + return; + } + else if(npcAction == "@appearance" ){ + DoAppearance(npcParams); + } + else if (npcAction =="@speed") { + DoSpeed(npcParams); + } + else if (npcAction =="@notecard") { + DoNewNote(npcParams); + Notecard = npcParams; + } + else if (npcAction == "@attach") + { + DoAttach(npcParams); + } + else if (npcAction == "@teleport") + { + DoTeleport(npcParams); + } + else if (npcAction == "@reset") + { + DoDelete(); + SetStop(FALSE); // a @resst will restart the original !Path after deleting the notecard. + } + + STATE = NULL; + TimerEvent(QUICK); // yeah I know, not possible this fast, we just go as fast as we can go - this is so we do not recurse the stack +} + + + +/////////////////// UTILITY Functions, not state-like ////////////////// + +// DEBUG(string) will chat a string or display it as hovertext if debug == TRUE +DEBUG(string str) { + if (debug && ! LSLEditor) + llOwnerSay( str); // Send the owner debug info + if (debug && LSLEditor) + llSay(0, str); // Send to the Console in LSLEDitor + if (iTitleText) { + llSetText(str,<1.0,1.0,1.0>,1.0); // show hovertext + + } +} + +GetDest(string npcParams) { + list dest = llParseString2List(npcParams, ["<", ",", ">"], []); + vDestPos.x = llList2Float(dest, 0); + vDestPos.y = llList2Float(dest, 1); + vDestPos.z = llList2Float(dest, 2); +} + +NPCReadNoteCard(string Note) { + DEBUG("NPCReadNoteCard"); + lNpcCommandList = llParseString2List(osGetNotecard(Note), ["\n"], []); +} + +integer SenseAvatar() +{ + //Returns a strided list of the UUID, position, and name of each avatar in the region + list avatars = llGetAgentList(AGENT_LIST_REGION ,[]); + integer numOfAvatars = llGetListLength(avatars); + if (numOfAvatars == 0) + { + DEBUG("No people"); + return 0; + } + //DEBUG("Located " + (string)numOfAvatars + " avatars and NPC's"); + + integer nAvatars; + integer i; + for( i = 0;i < numOfAvatars; i++) { + key aviKey = llList2Key(avatars,i); + if (!osIsNpc(aviKey)) { + list detail = llGetObjectDetails(aviKey,[OBJECT_POS]); + vector pos = llList2Vector(detail,0); + float dist = llVecDist(pos, llGetPos()); + if (dist < RANGE) + { + nAvatars++; + DEBUG("In range:" + llKey2Name(aviKey)); + } + } + } + //DEBUG("Located " + (string) nAvatars + " avatars"); + return nAvatars; +} + +// return TRUE if the avatar is owner when private is set, or TRUE if the avatar is in the same group and GROUP is set. +integer checkPerms() { + + integer group = (integer) KeyValueGet("pr"); + if (! group) + priPub = "Owner Only"; + else + priPub = "Group"; + + + if (llDetectedKey(0) == llGetOwner()){ + kUserKey = llDetectedKey(0); + return TRUE; + } + + if ( group && llDetectedGroup(0)) { + kUserKey = llDetectedKey(0); + return TRUE; + } + kUserKey = llDetectedKey(0); + return FALSE; +} + + + +NPCAnimate(string anim) +{ + DEBUG("Start Anim: " + anim); + if (llGetInventoryType(anim) == INVENTORY_ANIMATION ) { + + if (lastANIM != anim) { + if(llStringLength(lastANIM)) { + osNpcStopAnimation(NPCKey(), lastANIM); + } + osNpcPlayAnimation(NPCKey(), anim); + lastANIM = anim; + } + } else { + llSay(DEBUG_CHANNEL, "No animation named " + anim); + } +} + +// Kill a NPC by Name +Kill(string param) +{ + integer count; + list avatars = osGetAvatarList(); // Returns a strided list of the UUID, position, and name of each avatar in the region except the owner.\ + integer i; + integer j = llGetListLength(avatars); + for (i=0 ; i <= j; i+=3){ + + string desired = llList2String(avatars,i+2); + desired = llStringTrim(desired,STRING_TRIM); // should not be needed but is needed + + if (desired == param){ + vector v = llList2Vector(avatars,i+1); + key target = llList2Key(avatars,i); // get the UUID of the avatar + osNpcRemove(target); + + llOwnerSay("Removed " + param+ " at location " + (string) v); + count++; + } + } + + NPCEnabled = FALSE; // not in world + SaveKey(NULL_KEY ); // Rev 4.4 + + if (count) + llOwnerSay("Removed " + (string) count + " NPC's"); + else + llOwnerSay("Could not locate " + param); +} + + +// return a String for the position we are at. Strings used as the caller wants strings +string Pos() +{ + vector where = llGetPos(); // find the box position + + where.z += OffsetZ; // use the ground position + an offset + + if (LSLEditor) + where = <128,128,31 + llFrand(1)>; // force center of sim when editing + + // if attached the height will be too high by 1/2 the agent size + if (llGetAttached()) { + vector size = llGetAgentSize(llGetOwner()); + float Z = size.z; + where.z -= Z/2; + } + + // DEBUG("Pos= " + (string) where); + return (string) where; +} + +// setup a menu with a timer for timeouts, called by all make*() +menu() +{ + llListenRemove(iHandle); + iChannel = llCeil(llFrand(100000) + 20000); + iHandle = llListen(iChannel,"","",""); + TimerEvent(30.0); + MENU = TRUE; +} + +// make a text box +makeText(string Param) +{ + menu(); + llTextBox(kUserKey, Param, iChannel); +} + +// top level menu +makeMainMenu() +{ + menu(); + list buttons = ["Appearance","Recording","Save","Help","-","Erase RAM", priPub,relAbs,"-","Stop NPC",mSensor,"Start NPC"]; + llDialog(kUserKey,(string) llGetListLength(lCommands) + " Records",buttons,iChannel); +} + + +// Rev 1.4 +// top level menu for non group/ non owners +makeUserMenu() +{ + if (!allowUsers) return; + + menu(); + list buttons = ["Start NPC","Stop NPC"]; + llDialog(kUserKey,"Choose",buttons,iChannel); +} + + + +// programmable menu for @commands +makeMenu(list buttons) +{ + menu(); + llDialog(kUserKey,(string) llGetListLength(lCommands) + " Record",buttons,iChannel); +} + + +// make one or two text boxes with prompts +Text(string cmd, string p1, string p2) +{ + sCommand = cmd; + sParam2 = ""; + if (llStringLength(p2)) + sParam2 = p2; + + makeText(p1); +} + +// Set the Avatar Present flag - if sensors are off and we are force run, there will be one present. +ProcessSensor() +{ + integer SensorOn; + if ("on" == KeyValueGet("se")) + { + SensorOn = TRUE; // we need to scan for avatars + } else { + SensorOn = FALSE; // we need to scan for avatars + } + DEBUG("Sensor:" + (string) SensorOn); + + integer n = SenseAvatar(); + + DEBUG("Avatars:" + (string) n); + if (SensorOn && n) + avatarPresent = TRUE; // someone is here and we need to tell the system to run + else if (SensorOn && !n) + avatarPresent = FALSE; // someone is not here and we need to tell the system to stop + else { // sensor is off, lete see if there is a NPC. If so, we are ON + // DEBUG("NPCEnabled:" + (string) NPCEnabled); + + if (NPCEnabled) + avatarPresent = TRUE; + else + avatarPresent = FALSE; + } + + // start up from when when no one is near + if (avatarPresent && STATE == NobodyHome) + STATE = NULL; + + DEBUG("Avatar Present: " + (string) avatarPresent); +} + +vector CirclePoint(float radius) { + float x = llFrand(radius *2) - radius; // +/- radius, randomized + float y = llFrand(radius *2) - radius; // +/- radius, randomized + return ; // so this should always happen +} + +string KeyValueGet(string var) { + list dVars = llParseString2List(llGetObjectDesc(), ["&"], []); + do { + list data = llParseString2List(llList2String(dVars, 0), ["="], []); + string k = llList2String(data, 0); + if(k != var) jump continue; + //DEBUG("got " + var + " = " + llList2String(data, 1)); + return llList2String(data, 1); + @continue; + dVars = llDeleteSubList(dVars, 0, 0); + } while(llGetListLength(dVars)); + return ""; +} + +KeyValueSet(string var, string val) { + + //DEBUG("set " + var + " = " + val); + list dVars = llParseString2List(llGetObjectDesc(), ["&"], []); + if(llGetListLength(dVars) == 0) + { + llSetObjectDesc(var + "=" + val); + return; + } + list result = []; + do { + list data = llParseString2List(llList2String(dVars, 0), ["="], []); + string k = llList2String(data, 0); + if(k == "") jump continue; + if(k == var && val == "") jump continue; + if(k == var) { + result += k + "=" + val; + val = ""; + jump continue; + } + string v = llList2String(data, 1); + if(v == "") jump continue; + result += k + "=" + v; + @continue; + dVars = llDeleteSubList(dVars, 0, 0); + } while(llGetListLength(dVars)); + if(val != "") result += var + "=" + val; + llSetObjectDesc(llDumpList2String(result, "&")); +} + + +// clear RAM +Clr() { + + lCommands = []; + llOwnerSay("RAM Memory cleared. Notecards, if any, are not modified."); + makeMainMenu(); +} + +integer checkNoteCards() +{ + // Check that they have saved an Appeaance and Path notecard + integer num = llGetInventoryNumber(INVENTORY_NOTECARD); // how many notecards overall + + integer i; + integer count; + for (; i < num; i++){ + if (llGetInventoryName(INVENTORY_NOTECARD,i) == Notecard) + count++; + if (llGetInventoryName(INVENTORY_NOTECARD,i) == Appearance) + count++; + } + DEBUG("Checked " + (string) count + " Notecards"); + // if we have both, run the NPC + return count; +} + +Update(string SName) { + + // delete all NPC* scripts except myself + integer i; + integer j = llGetInventoryNumber(INVENTORY_SCRIPT); + for (i = 0; i < j; i++) { + string targetName = llGetInventoryName(INVENTORY_SCRIPT,i); + string match = llGetSubString(targetName,0,2); + + if (match == SName && llGetScriptName() != targetName){ + llOwnerSay("Upgrading " + targetName); + if (! LSLEditor){ // lets not kill the editor + llRemoveInventory(targetName); + } + } + } +} + +// Get all default saved params from the Description +GetSwitches() +{ + string rA = KeyValueGet("co"); // Get the remembered menu setting for Abs Vs Relative + if (rA == "A") + relAbs = "Absolute"; + else if (rA == "R") + relAbs = "Relative"; + else + relAbs = "Absolute"; + + + // reenable NPC if sensor is on. + if ("on" == KeyValueGet("se")) + { + NPCEnabled = TRUE; + mSensor = "Sense is On"; + ProcessSensor(); // fake 1 avatar to get it rezzed + } else { + mSensor = "Sense is Off"; + } + } + + +SaveKey(key akey) +{ + DEBUG("Saving Key of " + (string) akey); + KeyValueSet("key", akey); + if (akey != (key) KeyValueGet("key") ) + { + DEBUG("Fatal error, cannot save key"); + } + gNpcKey = akey; +} + + +key NPCKey() +{ + key akey = gNpcKey; // from cached copy + // gNpcKey saves a lot of CPU processing by caching the key, if blank we get it from the description + if (gNpcKey == NULL_KEY) + { + //DEBUG("Get DKey"); + akey = KeyValueGet("key"); // from Description of the prim + } + // DEBUG("NPC KEY:" + (string) akey); + return akey; +} + + +/////////////////// CODE BEGINS ////////////////// + + +default +{ + changed(integer change) { + if(change & CHANGED_REGION_START) { + llResetScript(); + } + } + + on_rez(integer start_param) + { + llResetScript(); + } + + state_entry() { + + llSetText("",<1,1,1>,1.0); // clr all hovertext- we may not be using it. + DoDelete(); // kill any NPC that is out running + Update("NPC"); // If dragged and ropped into a prim with any script named "NPC...", this will replace it. + GetSwitches(); // Get all default saved params from the Description + + // 4.1 allow listeners to send us commands + if (allowListener) + llListen(link_Channel,"","",""); + TimerEvent(TIMER); + } + + + touch_start(integer n) + { // if touched, make a menu + + if (checkPerms()) { + if (RecordPath == STATE) { + makeMenu(lAtButtons); + } else { + makeMainMenu(); + } + } else { + makeUserMenu(); + } + } + + // menu listener + listen(integer iChannel, string name, key id, string message) { + + // process @commands that come in via the listener + if (iChannel == link_Channel) + { + ParseMsg(message); + return; + } + + if (MENU) { + llListenRemove(iHandle); + MENU = 0; // menu is off + iHandle = 0; + } + + if (message == "Stop NPC") + { + lNpcCommandList = []; // force reload of notecard + NPCEnabled = FALSE; + if (NPCKey() != NULL_KEY){ + Kill(sNPCName); + sNPCName = ""; + } else { + bNPC_STOP = TRUE; + makeText("Enter name of an NPC to stop"); + } + } + else if (message == "Menu" ) { + makeMainMenu(); + } + else if (message == "Erase RAM"){ + Clr(); + } + else if (message == "Relative"){ + relAbs = "Absolute"; + KeyValueSet("co","A"); // remember coordinates = A + Clr(); + } + else if (message == "Absolute"){ + relAbs = "Relative"; + KeyValueSet("co","R"); // remember coordinates = R + Clr(); + } + else if (message == "Recording"){ + DoMenuForCommands(); // show them the recording menu + } + else if (message == "Owner Only") { + priPub = "Group"; + KeyValueSet("pr","1"); + + llOwnerSay("Group members have control"); + makeMainMenu(); + } + else if (message == "Group") { + priPub = "Owner Only"; + KeyValueSet("pr","0"); + llOwnerSay("Only you have control"); + makeMainMenu(); + } + else if (message == "Sense is On") { + mSensor ="Sense is Off"; + KeyValueSet("se", "off"); + llOwnerSay(mSensor); + makeMainMenu(); + } + else if (message == "Sense is Off") { + mSensor ="Sense is On"; + llOwnerSay(mSensor); + KeyValueSet("se", "on"); + + NPCEnabled = TRUE; + + integer count = checkNoteCards(); + if (count >= 2) { + DEBUG("Notecards ok, DoProcessNPCLine"); + DoProcessNPCLine(); + return; + } + if (LSLEditor) { + DoProcessNPCLine(); + return; + } + + llOwnerSay("You have not saved a recording and/or appearance, so you cannot start a NPC"); + makeMainMenu(); + } + else if (message == "Appearance") { + llRemoveInventory(Appearance); // delete the notecard + osAgentSaveAppearance(kUserKey,Appearance); // make the ntecard + llOwnerSay("Your outfit has been saved"); + makeMainMenu(); + } + else if (message == "Save") { + if (llGetListLength(lCommands) == 0) { + llOwnerSay("Nothing recorded, you need to make a recording first"); + makeMainMenu(); + return; + } + DoSave(); + } + else if (message == "Help"){ + llLoadURL(kUserKey,"Click to view help","http://www.outworldz.com/opensim/posts/NPC/"); + makeMainMenu(); + } + else if (message == "Start NPC") { + integer count = checkNoteCards(); + + NPCEnabled = TRUE; + + if (LSLEditor) { + DoProcessNPCLine(); + return; + } + + if (count >= 2) { + DEBUG("Notecards approved , calling DoProcessNPCLine"); + SetStop(FALSE); // Let's run the notecard + DoProcessNPCLine(); + return; + } + + llOwnerSay("You have not saved a recording or maybe an appearance, so we cannot start a NPC"); + + } + else if (bNPC_STOP){ + bNPC_STOP = FALSE; + Kill(message); + } + else if (message == ">>"){ + makeMenu(lMenu2); + } + else if (message == ">>>"){ + makeMenu(lMenu3); + } + else if (message == "<<") { + makeMenu(lAtButtons); + } + else if (message == "<<<") { + makeMenu(lMenu2); + } + else if (message == "@comment"){ + Text("# ","Enter a comment",""); + } + else if (message == "@stop"){ + lCommands += "@stop"+ "\n"; + makeMenu(lAtButtons); + } + else if (message == "@run"){ + lCommands += "@run=" + Pos() + "\n"; + llOwnerSay("Recorded position: " + Pos()); + makeMenu(lAtButtons); + } + else if (message == "@fly"){ + lCommands += "@fly=" + Pos() + "\n"; + llOwnerSay("Recorded position: " + Pos()); + makeMenu(lAtButtons); + } + else if (message == "@land"){ + lCommands += "@land=" + Pos() + "\n"; + llOwnerSay("Recorded position: " + Pos()); + makeMenu(lAtButtons); + } + else if (message == "@walk") { + lCommands += "@walk=" + Pos() + "\n"; + llOwnerSay("Recorded position: " + Pos()); + makeMenu(lAtButtons); + } + else if (message == "@stop"){ + lCommands += "@stop"+ "\n"; + makeMenu(lAtButtons); + } + else if (message == "@sound"){ + Text("@sound=","Enter a sound name or UUID to trigger",""); + } + else if (message == "@randsound"){ + lCommands += "@randsound"+ "\n"; + makeMenu(lAtButtons); + } + else if (message == "@say") { + Text("@say=","Enter what the NPC will say",""); + } + else if (message == "@whisper"){ + Text("@whisper=","Enter what the NPC will whisper",""); + } + else if (message == "@shout"){ + Text("@shout=","Enter what the NPC will shout",""); + } + else if (message == "@wander") { + Text("@wander=","Enter radius to wander","Enter number of wanders"); + } + else if (message == "@pause") { + Text("@pause=","Enter time to pause",""); + } + else if (message == "@rotate") { + Text("@rotate=","Enter degrees to rotate",""); + } + else if (message == "@sit"){ + Text("@sit=","Enter name of object to sit on",""); + } + else if (message == "@teleport"){ + lCommands += "@teleport=" + Pos() + "\n"; + llOwnerSay("teleport to position: " + Pos()); + makeMenu(lMenu3); + } + else if (message == "@touch"){ + Text("@touch=","Enter name of object to touch",""); + } + else if (message == "@cmd"){ + Text("@cmd=","Enter cjhannel to speak on","Enter text to speak"); + } + else if (message == "@stand"){ + lCommands += "@stand\n"; + llOwnerSay("Stand Recorded"); + makeMenu(lAtButtons); + } + else if (message == "@animate"){ + Text("@animate=","Enter animation name to play","Enter time to play the animation"); + } + else if (message == "@attach"){ + Text("@animate=","Enter inventory name to attach","Enter number of the attachment point (1-40)"); + } + else if (message == "@speed"){ + Text("@speed=","Enter a speed for the NPC, 1=100% normal speed, 0.5=50% speed",""); + } + + + // Save NPC name + else if (MakeNotecard == STATE) { + sNPCName = message; // in case we need to kill it. + + vector vDest = (vector) Pos(); + + if (relAbs == "Relative") + { + vDest -= llGetPos(); // just an offset for relative + } + sNotecard = "@spawn=" + message + "|" + (string) vDest + "\n"; + integer i; + integer j = llGetListLength(lCommands); + for (; i < j; i++){ + // get the command to save to the notecard + string line = llList2String(lCommands,i); + if (relAbs == "Absolute") { + sNotecard += line; // add the un-modified string to the notecard + } else { + // since we have to record absolute coords since we do not know where the box goes until they press Save, + // we process the absolute to relative conversion for walks here + list parts = llParseString2List(line,["="],[]); //get the @command + + if (llList2String(parts,0) == "@walk") { + vector vec = (vector) llList2String(parts,1) - llGetPos(); + sNotecard += "@walk=" + (string) vec + "\n"; + } + else if (llList2String(parts,0) == "@fly") { + vector vec = (vector) llList2String(parts,1) - llGetPos(); + sNotecard += "@fly=" + (string) vec + "\n"; + } + else if (llList2String(parts,0) == "@run") { + vector vec = (vector) llList2String(parts,1) - llGetPos(); + sNotecard += "@run=" + (string) vec + "\n"; + } + else if (llList2String(parts,0) == "@land") { + vector vec = (vector) llList2String(parts,1) - llGetPos(); + sNotecard += "@land=" + (string) vec + "\n"; + } + else { + sNotecard += line; // add the un-modified string to the notecard + } + } + } + llRemoveInventory(Notecard); // delete the old notecard + osMakeNotecard(Notecard,sNotecard); // Makes the notecard. + llSay(0,sNotecard); + llOwnerSay("Commands notecard has been written"); + STATE = NULL; + } // MakeNotecard + + else if (! llStringLength(sParam2)) { + lCommands += sCommand + message + "\n"; + llOwnerSay("Recorded"); + makeMenu(lAtButtons); + } + else if (llStringLength(sParam2)){ + sCommand = sCommand + message + "|"; + llOwnerSay("Recorded"); + makeText(sParam2); + sParam2 = ""; + } + + } + + + + timer(){ + // DEBUG("tick"); + + // if llDialog is up, kill the listener for the dialog box. + if (iHandle) { + llOwnerSay("Menu timed out"); + llListenRemove(iHandle); + iHandle = 0; + return; // ^^^^^^^^^^^^^^^^^^^^^^^ + } + // if NoBodyHome, we are sensing for an avatar + else if (NobodyHome == STATE) { + ProcessSensor(); + return; + } + // if we are spawning, we need time to rez the NPC, then start processing NPC Commands. + else if (Spawning == STATE) { + STATE = NULL; + TimerEvent(TIMER); + } + // We end aniamtions with a timer + else if (Animate == STATE){ + NPCAnimate(STAND); + TimerEvent(TIMER); + } + + else if (Walking == STATE) { + if (--iWaitCounter) { + DEBUG("still walking..."); + if (llVecDist(osNpcGetPos(NPCKey()), newDest) > MAXDIST) { + return; + } + } + + DEBUG("At Destination: " + (string) newDest); + + // walk, fly, run, land + if (walkstate == 1) { + NPCAnimate(STAND); + NPCWalkOption = OS_NPC_NO_FLY; + } else if (walkstate == 2) { + // nothing + } else if (walkstate == 3) { + NPCAnimate(STAND); + NPCWalkOption = OS_NPC_NO_FLY; + } else if (walkstate == 4) { + llShout(FLIGHT,"landing"); + NPCAnimate(STAND); + NPCWalkOption = OS_NPC_NO_FLY; + } + } + // Wandering timer + else if (Wander == STATE) { + if (--iWaitCounter) { // wait 60 seconds to get to a destination. + if (llVecDist(osNpcGetPos(NPCKey()), vWanderPos) > MAXDIST) + return; + } + + // see if wander counter == 0, if so, stop walking, go to stand and process next line + if(RAMwc == 0) { + NPCAnimate(STAND); + DEBUG("Wander ended, calling DoProcessNPCLine"); + STATE = NULL; + return; + } + // one less time to wander around + RAMwc--; + NPCAnimate(STAND); + TimerEvent(TIMER); + StateWanderhold(); + return; + } + // Wandering requires us to re-wander when we reach a destination + else if (WanderHold == STATE) { + StateWander(); + } + else if (DoProcess == STATE) { + TimerEvent(QUICK); + } + + STATE = NULL; + + // We always process a NPC line at end of timer. + DEBUG("Tick end, calling DoProcessNPCLine"); + DoProcessNPCLine(); + } + + // sensors are used for sitting on prims + // Neo Cortex: added different states to trigger sit or touch + sensor(integer num) { + if (Sit == STATE ) { + osNpcSit(NPCKey(), llDetectedKey(0), OS_NPC_SIT_NOW); + DEBUG("Seated, calling DoProcessNPCLine"); + + STATE = 0; + } else if (Touch == STATE) { + osNpcTouch(NPCKey(), llDetectedKey(0), LINK_THIS); + DEBUG("Touched, calling DoProcessNPCLine"); + STATE = 0; + } + DoProcessNPCLine(); + } + no_sensor(){ + DEBUG ("no target prim located, calling DoProcessNPCLine"); + DoProcessNPCLine(); + STATE = NULL; + } + + + link_message(integer sender, integer num, string str, key id){ + if (num == 0) + ParseMsg(str); + if (num == 99) + llResetScript(); + } + +} + +// __ END__ + + + + diff --git a/HyperGrid Story Nine/Nine/Event Horizon Controller/Controller.lsl b/HyperGrid Story Nine/Nine/Event Horizon Controller/Controller.lsl new file mode 100644 index 00000000..e6261610 --- /dev/null +++ b/HyperGrid Story Nine/Nine/Event Horizon Controller/Controller.lsl @@ -0,0 +1,347 @@ +// :SHOW:1 +// :CATEGORY:NPC +// :NAME:HyperGrid Story Nine +// :AUTHOR:Ferd Frederix +// :KEYWORDS:NPC, controller, console +// :CREATED:2015-11-24 20:25:33 +// :EDITED:2015-11-24 19:25:33 +// :ID:1087 +// :NUM:1838 +// :REV:1.0 +// :WORLD:OpenSim +// :DESCRIPTION: +// NPC console controller +// Accepts button input from 8 buttons in a compass rose configuration +// Controls a pait of NPCS to xap 6 other NPCs. +// :CODE: + +//vector osNpcGetPos(key id) { return <128,128,20>; } + +// TUNABLES + + +integer debug = 0; +integer CommandChannel = 23; +integer BUSY; + +string errorbeep = "back-in-time"; +string footsteps = "footstepmuffled"; +string click = "coins_bag_2_3"; +string sex="Sex"; +string effect="magic-string-spell-2"; + +integer gFireworksChannel =20; +integer STATE = 0; + +float LASTDIST = 1; +integer counter = 0; +list FarAway = ["She is too far away","Move her closer to the dancers", "Move her near the dancers and press the center button.","What button did you just push?","Is she stuck?", "I hope this is working"]; + +integer counter1 = 0; +list Instructions = ["She is moving","Move her next to the dancers", "Try the center button.","Try the other direction","I think you have to get near a dancer", "Move her close to the dancers and press the center button", "Keep going", " This looks good"]; + + +float MAXDIST = 15; +float MaxBeam = 3.0; // how far the helmet beam happens + +vector centerPoint = <213.89366, 129.07689, 39.56485>; + +// first three are root, namaka, and dylan +list avatarDancePos= [ZERO_VECTOR,ZERO_VECTOR,ZERO_VECTOR,ZERO_VECTOR,ZERO_VECTOR,ZERO_VECTOR,ZERO_VECTOR,ZERO_VECTOR,ZERO_VECTOR,ZERO_VECTOR]; + +list avatars; + +// Link Numbers of the NPC controller +integer namaka =2; +integer dylan =3; +integer npc1 =4; +integer npc2 =5; +integer npc3 =6; +integer npc4 =7; +integer npc5 =8; +integer npc6 =9; // MaryAnne + +// GLOBALS +integer busy ; + +key NamakaKey; +key DylanKey ; + +// npc counters +integer FIRST = 0; +integer LAST = 6; +integer currNpcNum; + +// FUNCTIONS + +DEBUG(string msg) +{ + if (debug ==1 || debug ==3) + llOwnerSay(llGetScriptName() + ":" + msg); + if (debug ==2 || debug ==3) { + llSetText(msg, <1,0,0>,1.0); + llSleep(0.25); + } +} + +string NpcSayInstructions() +{ + if (counter1 ++ >= llGetListLength(Instructions)) + counter1 = 0; + + return llList2String(Instructions,counter1); +} + + +string NpcSayTooFar() +{ + if (counter++ >= llGetListLength(FarAway)) + counter = 0; + + return llList2String(FarAway,counter); +} + + +Command(integer Npc,string msg) +{ + //DEBUG("Command:" +(string) Npc +":" + msg); + llMessageLinked(Npc,0,msg,""); +} + +ChangeNpcToDemon(integer npc) +{ + DEBUG("Change to demon " + (string) npc); + llMessageLinked(npc,100,"BOOM",""); // tell the effect to play + llMessageLinked(LINK_SET,200,"BANG",""); // tell the spiral effect to play + Command(npc,"@notecard=!Changed"); + +} + +Win() +{ + DEBUG("Win"); + + llRegionSay(gFireworksChannel,"Go"); + STATE=1; + + DEBUG("FIREWORKS"); + llSetTimerEvent(1); +} + + +InitAllNpc() +{ + DEBUG("Init all Npc"); + currNpcNum = FIRST; + llMessageLinked(LINK_SET,99,"reset",""); + busy = FALSE; + STATE = 0; + avatars = [0,0,0,0,1,1,1,1,1,1]; // 0 is skipped, 1 = root, 2 - namaka + llShout(CommandChannel,"0006"); + BUSY = FALSE; +} + +default +{ + state_entry() + { + DEBUG("Reset"); + busy = FALSE; + llSetText("",<1,1,1>,1.0); + InitAllNpc(); + } + + timer() + { + llShout(CommandChannel,(string) (180 - STATE)); + llSetTimerEvent(1); + + if (STATE == 15 || STATE== 20 || STATE == 25) { + llRegionSay(gFireworksChannel,"Go"); + + } + + if (STATE==30) + { + Command(namaka,"@notecard=dragon"); + Command(dylan,"@notecard=dragon"); + } + if (STATE == 180) + { + InitAllNpc(); + llSetTimerEvent(0); + } + STATE++; + } + + + link_message(integer sender_number, integer number, string message, key id) + { + if (BUSY) + return; + // DEBUG("Num:" + (string) number + " str:" + message); + // --10 is the link num and pos of the dancer + // -2 is dylans key + // -1 is Namakas key + // 4 is for NPC direction commands + // 1 is for doorway + // 2 is the helmet beam channel + + if (number == -10) { + list x = llParseString2List(message,["|"],[]); + integer linknum = (integer) llList2String( x,0); + vector v = (vector) llList2String( x,1); + + avatarDancePos = llListReplaceList(avatarDancePos,[v],linknum,linknum); + //DEBUG("Npc Count = " + (string) llGetListLength(avatarDancePos)); + } + else if (number == -2){ // -2 is Dylans key fromt the modified NPC controller script + DylanKey = id; + //DEBUG("Dylan Key set to " + (string) id); + + } else if (number == -1){ // -1 is Namakas key + NamakaKey = id; + //DEBUG("Namaka Key set to " + (string) id); + + } else if (number == 4){ // 4 is for NPC direction commands + + llMessageLinked(LINK_SET,0," ",""); // stimulate back a NPC key message + + vector direction = (vector) message; + direction *= 2.5; + DEBUG((string) direction); + + llTriggerSound(click,1.0); + + //DEBUG("Key: " + (string) NamakaKey); + list stuff = llGetObjectDetails(NamakaKey, [OBJECT_POS]); + + vector namakaPos = llList2Vector(stuff,0); + //vector namakaPos = osNpcGetPos(NamakaKey); +// DEBUG("NPC at " + (string) namakaPos); + + vector newNpcPos = namakaPos; + newNpcPos.z = 0; + centerPoint.z = 0; + + //DEBUG("Roam dist:" + (string) llVecDist(newNpcPos, centerPoint)); + llTriggerSound(footsteps,1.0); + + // Move the npc some direction + + if (llVecDist(newNpcPos, centerPoint) > MAXDIST) { + DEBUG("Went too far"); + llTriggerSound(errorbeep,1.0); + + string toSay = NpcSayTooFar(); + Command(dylan,"@animate=avatar_type|3"); + Command(dylan,"@say=" + toSay); + + // string newPos2 = (string) (namakaPos + (direction *-2)); +// DEBUG("Reversing to @walk=" + newPos2); + + Command(namaka,"@teleport=<207, 126, 41>" ); + Command(dylan,"@teleport=<198.4, 130, 41.5>" ); + return; + } + + + string newPos = (string) (namakaPos + direction + <0,0,0.2>); + //DEBUG("Moving to @walk=" + newPos); + + Command(namaka,"@walk=" + newPos); + + } else if (number == 1) { // 1 is for doorway + + if (busy) { + DEBUG("Busy"); + return; + } + + InitAllNpc(); + busy++; + + } else if (number == 2){ // 2 is the helmet beam channel + + DEBUG(llDumpList2String(avatars,":")); + + llTriggerSound(effect,1.0); + llMessageLinked(LINK_SET,0," ",""); // stimulate back a NPC key message + + LASTDIST = 99; + integer i; + integer j = llGetListLength(avatarDancePos); + for (i = 0; i < j; i++) + { + if (llList2Integer(avatars,i) != 0) + { + + //DEBUG("I:" + (string) i); + // get Namakas position and compare it to all the npcs + vector npcPos = llList2Vector(avatarDancePos,i); + npcPos.z = 0; + list stuff = llGetObjectDetails(NamakaKey, [OBJECT_POS]); + vector namakaPos = llList2Vector(stuff,0); + + if (namakaPos == ZERO_VECTOR) + { + DEBUG("Oops"); + return; + } + + namakaPos.z = 0; + + //DEBUG("namakaPos:" + (string) namakaPos); + //DEBUG("npcPos:" + (string) npcPos); + + float dist = llVecDist(npcPos,namakaPos); + //DEBUG("dist:" + (string) dist); + + if (dist < LASTDIST) + { + LASTDIST = dist; + } + + if (dist < MaxBeam) + { + llSetText("Distance\n" + (string) dist,<0,1,0>,1.0); + Command(dylan,"@say=Something is happening!" ); + + ChangeNpcToDemon(i); //1 = root, 2 = namake, 3 = dylan + + avatars= llListReplaceList(avatars,[0],i,i); + integer k; + integer l = llGetListLength(avatars); + integer count; + for (k=0; k < l; k++) + { + count += llList2Integer(avatars,k); + } + DEBUG("count:" + (string) count); + llShout(CommandChannel,(string) count); + + if (count <2) { + + llTriggerSound(sex,1.0); + Win(); + BUSY++; + return; + } + llTriggerSound(effect,1.0); + return; + } + + } + } + llSetText("Distance\n" + (string) LASTDIST,<0,1,0>,1.0); + + string toSay2 = NpcSayInstructions(); + Command(dylan,"@animate=avatar_type|3"); + Command(dylan,"@say=" + toSay2); + + integer d = (integer) LASTDIST; + Command(dylan,"@say=The closest dancer is about " + d + " meters away from her"); + + } + } +} diff --git a/HyperGrid Story Nine/Nine/Event Horizon Controller/Direction button.lsl b/HyperGrid Story Nine/Nine/Event Horizon Controller/Direction button.lsl new file mode 100644 index 00000000..c49c377c --- /dev/null +++ b/HyperGrid Story Nine/Nine/Event Horizon Controller/Direction button.lsl @@ -0,0 +1,44 @@ +// :SHOW:1 +// :CATEGORY:NPC +// :NAME:HyperGrid Story Nine +// :AUTHOR:Ferd Frederix +// :KEYWORDS:NPC, controller, console +// :CREATED:2015-11-24 20:25:33 +// :EDITED:2015-11-24 19:25:33 +// :ID:1087 +// :NUM:1839 +// :REV:1.0 +// :WORLD:OpenSim +// :DESCRIPTION: +// NPC console controller button script +// Goes into 8 buttons in a compass rose configuration +// Controls a pair of NPCS to xap 6 othert NPCs. +// each button gets a different config. + +// :CODE: + +string E = "<1,0,0>"; +string NE = "<1,1,>"; +string N = "<0,1,0>"; +string NW = "<-1,1,0>"; +string W = "<-1,0,0>"; +string SW = "<-1,-1,0>"; +string S = "<0,-1,0>"; +string SE = "<1,-1,0>"; + +// chose a direction for one of 8 buttons. + +default +{ + touch_start(integer total_number) + { + llSetLinkPrimitiveParamsFast(llGetLinkNumber(),[PRIM_FULLBRIGHT, ALL_SIDES,TRUE]); + llMessageLinked(LINK_SET,4,S,""); + llSetTimerEvent(0.5); + } + timer() + { + llSetLinkPrimitiveParamsFast(llGetLinkNumber(),[PRIM_FULLBRIGHT,ALL_SIDES, FALSE]); + llSetTimerEvent(0); + } +} diff --git a/HyperGrid Story Nine/Nine/Event Horizon Controller/Doorway.lsl b/HyperGrid Story Nine/Nine/Event Horizon Controller/Doorway.lsl new file mode 100644 index 00000000..5716dec3 --- /dev/null +++ b/HyperGrid Story Nine/Nine/Event Horizon Controller/Doorway.lsl @@ -0,0 +1,43 @@ +// :SHOW:1 +// :CATEGORY:Gaming +// :NAME:HyperGrid Story Nine +// :AUTHOR:Ferd Frederix +// :KEYWORDS:Game, Collider +// :CREATED:2015-11-24 20:25:33 +// :EDITED:2015-11-24 19:25:33 +// :ID:1087 +// :NUM:1840 +// :REV:2.0 +// :WORLD:OpenSim +// :DESCRIPTION: +// Triggers the NPC controller to play a notecard when collided. +// :CODE: + +string message = "@notecard=!Path"; + +Reset() { + llSetStatus(STATUS_PHANTOM, FALSE); // rev 2.0 + llVolumeDetect(FALSE); + llSleep(0.1); + llVolumeDetect(TRUE); +} + +default{ + state_entry(){ + Reset(); + } + collision_start(integer n){ + if (osIsNpc(llDetectedKey(0))){ + return; + } + llMessageLinked(LINK_SET,1,message,""); // 1 ios for doorway only + } + on_rez(integer p){ + llResetScript(); + } + changed(integer what){ + if (what & CHANGED_REGION_START){ + llResetScript(); + } + } +} diff --git a/HyperGrid Story Nine/Nine/Event Horizon Controller/Go.lsl b/HyperGrid Story Nine/Nine/Event Horizon Controller/Go.lsl new file mode 100644 index 00000000..927b003d --- /dev/null +++ b/HyperGrid Story Nine/Nine/Event Horizon Controller/Go.lsl @@ -0,0 +1,32 @@ +// :SHOW: +// :CATEGORY:NPC +// :NAME:HyperGrid Story Nine +// :AUTHOR:Ferd Frederix +// :KEYWORDS: +// :CREATED:2015-11-24 20:25:33 +// :EDITED:2015-11-24 19:25:33 +// :ID:1087 +// :NUM:1841 +// :REV:1 +// :WORLD:Second Life +// :DESCRIPTION: +// Button Script for console +// :CODE: + +// The Go button at the center + +default +{ + touch_start(integer total_number) + { + llSetLinkPrimitiveParamsFast(llGetLinkNumber(),[PRIM_FULLBRIGHT, ALL_SIDES,TRUE]); + llMessageLinked(LINK_SET,2,"Red",""); + llSetTimerEvent(0.5); + } + timer() + { + llSetLinkPrimitiveParamsFast(llGetLinkNumber(),[PRIM_FULLBRIGHT,ALL_SIDES, FALSE]); + llSetTimerEvent(0); + } + +} diff --git a/HyperGrid Story Nine/Nine/Event Horizon Controller/Light effect Bar.lsl b/HyperGrid Story Nine/Nine/Event Horizon Controller/Light effect Bar.lsl new file mode 100644 index 00000000..d31773ce --- /dev/null +++ b/HyperGrid Story Nine/Nine/Event Horizon Controller/Light effect Bar.lsl @@ -0,0 +1,90 @@ +// :SHOW:1 +// :CATEGORY:NPC +// :NAME:HyperGrid Story Nine +// :AUTHOR:Ferd Frederix +// :KEYWORDS:NPC, controller, cyber helmet +// :CREATED:2015-11-24 20:25:33 +// :EDITED:2015-11-24 19:25:33 +// :ID:1087 +// :NUM:1842 +// :REV:1.0 +// :WORLD:OpenSim +// :DESCRIPTION: +// NPC helmet controller +// Accepts a single chatted command to msake the cyber being helmet flash for a second or two. +// worn by the NPC +// :CODE: + +// TUNABLES +integer debug = 1; + + + + +// GLOBALS +integer counter = 0; + + +// FUNCTIONS +DEBUG(string msg) +{ + if (debug & 1) + llSay(0,llGetScriptName() + ":" + msg); + if (debug & 2) + llSetText(msg, <1,0,0>,1.0); +} + +Go() +{ + llSetLinkPrimitiveParamsFast(llGetLinkNumber(),[PRIM_GLOW,ALL_SIDES,.2, PRIM_SIZE, <0.5,0.5,2>,PRIM_COLOR,ALL_SIDES,<1,1,1>,0.5 ]); + llSetTimerEvent(2); + +} + + + +default +{ + on_rez(integer p) + { + llResetScript(); + } + + changed(integer what) + { + if (what & CHANGED_REGION_START) + { + llResetScript(); + } + } + + + state_entry() + { + llSetText("", <1,0,0>,1.0); + llListenRemove(listener); + + llSetTextureAnim(ANIM_ON | SMOOTH | LOOP , ALL_SIDES, 1, 1, 1.0, 1.0, 1.0); + } + + link_message(integer total_number, integer Num, string text, key id) + { + if ( text =="BANG") + Go(); + } + + + timer() + { + llSetLinkPrimitiveParamsFast(llGetLinkNumber(),[PRIM_GLOW,ALL_SIDES,0.2, PRIM_SIZE, <0.5,0.5,2> ] ); + llSleep(0.25); + llSetLinkPrimitiveParamsFast(llGetLinkNumber(),[PRIM_GLOW,ALL_SIDES,0.2, PRIM_SIZE, <0.4,0.4,2> ] ); + + llSetLinkPrimitiveParamsFast(llGetLinkNumber(),[PRIM_GLOW,ALL_SIDES,0, PRIM_SIZE, <0.2,0.2,2> ] ); + llSleep(0.25); + llSetLinkPrimitiveParamsFast(llGetLinkNumber(),[PRIM_GLOW,ALL_SIDES,0, PRIM_SIZE, <0.1,0.1,2>,PRIM_COLOR,ALL_SIDES,<1,1,1>,0 ] ); + llSetTimerEvent(0); + + } + +} diff --git a/HyperGrid Story Nine/Nine/Event Horizon Controller/back-in-time.wav b/HyperGrid Story Nine/Nine/Event Horizon Controller/back-in-time.wav new file mode 100644 index 00000000..8e901832 Binary files /dev/null and b/HyperGrid Story Nine/Nine/Event Horizon Controller/back-in-time.wav differ diff --git a/HyperGrid Story Nine/Nine/Event Horizon Controller/coins_bag_2_3.wav b/HyperGrid Story Nine/Nine/Event Horizon Controller/coins_bag_2_3.wav new file mode 100644 index 00000000..4b6355b4 Binary files /dev/null and b/HyperGrid Story Nine/Nine/Event Horizon Controller/coins_bag_2_3.wav differ diff --git a/HyperGrid Story Nine/Nine/Event Horizon Controller/footstepmuffled.wav b/HyperGrid Story Nine/Nine/Event Horizon Controller/footstepmuffled.wav new file mode 100644 index 00000000..48b3638a Binary files /dev/null and b/HyperGrid Story Nine/Nine/Event Horizon Controller/footstepmuffled.wav differ diff --git a/HyperGrid Story Nine/Nine/Event Horizon Controller/magic-string-spell-2.wav b/HyperGrid Story Nine/Nine/Event Horizon Controller/magic-string-spell-2.wav new file mode 100644 index 00000000..971bdbc4 Binary files /dev/null and b/HyperGrid Story Nine/Nine/Event Horizon Controller/magic-string-spell-2.wav differ diff --git a/HyperGrid Story Nine/Nine/Helmet/Script.lsl b/HyperGrid Story Nine/Nine/Helmet/Script.lsl new file mode 100644 index 00000000..9e8c01e6 --- /dev/null +++ b/HyperGrid Story Nine/Nine/Helmet/Script.lsl @@ -0,0 +1,116 @@ +// :SHOW:1 +// :CATEGORY:NPC +// :NAME:HyperGrid Story Nine +// :AUTHOR:Ferd Frederix +// :KEYWORDS:NPC, controller, cyber helmet +// :CREATED:2015-11-24 20:25:33 +// :EDITED:2015-11-24 19:25:33 +// :ID:1087 +// :NUM:1843 +// :REV:1.0 +// :WORLD:OpenSim +// :DESCRIPTION: +// NPC helmet controller +// Accepts a single chatted command to msake the cyber being helmet flash for a second or two. +// rezzed in world and not worn +// :CODE: + +// TUNABLES +integer debug = 0; + + +// GLOBALS +integer counter = 0; +integer listener; + +// FUNCTIONS +DEBUG(string msg) +{ + if (debug & 1) + llSay(0,llGetScriptName() + ":" + msg); + if (debug & 2) + llSetText(msg, <1,0,0>,1.0); +} +NpcSaySomething(){ + + list sayings = ["The helmet begins to glow","A beam of light appears","The helmet begins to hum","The cyberbeing helmet is activated", "bzzz", "Zap","KaPow!"]; + key NpcKey = llGetOwner(); + DEBUG("Owner Key = " + (string) NpcKey); + + llSay(0, llList2String(sayings,counter++)); + if (counter > llGetListLength(sayings)) { + counter = 0; + } +} + +Go() +{ + NpcSaySomething(); + llSetLinkPrimitiveParamsFast(0,[PRIM_GLOW,ALL_SIDES,.2, PRIM_SIZE, <2,2,2>,PRIM_COLOR,ALL_SIDES,<1,1,1>,0.5 ]); + llSetTimerEvent(2); + +} + +integer Helmet_Channel = 576; + +default +{ + on_rez(integer p) + { + llResetScript(); + } + + changed(integer what) + { + if (what & CHANGED_REGION_START) + { + llResetScript(); + } + } + + + state_entry() + { + llSetText("", <1,0,0>,1.0); + llListenRemove(listener); + listener= llListen(Helmet_Channel, "","","BOOM"); // listen for boom command + + llSetTextureAnim(ANIM_ON | SMOOTH | LOOP , ALL_SIDES, 1, 1, 1.0, 1.0, 1.0); + + llSensorRepeat("Namaka ♥", NULL_KEY, AGENT, 25.0, PI,5); + } + + listen(integer channel, string name, key id, string msg) + { + Go(); + } + + touch_start(integer total_number) + { + Go(); + } + + sensor(integer n) + { + llSetRegionPos(llDetectedPos(0) + <0,0,1>); + } + + no_sensor() + { + llSetRegionPos(<200, 128, 48>); // sop we can find this blasted thing + } + + timer() + { + llSetLinkPrimitiveParamsFast(0,[PRIM_GLOW,ALL_SIDES,0.2, PRIM_SIZE, <1.5,1.5,1> ] ); + llSleep(0.25); + llSetLinkPrimitiveParamsFast(0,[PRIM_GLOW,ALL_SIDES,0.2, PRIM_SIZE, <1.2,1.2,1> ] ); + + llSetLinkPrimitiveParamsFast(0,[PRIM_GLOW,ALL_SIDES,0, PRIM_SIZE, <1,1,1> ] ); + llSleep(0.25); + llSetLinkPrimitiveParamsFast(0,[PRIM_GLOW,ALL_SIDES,0, PRIM_SIZE, <1,1,1>,PRIM_COLOR,ALL_SIDES,<1,1,1>,0 ] ); + llSetTimerEvent(0); + + } + +} diff --git a/HyperGrid Story Nine/Nine/Namaka/NPC Custom for Namaka.lsl b/HyperGrid Story Nine/Nine/Namaka/NPC Custom for Namaka.lsl new file mode 100644 index 00000000..ea02cc42 --- /dev/null +++ b/HyperGrid Story Nine/Nine/Namaka/NPC Custom for Namaka.lsl @@ -0,0 +1,1705 @@ +// :SHOW:1 +// :CATEGORY:NPC +// :NAME:HyperGrid Story Nine +// :AUTHOR:Ferd Frederix +// :KEYWORDS:NPC, Puppeteer +// :CREATED:2015-11-24 20:25:34 +// :EDITED:2015-11-24 19:25:34 +// :ID:1087 +// :NUM:1844 +// :REV:9.3 +// :WORLD:OpenSim +// :DESCRIPTION: +// All in one NPC recorder player. +// Supports both absolute and relative paths and many new commands +// Add animations named "Fly, Walk, Stand and Run" +// Click Prim to use. +// Should be worn as a HUD to record. +// Put it on the ground and click Sensor or Start NPC when done. +// :CODE: + +// This is Rev 9.3 based on 4.5 09/26/2015 + +// Revision History +// Rev 1.1 10-2-2014 @Sit did not work. Minor tweaks to casting for lslEditor +// Rev 1.2 10-14-2014 @ sit had wrong type. +// Rev 1.3 relative movement fixed for @fly +// Rev 1.4 4-3-2014 allow anyone to use this, non owners and non group members can only start and stop. +// Rev 1.5 5-17-2014 set sensor to auto start on reboot of sim +// Rev 1.6 5-24-2014 move menu so you can get it by touching, removed many of the KeyValues to RAM for efficiency +// Rev 1.7 CHANGED_REGION_START, not CHANGED_REGION_START (Opensim difference) +// Rev 1.8 tuned up Kill NPC, added more flexible upgrader +// Rev 1.9 Better script injection by link message// Rev 2.0 Added osSetSpeed so you can speed up or slow down an NPC. +// Rev 2.1 No laggy sensor used exept to sit on stuff +// Rev 2.2 Various sensor fixes +// Rev 2.3 Sets No Sensor in menu, must be started by hand +// Rev 2.4 - reserved for patches to 2.3 if needed +// Rev 3.0 Refactor out into subs, not states to make command injection easier +// New command: @appearance=Notecardname so you can switch to a new notecard on the fly +// New command: @speed=1.0 which slows up ( < 1 ) or speeds up ( > 1) +// Rev 3.1 Commands are not interruptible by Link Message +// Rev 3.2 Sensor patches for consistency in removing the NPC +// Rev 3.3 Added Touch command by Neo.Cortex@hbase42/hopto/org:8002 +// Added Menu 3 for notecard and appearance commands +// Rev 3.4 animation timer cannot be zero or it shuts off timer tweaked +// solves the NPC starting up when no sensor is set. +// Rev 3.5 fixes saving to !Path notecard +// Rev 3.6 08-11-2015 @delete acts like @stop. TYjhe NPC now rezzes after an @go back in where it was deleted +// Rev 3.7 08-11-2015 @attach command added to load an attachment from the inventory to the NPC +// Rev 3.8 08-17-2015 process queued commands one at a time without calling ProcessNPCLine on link message +// Rev 3.9 08-23-2011 Queued command fixes including @delete which were not always working +// Rev 4.0 09-15-2015 Fixes for Sensor functions which continually rezzed a NPC when no one was around. +// Rev 4.1 09-20-2015 Added a Listener so link messages are not needed +// Rev 4.2 09-23-2015 Added @teleport= +// Rev 4.3 09-24-2015 Added @reset to restart the NPC at the very start of the !Path notecard +// @teleport works for relative and absolute modes +// Rev 4.4 09-26-2015 if it could not find the (deleted) NPC, it could not restart +// Rev 4.5 09-29-2015 remove wait for STATE == 0 +//*******************************************************************// + +// Instructions on how to use this are at http://www.outworldz.com/opensim/posts/NPC/ +// This is an OpenSim-only script. +// Author: Ferd Frederix aka Fred Beckhusen - fred@mitsi.com + +//////////////////////////////////////////////////////////////////////////////////////////// +// Original code was Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // +/////////////////////////////////////////////////////////////////////////////////////////// +// Please see: http://www.gnu.org/licenses/gpl.html for legal details, // +// rights of fair usage, the disclaimer and warranty conditions. // +/////////////////////////////////////////////////////////////////////////////////////////// +// The original NPC controller was from http://was.fm/opensim:npc +// Extensive additions and bug fixes by Fred Beckhusen, aka Ferd Frederix +// llSensor had two params swapped +// @Wander would wander where it had rezzed, not where it was. +// There was no 'no_sensor' event in sit, so if a @sit failed, the NPC got stuck +// The animation and walks always stopped old, then started new. It should be start new, then stop old so the default stand would be suppressed. +// New code: +// Merged with new Route recorder and notecard writer +// If the NPC failed to reach a destination it never moved on. +// Added WAIT global to tune this +// Exposed many tunable variables and ported the code +// Added floating point to times in notecard. +// Added @sound, @randsound, @whisper, @shout, and @cmd controls. +// notecards integers are not floats for better control +// +// Link Messages may be used to perform external control by injecting @commands into the stream of actions +// Example: +// To chat something, such as with a chat robot +// llMessageLinked(LINK_SET,0,"@npc_say=Hello",""); + +// This script assumes that NPCs and OSSl scripting is enabled in the OpenSim configuration. +// In order to enable them, the following changes must be made in the OpenSim.ini configuration file: +// +// ; Turn on OSSL +// AllowOSFunctions = true +// OSFunctionThreatLevel = Severe + +//[NPC] +// ;# {Enabled} {} {Enable Non Player Character (NPC) facilities} {true false} +// Enabled = true +// +// and then the server has to be restarted. +// please note that there are better ways to enable NPC in the latest Opensim. + +// Commands: All commands begin with an @ sign. All other lines are ignored +// @commands may have optional parameters. The syntax is always: +// @cmd=parm1|parm2 +// NaN in the table below meand Not a Number. This means there is no parameter + +//Command First Parameter Second Parameter Description +//@spawn name location (vector) Rezzes an NPC with name at a location. +//@appearance NoteCardName NaN switch the NPC appearance to a new notecard +//@walk destination (vector) NaN Makes the NPC walk to destination. +//@fly destination (vector) NaN Makes the NPC fly to destination. +//@land destination (vector) NaN Makes the NPC land at destination. +//@say string NaN Makes the NPC speak a phrase. +//@whisper string NaN Makes the NPC whisper a phrase. +//@shout string NaN Makes the NPC shout a phrase. +//@pause seconds (float) NaN Makes the NPC wait for a multiple of seconds. +//@wander radius (float) cycles (integer) Makes the NPC wander in radius, for cycles seconds. +//@delete NaN NaN Removes the NPC. Requires a link message to continue +//@goto label (string) NaN Jump to the label label in the script. +//@animate animation (string) time (float) Makes the NPC trigger the animation animation for time seconds. +//@sound sound_name NaN plays a sound from inventory +//@randsound NaN NaN Plays a random sound from inventory +//@rotate degrees (float) NaN Rotate the NPC degrees around the Z axis. +//@sit primitive name NaN Sit on a primitive with a given name. +//@touch primitive name NaN Touch on a primitive with a given name. +//@stand NaN NaN If sitting on a primitive, stand up. +//@cmd channel (integer) string Says string on channel, for controlling external gadgets +//@stop NaN NaN Halts the NPC script indefinitely. Can be started with a link message +//@go NaN NaN Continues on next notecard line, for use in link messages +//@speed speed (float) NaN from 0 to N, where 1.0 ius a normal speed of an avatar. 0.2 is a turtle. +//@notecard notename (string) NaN load a new Path notecard +//@attach InventoryName attachmentPoint load an attachment from the inventory to the NPC onto point +//@teleport destination (vector) NaN Makes the NPC teleport to destination in the same sim. They cannot tp to another sim or across the HG +//@reset NaN NaN Deletes the NPC, starts the !Path notecard over. + +// Constant attachmentPoint Comment +// ATTACH_CHEST 1 chest/sternum +// ATTACH_HEAD 2 head +// ATTACH_LSHOULDER 3 left shoulder +// ATTACH_RSHOULDER 4 right shoulder +// ATTACH_LHAND 5 left hand +// ATTACH_RHAND 6 right hand +// ATTACH_LFOOT 7 left foot +// ATTACH_RFOOT 8 right foot +// ATTACH_BACK 9 back +// ATTACH_PELVIS 10 pelvis +// ATTACH_MOUTH 11 mouth +// ATTACH_CHIN 12 chin +// ATTACH_LEAR 13 left ear +// ATTACH_REAR 14 right ear +// ATTACH_LEYE 15 left eye +// ATTACH_REYE 16 right eye +// ATTACH_NOSE 17 nose +// ATTACH_RUARM 18 right upper arm +// ATTACH_RLARM 19 right lower arm +// ATTACH_LUARM 20 left upper arm +// ATTACH_LLARM 21 left lower arm +// ATTACH_RHIP 22 right hip +// ATTACH_RULEG 23 right upper leg +// ATTACH_RLLEG 24 right lower leg +// ATTACH_LHIP 25 left hip +// ATTACH_LULEG 26 left upper leg +// ATTACH_LLLEG 27 left lower leg +// ATTACH_BELLY 28 belly/stomach/tummy +// ATTACH_LEFT_PEC 29 left pectoral +// ATTACH_RIGHT_PEC 30 right pectoral +// ATTACH_HUD_CENTER_2 31 HUD Center 2 +// ATTACH_HUD_TOP_RIGHT 32 HUD Top Right +// ATTACH_HUD_TOP_CENTER 33 HUD Top +// ATTACH_HUD_TOP_LEFT 34 HUD Top Left +// ATTACH_HUD_CENTER_1 35 HUD Center +// ATTACH_HUD_BOTTOM_LEFT 36 HUD Bottom Left +// ATTACH_HUD_BOTTOM 37 HUD Bottom +// ATTACH_HUD_BOTTOM_RIGHT 38 HUD Bottom Right +// ATTACH_NECK 39 neck +// ATTACH_AVATAR_CENTER 40 avatar center/root + + + +////////////////////////////////////////////////////////// +// DEBUG // +////////////////////////////////////////////////////////// +integer debug = FALSE; // set to TRUE or FALSE for debug chat on various actions +integer LSLEditor = FALSE; // set to to TRUE to working in LSLEditor, FALSE for in-world. + // you must also include the NPC commands found in the other script since LSLEditor does not support OpenSim +integer iTitleText = FALSE; // set to TRUE to see debug info in text above the controller + +////////////////////////////////////////////////////////// +// TUNABLE CONFIGURATION // +////////////////////////////////////////////////////////// +integer keyNum = -1; // (dylan) special number for link message to broadcast the NPC key +integer allowListener = TRUE; // set to TRUE to anable a command listener. Usually, this is setto FALSE +integer link_Channel = 4223; // some random number you want to talk to this gadget on. Best if large and negative +float TIMER = 2; // faster = less jerky stopping. How often the system checks the distance traveled. Fastest you can go is 0.5 seconds +float QUICK = 1; // when we need to move to the next state, we use a QUICK timer +string Appearance = "!Appearance"; // The name of the recorded Appearance notecard +string Notecard = "!Path"; // The name of the recorded routes +integer allowUsers = FALSE; // If true, any user can get a Start NPC and Stop NPC menu. Only groups and owners can get all commands if TRUE, or FALSE +float MAXDIST = 2.0; // how close a NPC has to get to a dest pos to continue to next state. Do not lower this too much, as it may miss the target +integer WANDERRAND = TRUE; // set to TRUE and they will pause during wanders a random number of seconds +float WANDERTIME = 3.0; // how long they stand after each @wander,if WANDERRAND is FALSE. If WANDERRAND is TRUE, this is the max time +integer WAIT = 30; // wait for this number of seconds for the NPC to reach a destination (for safety). If it fails to reach a target, it will move on after this time. +float RANGE = 150; // 1 to N meters - anyone this close to the controller will start NPCS if Sensor button is clicked +float REZTIME = 2.0; // wait this long for NPC to rez in, then start the process +string STAND = "Stand"; // the name of the default Stand animation +string WALK = "Walk"; // the name of the default Walk animation +string FLY = "Fly"; // the name of the default Fly animation +string RUN = "Run"; // the name of the default Run animation +string LAND = "Land"; // the name of the default land animation ( for birds only) +float OffsetZ = 0.5; // appear 0.5 meter above ground, this is added to all destinations to keep them from sinking in. +float SPEEDMULT =0.8; // 1.0 = regular avatar speed. Smaller numbers slow down walks. Large numbers speed them up. +integer FLIGHT = 299; // For controlling wings. A channel that is shouted at when flight starts and ends. "flying" or "landing" + +// DESCRIPTIONS FIELDS HAVE TO SURVIVE A RESET +// These vars are stored by saving them with KeyValueSet +// "pr" is a 0 if it is set for Owner Only, 1 for Group control +// "se" is "on" if Started +// "co" = "R" or "A" for relative or absolute addressing mode +// "key" = NPC key + +// These Globals used to be stored in description. Moved to RAM in V1.6 +float RAMPause; // @pause param +float RAMwd ; // @wander distance +integer RAMwc; // @wander count +float RAMrot; // @rotate +string RAMsit; // @sit primname +string RAMtouch; // @touch primname +string RAManimationName; // @animate animation (string) time (float) +float RAManimationTime; + +// other globals section +integer iChannel; // a listen channel, randomly assigned +integer iHandle; // the handle to it + +// NPC controls +vector newDest ; // tmp storage for the walks +integer iWaitCounter ; // wait for this number of seconds for the NPC to reach a desrtination +string sNPCName; // the name of the NPC that may be in world. So we can remove it. +integer bNPC_STOP = FALSE; // boolean to reuse a listener +integer Stopped = FALSE; // set to TRUE by link messages so we do not remember them +float fTimerVal ; // how long we wait when wandering (calculated) +float NPCEnabled; // true if the NPC is suppodes to be running + +// OS_NPC_CREATOR_OWNED will create an 'owned' NPC that will only respond to osNpc* commands issued from scripts that have the same owner as the one that created the NPC. +// OS_NPC_NOT_OWNED will create an 'unowned' NPC that will respond to any script that has OSSL permissions to call osNpc* commands. +integer NPCOptions = OS_NPC_CREATOR_OWNED; // only yhe owner of this box can control this NPC. + +integer walkstate = 0; // helps us reshare the walk state for run, fly and land - a bit of a hack, but it saves RAM. Has to be done this way because some bits of NPCWalkOption are asserted as 0 + +integer NPCWalkOption; // Some notes for what happens to NPCWalkOption: +// OS_NPC_FLY - Fly the avatar to the given position. The avatar will not land unless the OS_NPC_LAND_AT_TARGET option is also given. +// OS_NPC_NO_FLY - Do not fly to the target. The NPC will attempt to walk to the location. If it's up in the air then the avatar will keep bouncing hopeless until another move target is given or the move is stopped +//OS_NPC_LAND_AT_TARGET - If given and the avatar is flying, then it will land when it reaches the target. If OS_NPC_NO_FLY is given then this option has no effect. +// OS_NPC_RUNNING - if given, NPC avatar moves at running/fast flying speed, otherwise moves at walking/slow flying speed. + +// menus +string mSensor="Sense is Off"; // Sensor or "No Sensor" + +list lAtButtons = ["Menu","-", ">>", "@run", "@walk", "@fly", "@land", "@wander", "@sit", "@stand","@animate","@rotate"]; +list lMenu2 = ["<<", "@comment", ">>>", "@stop", "@say", "@whisper","@shout","@sound","@randsound","@cmd", "@pause", "@delete"]; +list lMenu3 = ["<<<","@notecard","@appearance", "@touch", "@speed", "@attach", "@teleport","-", "-", "-", "-", "-" ]; + +string sCommand; // place to store a command for two-prompted ones +string sParam2; // place to store a prompt for two-prompted ones +string priPub = "Owner Only"; // Private or Group +key kUserKey; // the person who is controlling the avatar, not the Owner +// the command lists +list lCommands; // commands are stored here +list lNpcCommandList; // Storage for the NPC script. +string npcAction; // Storage for the next action. @cmd=0|hello, this becomes @cmd +string npcParams; // Storage for the param, @cmd=0|hello, this becomes 0|hello + +// misc vars +string sNotecard; // commands are stored here temporarily for dumping +vector vWanderPos; // a place to wander to +string lastANIM ; // last animation run +// Sensor +integer avatarPresent; // Sensor sets this flag when people are within Range. + +// Coordinate control +vector vInitialPos ; // Vector that will be filled by the script with the initial starting position in region coordinates. +vector vDestPos = ZERO_VECTOR; // Storage for destination position. +string relAbs = "Relative"; // absolute vs relative positioning + + +// STATES +integer MENU ; // processing a dialog box state, may be concurrent with STATE +integer STATE; // state storage +integer NULL = 0; // the null state +integer MakeNotecard = 1; // displaying a text box for NPC name +integer RecordPath = 2; // displaying a path notecard menu +integer NobodyHome = 3; // looking for an avatar +integer Spawning = 4; // spawning an avatar +integer Animate = 5; // animation timer needed +integer Walking = 6; // Hey! I am walking here! +integer Wander = 7; // Wandering around neeeds a timer, too +integer WanderHold = 8; // We reached a wander point +integer DoProcess = 9; // Set this to make it process a new command +integer Touch = 10; // Timer is busy sensing something to touch +integer Sit = 11; // Timer is busy sensing something to sit on +integer Paused = 12; // Timer is busy pausing + +key gNpcKey = NULL_KEY; // global key storage for the one NPC, to save CPU cycles +list Stack ; // a command stack from link message input + +/////////////////////////////////////////////////////////////////////////// +// FUNCTIONS // +/////////////////////////////////////////////////////////////////////////// + + +TimerEvent(float timesent) +{ + if (LSLEditor) + timesent *= 5; // slow thinggs doen when the LSLEDITOR is in use + + DEBUG("Setting timer: " + (string) timesent); + llSetTimerEvent(timesent); +} + +// for 4.1 parse a message from a Listen or a Link message +ParseMsg(string str) { + DEBUG("Command In:" + str); + if (str=="@go") { + SetStop(FALSE); // Let's run the notecard + DEBUG("@go running"); + + DoProcessNPCLine(); + } else { + Stack += [str]; // take anything, the controller will filter away non @ stuff + DEBUG(llDumpList2String(Stack,",")); + if (STATE == NULL) { + DoProcessNPCLine(); // v 4.5 remove wait for STATE == 0 + } + } +} + +SetStop(integer what) +{ + DEBUG("Stopped set to " + (string ) what); + Stopped = what; +} +// Do* functions are much like states from the old V2 scripts. + +// Save a Path notecard +DoSave() +{ + STATE = MakeNotecard; + makeText("Stand where you want the NPC to appear, and enter the NPC Name"); +} + +// This function is used to record the path for the NPC +// Each command can take 0, 1, or 2 params +DoMenuForCommands() { + makeMenu(lAtButtons); +} + + +// No one is here when sensors were on, so we kill off the NPC +DoNobodyHome() +{ + DEBUG("Nobody Home"); + STATE = NobodyHome; + if (NPCKey() != NULL_KEY) { + osNpcRemove(NPCKey()); + SaveKey(NULL_KEY); + } + TimerEvent(5); // keep ticking to sense avatars +} + + +/////////////////////// STATELIKE BEHAVIOUR ///////////// +// these StateXX functions need to wait on a timer to fire. + +// Create a NPC +StateSpawn() { + DEBUG("state spawn " + sNPCName); + + NPCEnabled = TRUE; // in world + // see if there is already one out there. + if (NPCKey() != NULL_KEY) { + DEBUG("Already living"); + return; + } + + STATE = Spawning; + list name = llParseString2List(sNPCName, [" "], []); + + vector vRezPos = vInitialPos; + if (relAbs == "Relative"){ + vRezPos += llGetPos(); + } + + // llSay(0,llDumpList2String(name,",")); + + DEBUG("Rezzing NPC name " +llList2String(name, 0)+ llList2String(name, 1) + " at "+ (string) vRezPos); + key aKey = osNpcCreate(llList2String(name, 0), llList2String(name, 1), vRezPos, Appearance, NPCOptions); + + llMessageLinked(LINK_SET,keyNum,"",aKey); // bboradcast the key on num = -1 + SaveKey(aKey); // save in description and global, too + + osSetSpeed(aKey,SPEEDMULT); // 1.9 speed multiplier + TimerEvent(REZTIME); + NPCAnimate(STAND); +} + +StateSit() { + DEBUG ("state sit - looking for " + RAMsit); + STATE=Sit; + llSensor(RAMsit, "", PASSIVE|ACTIVE|SCRIPTED, 96, PI); +} + +StateTouch() { + DEBUG ("state touch - looking for " + RAMtouch); + STATE = Touch; + llSensor(RAMtouch, "", PASSIVE|ACTIVE|SCRIPTED, 96, PI); +} + +DoStand() { + DEBUG("state stand"); + osNpcStand(NPCKey()); +} + + +StateAnimate() { + + DEBUG("state animate"); + STATE = Animate; + NPCAnimate(RAManimationName); + if (RAManimationTime <=0 ) // V 3.4 tweak + RAManimationTime = 1; + TimerEvent(RAManimationTime); +} + +StateWalk() { + + DEBUG("Start Walk"); + //DEBUG("NPCWalkOption = " + (string) NPCWalkOption); + STATE = Walking; + + // walk, fly, run, land + if (walkstate == 1) { + NPCAnimate(WALK); + } else if (walkstate == 2) { + llShout(FLIGHT,"flying"); + NPCAnimate(FLY); + } else if (walkstate == 3) { + NPCAnimate(RUN); + } else if (walkstate == 4) { + NPCAnimate(LAND); + } + newDest = vDestPos ; + newDest.z += OffsetZ; + + // notecard is stored as offsets from this box with relative addressing. Convert to absolute + if (relAbs == "Relative"){ + newDest += llGetPos(); + } + + DEBUG("Moveto:" + (string) newDest); + osNpcMoveToTarget(NPCKey(), newDest, NPCWalkOption); + iWaitCounter = WAIT; // wait 60 seconds to get to a destination. + TimerEvent(TIMER); +} + + +StateWander(){ + DEBUG("state wander"); + STATE = Wander; + + vector point = CirclePoint(RAMwd); + DEBUG("CirclePoint:" + (string) point); + vWanderPos = vDestPos + point; + DEBUG("vWanderPos:" + (string) vWanderPos); + + fTimerVal = WANDERTIME; // default time to pause after each wander + if (WANDERRAND) + fTimerVal = llFrand(WANDERTIME) + 1; // override, they want random times + + NPCAnimate(WALK); + + DEBUG("Wander to:" + (string) vWanderPos); + + osNpcMoveToTarget(NPCKey(), vWanderPos, NPCWalkOption); + iWaitCounter = WAIT; // wait 60 seconds to get to a destination. + TimerEvent(TIMER); +} + +StateWanderhold() { + + DEBUG("Wander Hold"); + STATE = WanderHold; + + // now that we have reached a wander spot, slow the timer down to the desired value + TimerEvent(fTimerVal); +} + + + +DoRotate() { + DEBUG("@rotate=" + (string) RAMrot); + osNpcSetRot(NPCKey(), llEuler2Rot(<0,0,RAMrot> * DEG_TO_RAD)); +} + + + +// @pause=10 will do nothing for 10 seconds +DoPause() { + STATE = Paused; + if (RAMPause < 0.1) + RAMPause = 0.1; + DEBUG("@pause=" + (string)RAMPause); + TimerEvent(RAMPause); +} + + +// @stop makes the NPC stop moving in whatever state it is in. You have to linkmessage to get moving again +DoStop() { + DEBUG("NPC is Stopped"); + STATE = 0; // accept commands + SetStop(TRUE); // Link controlled - we mnust have a @go to continue with notecards + TimerEvent(0); + Stack = []; // v3.8 +} + +// @delete removes the NPC forever. Next command starts it up again at the beginning +DoDelete() { + DEBUG("state delete"); + osNpcRemove(NPCKey()); + SaveKey(NULL_KEY); + TimerEvent(0); + Stack = []; // v3.8 + STATE = NULL; // accept commands +} + +// change the appearance of the NPC +DoAppearance(string notecard) { + DEBUG("state appearance"); + if (llGetInventoryType(notecard) == INVENTORY_NOTECARD){ + DEBUG("Load appearance " + notecard); + osNpcLoadAppearance(NPCKey(),notecard); + } + STATE = NULL; // accept commands +} + +// Change the avatar speed +DoSpeed(string speed) { + float newspeed = (float) speed; + if (newspeed > 0.1 && newspeed < 5.0) {// sanity check + osSetSpeed(NPCKey(),newspeed); + } + STATE = NULL; // accept commands +} + +DoTeleport(string params) { + vector Dest = (vector) params; + + DEBUG("teleport to " + (string) Dest); + + if (Dest != ZERO_VECTOR) { + if (relAbs == "Relative"){ + Dest += llGetPos(); + } + DEBUG("Off to " + (string) Dest); + osTeleportAgent( NPCKey(), llGetRegionName(), Dest, ZERO_VECTOR ); + + } else { + llSay(DEBUG_CHANNEL,"Attempt to teleport to <0,0,0> probably not what you intended: @teleport="); + } + STATE = NULL; // accept commands +} + + + +DoNewNote (string card) { + DEBUG("Load Notecard " + card); + NPCReadNoteCard(card); + SetStop(FALSE); + STATE = NULL; // accept commands +} +DoAttach(string params) { + + list Data = llParseString2List(params, ["|"], []); + string itemName = llList2String(Data, 0); + integer attachmentPoint = (integer) llList2String(Data, 1); + if (attachmentPoint > 0 + && attachmentPoint < 40 + && llGetInventoryType(itemName) == INVENTORY_OBJECT + ) + { + osForceAttachToOtherAvatarFromInventory(NPCKey(),itemName,attachmentPoint); + } + STATE = NULL; // accept commands +} + +// This loops over the notecard, processing each command +DoProcessNPCLine() { + DEBUG("ProcessNPCLine, stopped = " + (string) Stopped); + + STATE = DoProcess; + + // auto load a notecard + if (! llGetListLength(lNpcCommandList)) { + DEBUG("Read Notecard"); + NPCReadNoteCard(Notecard); + } + + // look for link messages on the stack + + DEBUG(llDumpList2String(Stack,",")); + string next = llList2String(Stack,0); // lets see if there is anything from a link message + + DEBUG("Next:" + next); + + if (llStringLength(next)) + { + Stack = llDeleteSubList(Stack,0,0); + ProcessCmd(next); //lets do this command instead. + return; + } + + // @stop issued? + if (Stopped) { + TimerEvent(0); + DEBUG("Stopped, waiting for input"); + STATE = NULL; + return; + } + + // No, we have an @go for liftoff + next = llList2String(lNpcCommandList, 0); // get the next command + DEBUG("Execute:" + next); + lNpcCommandList = llDeleteSubList(lNpcCommandList, 0, 0); // delete it + + if (llGetListLength(lNpcCommandList) == 0) { + DEBUG("EOF"); + } + ProcessCmd(next); +} + + + +ProcessCmd(string cmd) { + + DEBUG("ProcessCmd:" + cmd); + llMessageLinked(LINK_SET,keyNum,"",NPCKey()); // bboradcast the key on num = -1 + if (llGetSubString(cmd, 0, 0) != "@") { + DEBUG("ignoring"); + TimerEvent(QUICK); // this is so we do not recurse the stack + STATE = NULL; + return; + } + + list data = llParseString2List(cmd, ["="], []); + npcAction = llToLower(llStringTrim(llList2String(data, 0), STRING_TRIM)); + + DEBUG("Action:" + npcAction); + npcParams = llStringTrim(llList2String(data, 1), STRING_TRIM); + DEBUG("Params:" + npcParams); + + @commands; + + ProcessSensor(); + + if(npcAction == "@spawn") { + DEBUG("@spawn npcParams "); + list spawnData = llParseString2List(npcParams, ["|"], []); + sNPCName =llList2String(spawnData, 0); // V 1.6 name in RAM + + vInitialPos = (vector) llList2String(spawnData, 1); + DEBUG("Coords for NPC at " + (string) vInitialPos); + StateSpawn(); + return; + } + + if (! avatarPresent){ + DoNobodyHome(); + DEBUG("No avatar nearby"); + STATE = NULL; + return; + } else { + if ( NPCKey() == NULL_KEY) { + StateSpawn(); + } + } + + + + + if(npcAction == "@stop") { + DoStop(); + STATE = NULL; + return; + } + else if(npcAction == "@goto") { + DEBUG("goto"); + integer lastIdx = llGetListLength(lNpcCommandList)-1; + lNpcCommandList = llDeleteSubList(lNpcCommandList, lastIdx, lastIdx); + // Wind commands till goto label. + @wind; + string next1 = llList2String(lNpcCommandList, 0); + lNpcCommandList = llDeleteSubList(lNpcCommandList, 0, 0); + lNpcCommandList += next1; + if(next1 != npcParams) jump wind; + // Wind the label too. + next1 = llList2String(lNpcCommandList, 0); + lNpcCommandList = llDeleteSubList(lNpcCommandList, 0, 0); + lNpcCommandList += next1; + // Get next command. + list data1 = llParseString2List(next1, ["="], []); + npcAction = llToLower(llStringTrim(llList2String(data1, 0), STRING_TRIM)); + npcParams = llStringTrim(llList2String(data1, 1), STRING_TRIM); + // Reschedule. + jump commands; + } + else if(npcAction == "@sound") { + DEBUG("sound"); + llTriggerSound(npcParams,1.0); + } + else if(npcAction == "@randsound") { + DEBUG("@randsound"); + integer N = llGetInventoryNumber(INVENTORY_SOUND); + integer rand = llCeil(llFrand(N)) -1; // pick a random sound + string toPlay = llGetInventoryName(INVENTORY_SOUND,rand); + llTriggerSound(toPlay,1.0); + } + else if(npcAction == "@walk") { + DEBUG("@walk"); + GetDest(npcParams); + walkstate = 1;// walking + NPCWalkOption = OS_NPC_NO_FLY ; + StateWalk(); + return; + } + else if(npcAction == "@fly") { + GetDest(npcParams); + walkstate = 2;// flying + NPCWalkOption = OS_NPC_FLY ; + StateWalk(); + return; + } + else if(npcAction == "@run") { + DEBUG("@run"); + GetDest(npcParams); + walkstate = 3;// running + NPCWalkOption = OS_NPC_NO_FLY | OS_NPC_RUNNING; + StateWalk(); + return; + } + else if(npcAction == "@land") { + DEBUG("@land"); + GetDest(npcParams); + walkstate = 4;// landing + NPCWalkOption= OS_NPC_FLY | OS_NPC_LAND_AT_TARGET ; + StateWalk(); + return; + } + else if(npcAction == "@say") { + DEBUG("@say " + npcParams); + osNpcSay(NPCKey(), 0, npcParams); + } + else if(npcAction == "@shout") { + DEBUG("@shout"); + osNpcShout(NPCKey(),0, npcParams); + } + else if(npcAction == "@whisper") { + DEBUG("@whisper " + npcParams); + osNpcWhisper(NPCKey(),0, npcParams); + } + // speak a command on a channel, so you can open doors and control stuff. + else if(npcAction == "@cmd") { + DEBUG("@cmd"); + list dataToSpeak = llParseString2List(npcParams, ["|"], []); + string channel = llList2String(dataToSpeak,0); + DEBUG("Channel:"+(string) channel); + integer iChannel = (integer) channel; + string stringToSpeak = llList2String(dataToSpeak,1); + llSay(iChannel, stringToSpeak); + } + // stop everything + else if(npcAction == "@pause") { + RAMPause = (float) npcParams; + DoPause(); + return; + } + else if(npcAction == "@wander") { + list wanderData = llParseString2List(npcParams, ["|"], []); + RAMwd = (float) llList2String(wanderData, 0); + RAMwc = (integer) llList2String(wanderData, 1); + vDestPos = osNpcGetPos(NPCKey()); // set the wander start + DEBUG("Starting at " + (string) vDestPos); + StateWander(); + return; + } + else if(npcAction == "@rotate") { + RAMrot = (float) npcParams; + DoRotate(); + } + else if(npcAction == "@sit") { + RAMsit= npcParams; + StateSit(); + return; + } + else if(npcAction == "@touch") { + RAMtouch= npcParams; + StateTouch(); + return; + } + else if(npcAction == "@stand") { + DoStand(); + } + else if(npcAction == "@delete") { + DoDelete(); + SetStop(TRUE); // Link controlled - we mnust have a @go to continue with notecards + return; + } + else if(npcAction == "@animate") { + list animateData = llParseString2List(npcParams, ["|"], []); + RAManimationName = llList2String(animateData, 0); + RAManimationTime = (float) llList2String(animateData, 1); + StateAnimate(); + return; + } + else if(npcAction == "@appearance" ){ + DoAppearance(npcParams); + } + else if (npcAction =="@speed") { + DoSpeed(npcParams); + } + else if (npcAction =="@notecard") { + DoNewNote(npcParams); + Notecard = npcParams; + } + else if (npcAction == "@attach") + { + DoAttach(npcParams); + } + else if (npcAction == "@teleport") + { + DoTeleport(npcParams); + } + else if (npcAction == "@reset") + { + DoDelete(); + SetStop(FALSE); // a @resst will restart the original !Path after deleting the notecard. + } + + STATE = NULL; + TimerEvent(QUICK); // yeah I know, not possible this fast, we just go as fast as we can go - this is so we do not recurse the stack +} + + + +/////////////////// UTILITY Functions, not state-like ////////////////// + +// DEBUG(string) will chat a string or display it as hovertext if debug == TRUE +DEBUG(string str) { + if (debug && ! LSLEditor) + llOwnerSay( str); // Send the owner debug info + if (debug && LSLEditor) + llSay(0, str); // Send to the Console in LSLEDitor + if (iTitleText) { + llSetText(str,<1.0,1.0,1.0>,1.0); // show hovertext + + } +} + +GetDest(string npcParams) { + list dest = llParseString2List(npcParams, ["<", ",", ">"], []); + vDestPos.x = llList2Float(dest, 0); + vDestPos.y = llList2Float(dest, 1); + vDestPos.z = llList2Float(dest, 2); +} + +NPCReadNoteCard(string Note) { + DEBUG("NPCReadNoteCard"); + lNpcCommandList = llParseString2List(osGetNotecard(Note), ["\n"], []); +} + +integer SenseAvatar() +{ + //Returns a strided list of the UUID, position, and name of each avatar in the region + list avatars = llGetAgentList(AGENT_LIST_REGION ,[]); + integer numOfAvatars = llGetListLength(avatars); + if (numOfAvatars == 0) + { + DEBUG("No people"); + return 0; + } + //DEBUG("Located " + (string)numOfAvatars + " avatars and NPC's"); + + integer nAvatars; + integer i; + for( i = 0;i < numOfAvatars; i++) { + key aviKey = llList2Key(avatars,i); + if (!osIsNpc(aviKey)) { + list detail = llGetObjectDetails(aviKey,[OBJECT_POS]); + vector pos = llList2Vector(detail,0); + float dist = llVecDist(pos, llGetPos()); + if (dist < RANGE) + { + nAvatars++; + DEBUG("In range:" + llKey2Name(aviKey)); + } + } + } + //DEBUG("Located " + (string) nAvatars + " avatars"); + return nAvatars; +} + +// return TRUE if the avatar is owner when private is set, or TRUE if the avatar is in the same group and GROUP is set. +integer checkPerms() { + + integer group = (integer) KeyValueGet("pr"); + if (! group) + priPub = "Owner Only"; + else + priPub = "Group"; + + + if (llDetectedKey(0) == llGetOwner()){ + kUserKey = llDetectedKey(0); + return TRUE; + } + + if ( group && llDetectedGroup(0)) { + kUserKey = llDetectedKey(0); + return TRUE; + } + kUserKey = llDetectedKey(0); + return FALSE; +} + + + +NPCAnimate(string anim) +{ + DEBUG("Start Anim: " + anim); + if (llGetInventoryType(anim) == INVENTORY_ANIMATION ) { + + if (lastANIM != anim) { + if(llStringLength(lastANIM)) { + osNpcStopAnimation(NPCKey(), lastANIM); + } + osNpcPlayAnimation(NPCKey(), anim); + lastANIM = anim; + } + } else { + llSay(DEBUG_CHANNEL, "No animation named " + anim); + } +} + +// Kill a NPC by Name +Kill(string param) +{ + integer count; + list avatars = osGetAvatarList(); // Returns a strided list of the UUID, position, and name of each avatar in the region except the owner.\ + integer i; + integer j = llGetListLength(avatars); + for (i=0 ; i <= j; i+=3){ + + string desired = llList2String(avatars,i+2); + desired = llStringTrim(desired,STRING_TRIM); // should not be needed but is needed + + if (desired == param){ + vector v = llList2Vector(avatars,i+1); + key target = llList2Key(avatars,i); // get the UUID of the avatar + osNpcRemove(target); + + llOwnerSay("Removed " + param+ " at location " + (string) v); + count++; + } + } + + NPCEnabled = FALSE; // not in world + SaveKey(NULL_KEY ); // Rev 4.4 + + if (count) + llOwnerSay("Removed " + (string) count + " NPC's"); + else + llOwnerSay("Could not locate " + param); +} + + +// return a String for the position we are at. Strings used as the caller wants strings +string Pos() +{ + vector where = llGetPos(); // find the box position + + where.z += OffsetZ; // use the ground position + an offset + + if (LSLEditor) + where = <128,128,31 + llFrand(1)>; // force center of sim when editing + + // if attached the height will be too high by 1/2 the agent size + if (llGetAttached()) { + vector size = llGetAgentSize(llGetOwner()); + float Z = size.z; + where.z -= Z/2; + } + + // DEBUG("Pos= " + (string) where); + return (string) where; +} + +// setup a menu with a timer for timeouts, called by all make*() +menu() +{ + llListenRemove(iHandle); + iChannel = llCeil(llFrand(100000) + 20000); + iHandle = llListen(iChannel,"","",""); + TimerEvent(30.0); + MENU = TRUE; +} + +// make a text box +makeText(string Param) +{ + menu(); + llTextBox(kUserKey, Param, iChannel); +} + +// top level menu +makeMainMenu() +{ + menu(); + list buttons = ["Appearance","Recording","Save","Help","-","Erase RAM", priPub,relAbs,"-","Stop NPC",mSensor,"Start NPC"]; + llDialog(kUserKey,(string) llGetListLength(lCommands) + " Records",buttons,iChannel); +} + + +// Rev 1.4 +// top level menu for non group/ non owners +makeUserMenu() +{ + if (!allowUsers) return; + + menu(); + list buttons = ["Start NPC","Stop NPC"]; + llDialog(kUserKey,"Choose",buttons,iChannel); +} + + + +// programmable menu for @commands +makeMenu(list buttons) +{ + menu(); + llDialog(kUserKey,(string) llGetListLength(lCommands) + " Record",buttons,iChannel); +} + + +// make one or two text boxes with prompts +Text(string cmd, string p1, string p2) +{ + sCommand = cmd; + sParam2 = ""; + if (llStringLength(p2)) + sParam2 = p2; + + makeText(p1); +} + +// Set the Avatar Present flag - if sensors are off and we are force run, there will be one present. +ProcessSensor() +{ + integer SensorOn; + if ("on" == KeyValueGet("se")) + { + SensorOn = TRUE; // we need to scan for avatars + } else { + SensorOn = FALSE; // we need to scan for avatars + } + DEBUG("Sensor:" + (string) SensorOn); + + integer n = SenseAvatar(); + + DEBUG("Avatars:" + (string) n); + if (SensorOn && n) + avatarPresent = TRUE; // someone is here and we need to tell the system to run + else if (SensorOn && !n) + avatarPresent = FALSE; // someone is not here and we need to tell the system to stop + else { // sensor is off, lete see if there is a NPC. If so, we are ON + // DEBUG("NPCEnabled:" + (string) NPCEnabled); + + if (NPCEnabled) + avatarPresent = TRUE; + else + avatarPresent = FALSE; + } + + // start up from when when no one is near + if (avatarPresent && STATE == NobodyHome) + STATE = NULL; + + DEBUG("Avatar Present: " + (string) avatarPresent); +} + +vector CirclePoint(float radius) { + float x = llFrand(radius *2) - radius; // +/- radius, randomized + float y = llFrand(radius *2) - radius; // +/- radius, randomized + return ; // so this should always happen +} + +string KeyValueGet(string var) { + list dVars = llParseString2List(llGetObjectDesc(), ["&"], []); + do { + list data = llParseString2List(llList2String(dVars, 0), ["="], []); + string k = llList2String(data, 0); + if(k != var) jump continue; + //DEBUG("got " + var + " = " + llList2String(data, 1)); + return llList2String(data, 1); + @continue; + dVars = llDeleteSubList(dVars, 0, 0); + } while(llGetListLength(dVars)); + return ""; +} + +KeyValueSet(string var, string val) { + + //DEBUG("set " + var + " = " + val); + list dVars = llParseString2List(llGetObjectDesc(), ["&"], []); + if(llGetListLength(dVars) == 0) + { + llSetObjectDesc(var + "=" + val); + return; + } + list result = []; + do { + list data = llParseString2List(llList2String(dVars, 0), ["="], []); + string k = llList2String(data, 0); + if(k == "") jump continue; + if(k == var && val == "") jump continue; + if(k == var) { + result += k + "=" + val; + val = ""; + jump continue; + } + string v = llList2String(data, 1); + if(v == "") jump continue; + result += k + "=" + v; + @continue; + dVars = llDeleteSubList(dVars, 0, 0); + } while(llGetListLength(dVars)); + if(val != "") result += var + "=" + val; + llSetObjectDesc(llDumpList2String(result, "&")); +} + + +// clear RAM +Clr() { + + lCommands = []; + llOwnerSay("RAM Memory cleared. Notecards, if any, are not modified."); + makeMainMenu(); +} + +integer checkNoteCards() +{ + // Check that they have saved an Appeaance and Path notecard + integer num = llGetInventoryNumber(INVENTORY_NOTECARD); // how many notecards overall + + integer i; + integer count; + for (; i < num; i++){ + if (llGetInventoryName(INVENTORY_NOTECARD,i) == Notecard) + count++; + if (llGetInventoryName(INVENTORY_NOTECARD,i) == Appearance) + count++; + } + DEBUG("Checked " + (string) count + " Notecards"); + // if we have both, run the NPC + return count; +} + +Update(string SName) { + + // delete all NPC* scripts except myself + integer i; + integer j = llGetInventoryNumber(INVENTORY_SCRIPT); + for (i = 0; i < j; i++) { + string targetName = llGetInventoryName(INVENTORY_SCRIPT,i); + string match = llGetSubString(targetName,0,2); + + if (match == SName && llGetScriptName() != targetName){ + llOwnerSay("Upgrading " + targetName); + if (! LSLEditor){ // lets not kill the editor + llRemoveInventory(targetName); + } + } + } +} + +// Get all default saved params from the Description +GetSwitches() +{ + string rA = KeyValueGet("co"); // Get the remembered menu setting for Abs Vs Relative + if (rA == "A") + relAbs = "Absolute"; + else if (rA == "R") + relAbs = "Relative"; + else + relAbs = "Absolute"; + + + // reenable NPC if sensor is on. + if ("on" == KeyValueGet("se")) + { + NPCEnabled = TRUE; + mSensor = "Sense is On"; + ProcessSensor(); // fake 1 avatar to get it rezzed + } else { + mSensor = "Sense is Off"; + } + } + + +SaveKey(key akey) +{ + DEBUG("Saving Key of " + (string) akey); + KeyValueSet("key", akey); + if (akey != (key) KeyValueGet("key") ) + { + DEBUG("Fatal error, cannot save key"); + } + gNpcKey = akey; +} + + +key NPCKey() +{ + key akey = gNpcKey; // from cached copy + // gNpcKey saves a lot of CPU processing by caching the key, if blank we get it from the description + if (gNpcKey == NULL_KEY) + { + //DEBUG("Get DKey"); + akey = KeyValueGet("key"); // from Description of the prim + } + // DEBUG("NPC KEY:" + (string) akey); + return akey; +} + + +/////////////////// CODE BEGINS ////////////////// + + +default +{ + changed(integer change) { + if(change & CHANGED_REGION_START) { + llResetScript(); + } + } + + on_rez(integer start_param) + { + llResetScript(); + } + + state_entry() { + + llSetText("",<1,1,1>,1.0); // clr all hovertext- we may not be using it. + DoDelete(); // kill any NPC that is out running + Update("NPC"); // If dragged and ropped into a prim with any script named "NPC...", this will replace it. + GetSwitches(); // Get all default saved params from the Description + + // 4.1 allow listeners to send us commands + if (allowListener) + llListen(link_Channel,"","",""); + TimerEvent(TIMER); + } + + + touch_start(integer n) + { // if touched, make a menu + + if (checkPerms()) { + if (RecordPath == STATE) { + makeMenu(lAtButtons); + } else { + makeMainMenu(); + } + } else { + makeUserMenu(); + } + } + + // menu listener + listen(integer iChannel, string name, key id, string message) { + + // process @commands that come in via the listener + if (iChannel == link_Channel) + { + ParseMsg(message); + return; + } + + if (MENU) { + llListenRemove(iHandle); + MENU = 0; // menu is off + iHandle = 0; + } + + if (message == "Stop NPC") + { + lNpcCommandList = []; // force reload of notecard + NPCEnabled = FALSE; + if (NPCKey() != NULL_KEY){ + Kill(sNPCName); + sNPCName = ""; + } else { + bNPC_STOP = TRUE; + makeText("Enter name of an NPC to stop"); + } + } + else if (message == "Menu" ) { + makeMainMenu(); + } + else if (message == "Erase RAM"){ + Clr(); + } + else if (message == "Relative"){ + relAbs = "Absolute"; + KeyValueSet("co","A"); // remember coordinates = A + Clr(); + } + else if (message == "Absolute"){ + relAbs = "Relative"; + KeyValueSet("co","R"); // remember coordinates = R + Clr(); + } + else if (message == "Recording"){ + DoMenuForCommands(); // show them the recording menu + } + else if (message == "Owner Only") { + priPub = "Group"; + KeyValueSet("pr","1"); + + llOwnerSay("Group members have control"); + makeMainMenu(); + } + else if (message == "Group") { + priPub = "Owner Only"; + KeyValueSet("pr","0"); + llOwnerSay("Only you have control"); + makeMainMenu(); + } + else if (message == "Sense is On") { + mSensor ="Sense is Off"; + KeyValueSet("se", "off"); + llOwnerSay(mSensor); + makeMainMenu(); + } + else if (message == "Sense is Off") { + mSensor ="Sense is On"; + llOwnerSay(mSensor); + KeyValueSet("se", "on"); + + NPCEnabled = TRUE; + + integer count = checkNoteCards(); + if (count >= 2) { + DEBUG("Notecards ok, DoProcessNPCLine"); + DoProcessNPCLine(); + return; + } + if (LSLEditor) { + DoProcessNPCLine(); + return; + } + + llOwnerSay("You have not saved a recording and/or appearance, so you cannot start a NPC"); + makeMainMenu(); + } + else if (message == "Appearance") { + llRemoveInventory(Appearance); // delete the notecard + osAgentSaveAppearance(kUserKey,Appearance); // make the ntecard + llOwnerSay("Your outfit has been saved"); + makeMainMenu(); + } + else if (message == "Save") { + if (llGetListLength(lCommands) == 0) { + llOwnerSay("Nothing recorded, you need to make a recording first"); + makeMainMenu(); + return; + } + DoSave(); + } + else if (message == "Help"){ + llLoadURL(kUserKey,"Click to view help","http://www.outworldz.com/opensim/posts/NPC/"); + makeMainMenu(); + } + else if (message == "Start NPC") { + integer count = checkNoteCards(); + + NPCEnabled = TRUE; + + if (LSLEditor) { + DoProcessNPCLine(); + return; + } + + if (count >= 2) { + DEBUG("Notecards approved , calling DoProcessNPCLine"); + SetStop(FALSE); // Let's run the notecard + DoProcessNPCLine(); + return; + } + + llOwnerSay("You have not saved a recording or maybe an appearance, so we cannot start a NPC"); + + } + else if (bNPC_STOP){ + bNPC_STOP = FALSE; + Kill(message); + } + else if (message == ">>"){ + makeMenu(lMenu2); + } + else if (message == ">>>"){ + makeMenu(lMenu3); + } + else if (message == "<<") { + makeMenu(lAtButtons); + } + else if (message == "<<<") { + makeMenu(lMenu2); + } + else if (message == "@comment"){ + Text("# ","Enter a comment",""); + } + else if (message == "@stop"){ + lCommands += "@stop"+ "\n"; + makeMenu(lAtButtons); + } + else if (message == "@run"){ + lCommands += "@run=" + Pos() + "\n"; + llOwnerSay("Recorded position: " + Pos()); + makeMenu(lAtButtons); + } + else if (message == "@fly"){ + lCommands += "@fly=" + Pos() + "\n"; + llOwnerSay("Recorded position: " + Pos()); + makeMenu(lAtButtons); + } + else if (message == "@land"){ + lCommands += "@land=" + Pos() + "\n"; + llOwnerSay("Recorded position: " + Pos()); + makeMenu(lAtButtons); + } + else if (message == "@walk") { + lCommands += "@walk=" + Pos() + "\n"; + llOwnerSay("Recorded position: " + Pos()); + makeMenu(lAtButtons); + } + else if (message == "@stop"){ + lCommands += "@stop"+ "\n"; + makeMenu(lAtButtons); + } + else if (message == "@sound"){ + Text("@sound=","Enter a sound name or UUID to trigger",""); + } + else if (message == "@randsound"){ + lCommands += "@randsound"+ "\n"; + makeMenu(lAtButtons); + } + else if (message == "@say") { + Text("@say=","Enter what the NPC will say",""); + } + else if (message == "@whisper"){ + Text("@whisper=","Enter what the NPC will whisper",""); + } + else if (message == "@shout"){ + Text("@shout=","Enter what the NPC will shout",""); + } + else if (message == "@wander") { + Text("@wander=","Enter radius to wander","Enter number of wanders"); + } + else if (message == "@pause") { + Text("@pause=","Enter time to pause",""); + } + else if (message == "@rotate") { + Text("@rotate=","Enter degrees to rotate",""); + } + else if (message == "@sit"){ + Text("@sit=","Enter name of object to sit on",""); + } + else if (message == "@teleport"){ + lCommands += "@teleport=" + Pos() + "\n"; + llOwnerSay("teleport to position: " + Pos()); + makeMenu(lMenu3); + } + else if (message == "@touch"){ + Text("@touch=","Enter name of object to touch",""); + } + else if (message == "@cmd"){ + Text("@cmd=","Enter cjhannel to speak on","Enter text to speak"); + } + else if (message == "@stand"){ + lCommands += "@stand\n"; + llOwnerSay("Stand Recorded"); + makeMenu(lAtButtons); + } + else if (message == "@animate"){ + Text("@animate=","Enter animation name to play","Enter time to play the animation"); + } + else if (message == "@attach"){ + Text("@animate=","Enter inventory name to attach","Enter number of the attachment point (1-40)"); + } + else if (message == "@speed"){ + Text("@speed=","Enter a speed for the NPC, 1=100% normal speed, 0.5=50% speed",""); + } + + + // Save NPC name + else if (MakeNotecard == STATE) { + sNPCName = message; // in case we need to kill it. + + vector vDest = (vector) Pos(); + + if (relAbs == "Relative") + { + vDest -= llGetPos(); // just an offset for relative + } + sNotecard = "@spawn=" + message + "|" + (string) vDest + "\n"; + integer i; + integer j = llGetListLength(lCommands); + for (; i < j; i++){ + // get the command to save to the notecard + string line = llList2String(lCommands,i); + if (relAbs == "Absolute") { + sNotecard += line; // add the un-modified string to the notecard + } else { + // since we have to record absolute coords since we do not know where the box goes until they press Save, + // we process the absolute to relative conversion for walks here + list parts = llParseString2List(line,["="],[]); //get the @command + + if (llList2String(parts,0) == "@walk") { + vector vec = (vector) llList2String(parts,1) - llGetPos(); + sNotecard += "@walk=" + (string) vec + "\n"; + } + else if (llList2String(parts,0) == "@fly") { + vector vec = (vector) llList2String(parts,1) - llGetPos(); + sNotecard += "@fly=" + (string) vec + "\n"; + } + else if (llList2String(parts,0) == "@run") { + vector vec = (vector) llList2String(parts,1) - llGetPos(); + sNotecard += "@run=" + (string) vec + "\n"; + } + else if (llList2String(parts,0) == "@land") { + vector vec = (vector) llList2String(parts,1) - llGetPos(); + sNotecard += "@land=" + (string) vec + "\n"; + } + else { + sNotecard += line; // add the un-modified string to the notecard + } + } + } + llRemoveInventory(Notecard); // delete the old notecard + osMakeNotecard(Notecard,sNotecard); // Makes the notecard. + llSay(0,sNotecard); + llOwnerSay("Commands notecard has been written"); + STATE = NULL; + } // MakeNotecard + + else if (! llStringLength(sParam2)) { + lCommands += sCommand + message + "\n"; + llOwnerSay("Recorded"); + makeMenu(lAtButtons); + } + else if (llStringLength(sParam2)){ + sCommand = sCommand + message + "|"; + llOwnerSay("Recorded"); + makeText(sParam2); + sParam2 = ""; + } + + } + + + + timer(){ + // DEBUG("tick"); + + // if llDialog is up, kill the listener for the dialog box. + if (iHandle) { + llOwnerSay("Menu timed out"); + llListenRemove(iHandle); + iHandle = 0; + return; // ^^^^^^^^^^^^^^^^^^^^^^^ + } + // if NoBodyHome, we are sensing for an avatar + else if (NobodyHome == STATE) { + ProcessSensor(); + return; + } + // if we are spawning, we need time to rez the NPC, then start processing NPC Commands. + else if (Spawning == STATE) { + STATE = NULL; + TimerEvent(TIMER); + } + // We end aniamtions with a timer + else if (Animate == STATE){ + NPCAnimate(STAND); + TimerEvent(TIMER); + } + + else if (Walking == STATE) { + if (--iWaitCounter) { + DEBUG("still walking..."); + if (llVecDist(osNpcGetPos(NPCKey()), newDest) > MAXDIST) { + return; + } + } + + DEBUG("At Destination: " + (string) newDest); + + // walk, fly, run, land + if (walkstate == 1) { + NPCAnimate(STAND); + NPCWalkOption = OS_NPC_NO_FLY; + } else if (walkstate == 2) { + // nothing + } else if (walkstate == 3) { + NPCAnimate(STAND); + NPCWalkOption = OS_NPC_NO_FLY; + } else if (walkstate == 4) { + llShout(FLIGHT,"landing"); + NPCAnimate(STAND); + NPCWalkOption = OS_NPC_NO_FLY; + } + } + // Wandering timer + else if (Wander == STATE) { + if (--iWaitCounter) { // wait 60 seconds to get to a destination. + if (llVecDist(osNpcGetPos(NPCKey()), vWanderPos) > MAXDIST) + return; + } + + // see if wander counter == 0, if so, stop walking, go to stand and process next line + if(RAMwc == 0) { + NPCAnimate(STAND); + DEBUG("Wander ended, calling DoProcessNPCLine"); + STATE = NULL; + return; + } + // one less time to wander around + RAMwc--; + NPCAnimate(STAND); + TimerEvent(TIMER); + StateWanderhold(); + return; + } + // Wandering requires us to re-wander when we reach a destination + else if (WanderHold == STATE) { + StateWander(); + } + else if (DoProcess == STATE) { + TimerEvent(QUICK); + } + + STATE = NULL; + + // We always process a NPC line at end of timer. + DEBUG("Tick end, calling DoProcessNPCLine"); + DoProcessNPCLine(); + } + + // sensors are used for sitting on prims + // Neo Cortex: added different states to trigger sit or touch + sensor(integer num) { + if (Sit == STATE ) { + osNpcSit(NPCKey(), llDetectedKey(0), OS_NPC_SIT_NOW); + DEBUG("Seated, calling DoProcessNPCLine"); + + STATE = 0; + } else if (Touch == STATE) { + osNpcTouch(NPCKey(), llDetectedKey(0), LINK_THIS); + DEBUG("Touched, calling DoProcessNPCLine"); + STATE = 0; + } + DoProcessNPCLine(); + } + no_sensor(){ + DEBUG ("no target prim located, calling DoProcessNPCLine"); + DoProcessNPCLine(); + STATE = NULL; + } + + + link_message(integer sender, integer num, string str, key id){ + if (num == 0) + ParseMsg(str); + if (num == 99) + llResetScript(); + } + +} + +// __ END__ + + + + diff --git a/HyperGrid Story Nine/Nine/Nine.prj b/HyperGrid Story Nine/Nine/Nine.prj new file mode 100644 index 00000000..0a81e867 --- /dev/null +++ b/HyperGrid Story Nine/Nine/Nine.prj @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/HyperGrid Story Nine/Nine/Timer/Shins Script.lsl b/HyperGrid Story Nine/Nine/Timer/Shins Script.lsl new file mode 100644 index 00000000..50363449 --- /dev/null +++ b/HyperGrid Story Nine/Nine/Timer/Shins Script.lsl @@ -0,0 +1,280 @@ +// :SHOW:1 +// :CATEGORY:NPC +// :NAME:HyperGrid Story Nine +// :AUTHOR:Shin Ingen +// :KEYWORDS:NPC, controller and timer +// :CREATED:2015-11-24 20:25:34 +// :EDITED:2015-11-24 19:25:34 +// :ID:1087 +// :NUM:1845 +// :REV:1.0 +// :WORLD:OpenSim +// :DESCRIPTION: +// Timer script - ended up not being used. +// :CODE: + +//HYPERGRID STORY NINE +// List NPC Listener channels +//sara=11 sammy=12 claire=13 jolinda=14 tiny=15 tiny2=16 fireworks=20 + +integer gListener; // Listener for handling different channels +integer gSimpleMenuChannel; // The channel used for the menu +float gTimerInterval = 1; // Keep this at 1 for your sanity. +list gTimeoutList; +float gTimeElapsed=0; +integer gAutofire=FALSE; +//============================================================================ +vector gFacecolor=<1.0,0.0,0.0>; //RED +integer gNewTime; +integer gOldTime; +//==Function that returns a random number for our menu handler channel +integer randomchannel() { + return((integer)(llFrand(99999.0)*-1)); + } + +menu(key id, integer channel, string title, list buttons) { + llListenRemove(gListener); + gListener = llListen(channel,"",id,""); + llDialog(id,title,buttons,channel); + // TimerEvent for killing the menu listener + settimeout("untouched", 30); //call untouched timeout + } + +simplemenu(key id) { + gSimpleMenuChannel = randomchannel(); + menu(id,gSimpleMenuChannel,"Select an option",["FIREWORKS","SARA","SAMMY","CLAIRE","JOLINDA","TINY","TINY2","LeadNPC","Finale!","CeaseFire"]); + } + +settimeout(string timereventid, float time) { + if(gTimeoutList == []) + llSetTimerEvent(gTimerInterval); + integer identifyerIndex = llListFindList(gTimeoutList, [timereventid]); + if (identifyerIndex != -1) + gTimeoutList = llDeleteSubList(gTimeoutList, identifyerIndex - 1, identifyerIndex); + if (time != 0) { + gTimeoutList += time + gTimeElapsed; + gTimeoutList += timereventid; + } + gTimeoutList = llListSort(gTimeoutList, 2, TRUE); + } + +timeout(string timereventid) +{ + if (timereventid == "untouched") + { + llSay(0, "I have been untouched for 30 seconds, killing menu listener!"); + llListenRemove(gListener); // kill the listener after 30 seconds inactivity + } + else if (timereventid == "countdown2execute") + { + float seconds2count = 1800; + llSay(0, "I have counted down " + (string)seconds2count + " seconds, repeat countdown and start the show"); + settimeout("countdown2execute", seconds2count); //call it again to loop. + } + else if (timereventid == "autofire") + { + if(gAutofire) + { + llSay(0,"should fire every 5 seconds."); + llRegionSay(20,"Go"); + settimeout("autofire", 5); + } + } + if (timereventid == "stopautofire") + { + gAutofire=FALSE; + } +} +timertick() +{ + gTimeElapsed += gTimerInterval; + integer i; + integer numTimers = llGetListLength(gTimeoutList); + for (i = 0; i < numTimers; i += 2) + { + float triggerTime = llList2Float(gTimeoutList, i); + if (triggerTime <= gTimeElapsed) + { + string timereventid = llList2String(gTimeoutList, i + 1); + gTimeoutList = llDeleteSubList(gTimeoutList, i, i + 1); + timeout(timereventid); + if (timereventid=="countdown2execute") + { + displaycountdowntext((integer)gTimeElapsed); + getdigit((integer)gTimeElapsed); + } + if (gTimeoutList == []) + llSetTimerEvent(0); + } + else + { + return; + } + } +} + +displaydigit(integer n, string d){ + list Facelist; //list depends on your mesh digital readout face number. + integer i; + if(n==0) Facelist=[ 1,1,1,1,0,1,1 ]; //index @0 | 7 faces per digit + if(n==1) Facelist=[ 0,0,1,0,0,1,0 ]; + if(n==2) Facelist=[ 1,1,0,1,1,1,0 ]; + if(n==3) Facelist=[ 1,1,1,0,1,1,0 ]; + if(n==4) Facelist=[ 0,0,1,0,1,1,1 ]; + if(n==5) Facelist=[ 1,1,1,0,1,0,1 ]; + if(n==6) Facelist=[ 0,1,1,1,1,0,1 ]; + if(n==7) Facelist=[ 1,0,1,0,0,1,0 ]; + if(n==8) Facelist=[ 1,1,1,1,1,1,1 ]; + if(n==9) Facelist=[ 1,0,1,0,1,1,1 ]; + + integer l = llGetLinkNumber() != 0; + integer x = llGetNumberOfPrims() + l; + + for (; l < x; ++l){ + if (llGetLinkName(l) == d){ + for (i=0;i<7;i++){ + llSetLinkPrimitiveParamsFast(l,[PRIM_COLOR,i,gFacecolor,llList2Float(Facelist,i)]); + } + } + } +} + +getdigit(integer time){ +string dOne; +string dTen; +string dHundred; +string dThousand; + +integer i; + + if (time<10){ + dOne=(string)time; + dTen="0"; + dHundred="0"; + dThousand="0"; // add your digits here + }else if(time<100){ + dOne=llGetSubString((string)time,-1,-1); + dTen=llGetSubString((string)time,0,0); + dHundred="0"; + dThousand="0"; + }else if(time<1000){ + dOne=llGetSubString((string)time,-1,-1); + dTen=llGetSubString((string)time,1,1); + dHundred=llGetSubString((string)time,0,0); + dThousand="0"; + }else if(time<10000){ + dOne=llGetSubString((string)time,-1,-1); + dTen=llGetSubString((string)time,2,2); + dHundred=llGetSubString((string)time,1,1); + dThousand=llGetSubString((string)time,0,0); + } + displaydigit((integer)dOne, "one"); + displaydigit((integer)dTen, "ten"); + displaydigit((integer)dHundred, "hundred"); + displaydigit((integer)dThousand, "thousand"); +} + +displaycountdowntext(integer time){ + gNewTime = time; + if (gOldTime < gNewTime){ + llSetText("Elapsed Ticker: " + (string)gNewTime + " ticks",<1,1,.6>,1.0); + gOldTime = gNewTime; + } +} + +default +{ + state_entry() + { + float seconds2count = 3600; + settimeout("countdown2execute", seconds2count); + + } + touch_start(integer num) + { + simplemenu(llDetectedKey(0)); + } + + listen (integer channel, string name, key id, string message) + { + if (message == "FIREWORKS") { + llSay(0,"You selected option FIREWORKS"); + llRegionSay(20,"Go"); + //do something else here + } + else if (message == "SARA") + { + llSay(0,"You selected option SARA"); + llRegionSay(11,"@go"); + //do something else here + } + else if (message == "SAMMY") + { + llSay(0,"You selected option SAMMY"); + llRegionSay(12,"@go"); + //do something else here + } + else if (message == "CLAIRE") + { + llSay(0,"You selected option CLAIRE"); + llRegionSay(13,"@go"); + //do something else here + } + else if (message == "JOLINDA") + { + llSay(0,"You selected option JOLINDA"); + llRegionSay(14,"@go"); + //do something else here + } + else if (message == "TINY") + { + llSay(0,"You selected option TINY"); + llRegionSay(15,"@go"); + //do something else here + } + else if (message == "TINY2") + { + llSay(0,"You selected option TINY2"); + llRegionSay(16,"@go"); + //do something else here + } + else if (message == "LeadNPC") + { + llSay(0,"You selected option LeadNPC"); + llRegionSay(10,"@go"); + //do something else here + } + else if (message == "Finale!") + { + llSay(0,"You selected option Finale!"); + gAutofire=TRUE; + settimeout("autofire", 1); + settimeout("stopautofire", 90); + //do something else here + } + else if (message == "CeaseFire") + { + llSay(0,"You selected option CeaseFire"); + gAutofire=FALSE; + //do something else here + } + simplemenu(id); // refresh menu + } + + timer() + { + timertick(); + integer MaxTicks = 1800; //Ticks to countdown from + //limit the ticker to maximum ticks + if(gTimeElapsed >= MaxTicks){ + gTimeElapsed=0; + } + integer CountDown = MaxTicks - (integer)gTimeElapsed; + integer mm = (CountDown / 60) * 100; + integer ss = (CountDown % 60); + integer cc = mm + ss; + displaycountdowntext((integer)gTimeElapsed); + getdigit((integer)cc); + + } +} diff --git a/HyperGrid Story Nine/Nine/Timer/Timer.lsl b/HyperGrid Story Nine/Nine/Timer/Timer.lsl new file mode 100644 index 00000000..17f2cd54 --- /dev/null +++ b/HyperGrid Story Nine/Nine/Timer/Timer.lsl @@ -0,0 +1,402 @@ +// :SHOW:1 +// :CATEGORY:NPC +// :NAME:HyperGrid Story Nine +// :AUTHOR:Shin Ingen +// :KEYWORDS:NPC, controller and timer +// :CREATED:2015-11-24 20:25:34 +// :EDITED:2015-11-24 19:25:34 +// :ID:1087 +// :NUM:1846 +// :REV:1.0 +// :WORLD:OpenSim +// :DESCRIPTION: +// Timer script - ended up not being used. +// :CODE: + + + +integer debug = 2; + +// Link Numbers of the NPC controller +integer namaka =2; +integer dylan =3; +integer npc1 =4; +integer npc2 =5; +integer npc3 =6; +integer npc4 =7; +integer npc5 =8; +integer npc6 =9; + +// List of NPC Listener channels + +integer gFireworksChannel =20; +integer gTeleportChannel = 21; +integer CommandChannel = 23; +integer gSimpleMenuChannel = 999; // The channel used for the menu + +integer MaxTicks = 60; // Show length +// The repeat the show timer + +list kDance1 = ["Dance1",1]; // Dance +list kDance2 = ["Dance2",10]; // Dance +list kDance3 = ["Dance3",20]; // Dance +list kDance4 = ["Dance4",30]; // Dance +list kDance5 = ["Dance5",40]; // Dance +list kDance6 = ["Dance6",50]; // Dance + +list kFireworksRepRate = ["Shoot", 2]; // rep rate for finale + +// the following are calculated based on the current timer tick +list kFireworksStartsAt = ["StartFireworks", 99999]; // rep rate for finale +list kRaiseTeleport = ["BeamMeUp",99999]; // raise the teleports +list kStopAutoFire = ["EndFireworks",99999]; // and stops here + +SendMessageLinked(integer npc, string cmd) +{ + llRegionSay(CommandChannel,(string) npc + "|" + cmd); +} +DoCmd(string message, integer channel) +{ + if (channel == CommandChannel) { + Process(message); + } + + if (message == "ShowTime") { + + DEBUG("Running"); + ShowTime(); + } + else if (message == "Finale!") + { + kFireworksStartsAt = ["StartFireworks", gTimeElapsed+1]; // rep rate for finale + kRaiseTeleport = ["BeamMeUp",gTimeElapsed+25]; // raise the teleports + kStopAutoFire = ["EndFireworks",gTimeElapsed+30]; // and stops here + + settimeout(kFireworksStartsAt); + settimeout(kStopAutoFire); + settimeout(kRaiseTeleport); + } + else if (message == "Stop") + { + llSay(0,"You selected option Stop"); + gTimeoutList = []; + } + +} +// this is a list of all the cycling show bits +ShowTime() +{ + DEBUG("Starting Countdown"); + gTimeElapsed=0; + + settimeout(kDance1); + settimeout(kDance2); + settimeout(kDance3); + settimeout(kDance4); + settimeout(kDance5); + settimeout(kDance6); + + DEBUG(llDumpList2String(gTimeoutList,":")); +} + +Process(string timereventid) { + + if (llList2String(kDance1,0) == timereventid) + { + DEBUG("Dance1"); +; + SendMessageLinked(dylan,"@appearance=Dylan"); + SendMessageLinked(namaka,"@appearance=Namaka"); + + SendMessageLinked(npc1,"@appearance=Crispen Enpeacee"); + SendMessageLinked(npc2,"@appearance=Alessandra Enpeacee"); + SendMessageLinked(npc3,"@appearance=Sammy Enpeacee"); + SendMessageLinked(npc4,"@appearance=Jolinda Enpeacee"); + SendMessageLinked(npc5,"@appearance=Claire Enpeacee"); + SendMessageLinked(npc6,"@appearance=Marianne Enpeacee"); + + SendMessageLinked(LINK_SET,"@notecard=Dance1"); + } + else if (llList2String(kDance2,0) == timereventid) + { + DEBUG("Dance2"); + SendMessageLinked(LINK_SET,"@appearance=Alessandra Enpeacee"); + SendMessageLinked(LINK_SET,"@notecard=Dance2"); + } + else if (llList2String(kDance3,0) == timereventid) + { + DEBUG("Dance3"); + SendMessageLinked(LINK_SET,"@appearance=Sammy Enpeacee"); + SendMessageLinked(LINK_SET,"@notecard=Dance3"); + } + else if (llList2String(kDance4,0)== timereventid) + { + DEBUG("Dance4"); + SendMessageLinked(LINK_SET,"@appearance=Jolinda Enpeacee"); + SendMessageLinked(LINK_SET,"@notecard=Dance4"); + } + else if (llList2String(kDance5,0)== timereventid) + { + DEBUG("Dance5"); + SendMessageLinked(LINK_SET,"@appearance=Claire Enpeacee"); + SendMessageLinked(LINK_SET,"@notecard=Dance5"); + } + else if (llList2String(kDance6,0)== timereventid) + { + DEBUG("Dance6"); + SendMessageLinked(LINK_SET,"@appearance=MarianneEnpeacee"); + SendMessageLinked(LINK_SET,"@notecard=Dance6"); + } + else if (llList2String(kFireworksStartsAt,0) == timereventid) + { + DEBUG("Shoot!"); + SendMessageLinked(LINK_SET,"@appearance=Dragon"); + SendMessageLinked(LINK_SET,"@notecard=Fireworks"); + llRegionSay(gFireworksChannel,"Go"); + settimeout(kFireworksRepRate); + } + else if (llList2String(kFireworksRepRate,0) == timereventid) + { + DEBUG("Fireworks Start"); + llRegionSay(gFireworksChannel,"Go"); + + SendMessageLinked(npc1,"@appearance=Crispen Enpeacee"); + SendMessageLinked(npc2,"@appearance=Alessandra Enpeacee"); + SendMessageLinked(npc3,"@appearance=Sammy Enpeacee"); + SendMessageLinked(npc4,"@appearance=Jolinda Enpeacee"); + SendMessageLinked(npc5,"@appearance=Claire Enpeacee"); + SendMessageLinked(npc6,"@appearance=Marianne Enpeacee"); + + SendMessageLinked(dylan,"@appearance=Dylan"); + SendMessageLinked(namaka,"@appearance=Namaka"); + + SendMessageLinked(LINK_SET,"@notecard=Final"); + + settimeout(kFireworksRepRate); + } + else if (llList2String(kStopAutoFire,0) == timereventid) + { + + DEBUG("Fireworks End"); + deletetimeout(kFireworksRepRate); + settimeout(kRaiseTeleport); + } + else if (llList2String(kRaiseTeleport,0) == timereventid) + { + SendMessageLinked(LINK_SET,"@notecard=Teleport"); + DEBUG("Teleports Up"); + llRegionSay(gTeleportChannel,"Up"); + } +} + +integer busy; +integer gListener; // Listener for handling different channels +list gTimeoutList; +float gTimeElapsed=0; + +//============================================================================ +vector gFacecolor=<1.0,0.0,0.0>; //RED + + +DEBUG(string msg) +{ + if (debug ==1) + llSay(0,llGetScriptName() + ":" + msg); + if (debug ==2) + llSetText(msg, <1,0,0>,1.0); +} + + + + + +menu(key id, integer channel, string title, list buttons) { + llListenRemove(gListener); + gListener = llListen(channel,"",id,""); + llDialog(id,title,buttons,channel); +} + +simplemenu(key id) { + menu(id,gSimpleMenuChannel,"Select an option",["ShowTime","Finale","Stop"]); +} + +deletetimeout(list events) { + + string timereventid = llList2String(events,0); + + DEBUG("Deleteing timer event " + timereventid); + integer identifyerIndex = llListFindList(gTimeoutList, [timereventid]); + if (identifyerIndex != -1) + gTimeoutList = llDeleteSubList(gTimeoutList, identifyerIndex - 1, identifyerIndex); +} + +settimeout(list events) +{ + // unpack the list + string timereventid = llList2String(events,0); + integer time = llList2Integer(events,1); + + DEBUG("Adding " + timereventid); + integer identifyerIndex = llListFindList(gTimeoutList, [timereventid]); + if (identifyerIndex != -1) + gTimeoutList = llDeleteSubList(gTimeoutList, identifyerIndex - 1, identifyerIndex); + if (time != 0) { + gTimeoutList += time; + gTimeoutList += timereventid; + } + + llSetTimerEvent(1.0); + +} + +timertick() { + gTimeElapsed ++; + //DEBUG((string) gTimeElapsed ); + + integer i; + integer numTimers = llGetListLength(gTimeoutList); + + //DEBUG((string) (numTimers/2) + " timers"); + // scan over all queued timers + for (i = 0; i < numTimers; i += 2) + { + integer triggerTime = llList2Integer(gTimeoutList, i); + + //DEBUG("TriggerTime = " + (string)triggerTime); + if (triggerTime == gTimeElapsed) { + + string timereventid = llList2String(gTimeoutList, i + 1); + //DEBUG("matched " + timereventid); + + gTimeoutList = llDeleteSubList(gTimeoutList, i, i + 1); // they are one-shots, so delete the event that just happened. + + Process(timereventid); // process the callback + + + if (gTimeoutList == []) { + DEBUG("Show over! Starting back up"); + ShowTime(); + } + } + } +} + +displaydigit(integer n, string d){ + list Facelist; //list depends on your mesh digital readout face number. + integer i; + if(n==0) Facelist=[ 1,1,1,1,0,1,1 ]; //index @0 | 7 faces per digit + if(n==1) Facelist=[ 0,0,1,0,0,1,0 ]; + if(n==2) Facelist=[ 1,1,0,1,1,1,0 ]; + if(n==3) Facelist=[ 1,1,1,0,1,1,0 ]; + if(n==4) Facelist=[ 0,0,1,0,1,1,1 ]; + if(n==5) Facelist=[ 1,1,1,0,1,0,1 ]; + if(n==6) Facelist=[ 0,1,1,1,1,0,1 ]; + if(n==7) Facelist=[ 1,0,1,0,0,1,0 ]; + if(n==8) Facelist=[ 1,1,1,1,1,1,1 ]; + if(n==9) Facelist=[ 1,0,1,0,1,1,1 ]; + + integer l = llGetLinkNumber() != 0; + integer x = llGetNumberOfPrims() + l; + + for (; l < x; ++l){ + if (llGetLinkName(l) == d){ + for (i=0;i<7;i++){ + llSetLinkPrimitiveParamsFast(l,[PRIM_COLOR,i,gFacecolor,llList2Float(Facelist,i)]); + } + } + } +} + +getdigit(integer time){ + string dOne; + string dTen; + string dHundred; + string dThousand; + + if (time<10){ + dOne=(string)time; + dTen="0"; + dHundred="0"; + dThousand="0"; // add your digits here + }else if(time<100){ + dOne=llGetSubString((string)time,-1,-1); + dTen=llGetSubString((string)time,0,0); + dHundred="0"; + dThousand="0"; + }else if(time<1000){ + dOne=llGetSubString((string)time,-1,-1); + dTen=llGetSubString((string)time,1,1); + dHundred=llGetSubString((string)time,0,0); + dThousand="0"; + }else if(time<10000){ + dOne=llGetSubString((string)time,-1,-1); + dTen=llGetSubString((string)time,2,2); + dHundred=llGetSubString((string)time,1,1); + dThousand=llGetSubString((string)time,0,0); + } + displaydigit((integer)dOne, "one"); + displaydigit((integer)dTen, "ten"); + displaydigit((integer)dHundred, "hundred"); + displaydigit((integer)dThousand, "thousand"); + + //DEBUG(dThousand + dHundred + ":" + dTen + dOne); +} + +StopAll() +{ + DEBUG("Stopped"); + llSetTimerEvent(0); + +} + +default +{ + state_entry() + { + llListen(CommandChannel,"","",""); + getdigit(0); + } + + touch_start(integer num) + { + if ( llGetOwner() == llDetectedKey(0)) { + if (! busy) { + simplemenu(llDetectedKey(0)); + busy++; + } else { + busy = FALSE; + StopAll(); + } + } + } + + link_message(integer sender_number, integer number, string message, key id) + { + DEBUG("LINK MESSAGE\n" + message); + DoCmd(message, number); + } + + listen (integer channel, string name, key id, string message) + { + // !!! add parser for killing the NPC + DEBUG("LISTEN\n" + message); + DoCmd(message, channel); + } + + timer() + { + timertick(); + + //limit the ticker to maximum ticks + if(gTimeElapsed >= MaxTicks){ + gTimeElapsed=0; + } + integer CountDown = MaxTicks - (integer)gTimeElapsed; + integer mm = (CountDown / 60) * 100; + integer ss = (CountDown % 60); + integer cc = mm + ss; + + getdigit((integer)cc); + } + +} diff --git a/HyperGrid Story Nine/Photos/Image1.jpg b/HyperGrid Story Nine/Photos/Image1.jpg new file mode 100644 index 00000000..62419257 Binary files /dev/null and b/HyperGrid Story Nine/Photos/Image1.jpg differ diff --git a/HyperGrid Story Nine/Photos/Image1noted.jpg b/HyperGrid Story Nine/Photos/Image1noted.jpg new file mode 100644 index 00000000..94452c52 Binary files /dev/null and b/HyperGrid Story Nine/Photos/Image1noted.jpg differ diff --git a/HyperGrid Story Nine/Photos/Image2.jpg b/HyperGrid Story Nine/Photos/Image2.jpg new file mode 100644 index 00000000..9f1c00fa Binary files /dev/null and b/HyperGrid Story Nine/Photos/Image2.jpg differ diff --git a/HyperGrid Story Nine/Photos/Image3.jpg b/HyperGrid Story Nine/Photos/Image3.jpg new file mode 100644 index 00000000..a4f703ce Binary files /dev/null and b/HyperGrid Story Nine/Photos/Image3.jpg differ diff --git a/HyperGrid Story Nine/Photos/Image4.jpg b/HyperGrid Story Nine/Photos/Image4.jpg new file mode 100644 index 00000000..c43fcc0f Binary files /dev/null and b/HyperGrid Story Nine/Photos/Image4.jpg differ diff --git a/HyperGrid Story Nine/Photos/Lighting change.gif b/HyperGrid Story Nine/Photos/Lighting change.gif new file mode 100644 index 00000000..a423628b Binary files /dev/null and b/HyperGrid Story Nine/Photos/Lighting change.gif differ diff --git a/HyperGrid Story Nine/Photos/Nine.gif b/HyperGrid Story Nine/Photos/Nine.gif new file mode 100644 index 00000000..4d51fa37 Binary files /dev/null and b/HyperGrid Story Nine/Photos/Nine.gif differ diff --git a/HyperGrid Story Nine/Photos/lighting.gif b/HyperGrid Story Nine/Photos/lighting.gif new file mode 100644 index 00000000..9f58f845 Binary files /dev/null and b/HyperGrid Story Nine/Photos/lighting.gif differ diff --git a/Hypergrid Story One/HyperGrid Story One.sol b/Hypergrid Story One/HyperGrid Story One.sol new file mode 100644 index 00000000..c3c198ff --- /dev/null +++ b/Hypergrid Story One/HyperGrid Story One.sol @@ -0,0 +1,3 @@ + + + diff --git a/Hypergrid Story One/HyperGrid Story One/HyperGrid Story One.prj b/Hypergrid Story One/HyperGrid Story One/HyperGrid Story One.prj new file mode 100644 index 00000000..84aad81a --- /dev/null +++ b/Hypergrid Story One/HyperGrid Story One/HyperGrid Story One.prj @@ -0,0 +1,14 @@ + + + + + + + + + + + + + diff --git a/Hypergrid Story One/HyperGrid Story One/Object/!Story b/Hypergrid Story One/HyperGrid Story One/Object/!Story new file mode 100644 index 00000000..434431a0 --- /dev/null +++ b/Hypergrid Story One/HyperGrid Story One/Object/!Story @@ -0,0 +1,23 @@ +// :SHOW:1 +// :CATEGORY:NPC +// :NAME:Hypergrid Story One +// :AUTHOR:Ferd Frederix +// :KEYWORDS:Game, Collider +// :CREATED:2015-11-24 20:36:34 +// :EDITED:2015-11-24 19:36:34 +// :ID:1089 +// :NUM:1852 +// :REV:3.0 +// :WORLD:Opensim +// :DESCRIPTION: +// Sample NPC Card +// :CODE: + +1,1,"@spawn=Namaka ♥|<127, 126, 23>|Namaka" +2,1,"@spawn=Dylan ♥|<127, 129, 23>|Dylan" +1,1,"@sit=dancerock" +2,2,"@sit=dancerock" +1,1,"@say=Hi, I am Namaka" +2,5,"@say=I am Dylan!... and now we leave" +1,1,"@delete" +2,1,"@delete" diff --git a/Hypergrid Story One/HyperGrid Story One/Object/Dylan b/Hypergrid Story One/HyperGrid Story One/Object/Dylan new file mode 100644 index 00000000..07319cf9 --- /dev/null +++ b/Hypergrid Story One/HyperGrid Story One/Object/Dylan @@ -0,0 +1,145 @@ +// :SHOW:1 +// :CATEGORY:NPC +// :NAME:Hypergrid Story One +// :AUTHOR:Ferd Frederix +// :KEYWORDS:Game, Collider +// :CREATED:2015-11-24 20:36:34 +// :EDITED:2015-11-24 19:36:34 +// :ID:1089 +// :NUM:1853 +// :REV:3.0 +// :WORLD:Opensim +// :DESCRIPTION: +// Sample NPC Card +// :CODE: + + + + serial + 0 + height + 1.6691286563873291 + wearables + + + + item + 0d67c6b9-90a6-4430-b124-5d3865253563 + asset + 501d4ff3-7669-4f2b-3de8-6077183b4314 + + + + + item + 3d56f323-beb2-4a4f-9b33-bedd01ef6d87 + asset + 9aa403be-7096-d638-d7b5-ab2739926664 + + + + + item + 3cc100b5-efb9-4357-84de-d53c763a9fe1 + asset + 9fe42dc9-32b9-863b-bbe3-2ed225aaa4dc + + + + + item + 657b06b0-ec8b-4f13-8fcc-8cab56e76145 + asset + 2ba00144-a1c5-4c60-b70b-54f888d57104 + + + + + + + + + + + + + + item + f0d962ee-8899-4853-b643-5f1e8414f386 + asset + 9942fa1d-a7e6-0dee-2345-9b2449a137fb + + + + + textures + + 46697265-7374-6f72-6d00-000000000000 + c228d1cf-4b5d-4ba8-84f4-899a0796aa97 + c228d1cf-4b5d-4ba8-84f4-899a0796aa97 + c228d1cf-4b5d-4ba8-84f4-899a0796aa97 + c228d1cf-4b5d-4ba8-84f4-899a0796aa97 + c228d1cf-4b5d-4ba8-84f4-899a0796aa97 + c228d1cf-4b5d-4ba8-84f4-899a0796aa97 + c228d1cf-4b5d-4ba8-84f4-899a0796aa97 + 837d151d-b151-4f28-bd64-d64315dcd787 + b74b9a80-b354-4a55-9a11-b04ddf9ad824 + 8e200c7a-6b01-43df-9196-1f9ad1df19b6 + 46a5b8b7-5f50-4799-8b0b-27f56cceb424 + c228d1cf-4b5d-4ba8-84f4-899a0796aa97 + c228d1cf-4b5d-4ba8-84f4-899a0796aa97 + c228d1cf-4b5d-4ba8-84f4-899a0796aa97 + c228d1cf-4b5d-4ba8-84f4-899a0796aa97 + c228d1cf-4b5d-4ba8-84f4-899a0796aa97 + c228d1cf-4b5d-4ba8-84f4-899a0796aa97 + c228d1cf-4b5d-4ba8-84f4-899a0796aa97 + c228d1cf-4b5d-4ba8-84f4-899a0796aa97 + c3632e30-d73b-4d09-a96e-2024652f9562 + + visualparams + ADNV/wB/Hpk/AP//P3UBAJyEIEwAP0S8LIhRR316nv/L/wB/AAB/AAAAAAAAAAB6QnWEia1/AAAArABKAAAAAAAAAABehFWHAF4Asq0AAImZbVV/fw84AGTY1szMzDMZWUzMAIIAAMSLf+NjVAB/cn9/f4mHe0SMf10vT4JJoD8AAAAAf38AAAAAfwCfAAAAAEdJSTVxa3xwQgBTAFR6ANbMxgAAaJN/4v/G////////////zAD//////////////wD//////wCCf/8ZZP////9UAAAAMwD///8AABkAGRczABkXMwAAGQAZFzMAABkAGRczABkXMwAZFzMAfw== + attachments + + + point + 17 + item + 06817d22-cc5c-4964-83a5-da7f9917c3d3 + asset + 07f0840d-9dc5-4011-8524-15918532ba04 + + + point + 29 + item + 4d02b390-6aea-4c35-aae2-02c36034340d + asset + 546bdf75-8b2a-4365-9724-ede209e5b694 + + + point + 2 + item + 17d0f3ce-76f7-429a-8d88-ecdf67014c1b + asset + 3ca39d60-c6c5-40be-817d-cc60ecb60cdb + + + point + 23 + item + 78959931-fcc8-40cf-a2b7-9bc694da2152 + asset + 895b3226-52df-4967-97cc-fc798799363f + + + point + 1 + item + 7bdd00a1-1936-4638-b363-9bfba9d9e203 + asset + 94e3709f-e90b-4395-b94f-9c9940cba790 + + + + diff --git a/Hypergrid Story One/HyperGrid Story One/Object/NPC00A Ruth Enpeace b/Hypergrid Story One/HyperGrid Story One/Object/NPC00A Ruth Enpeace new file mode 100644 index 00000000..4ab7ca9d --- /dev/null +++ b/Hypergrid Story One/HyperGrid Story One/Object/NPC00A Ruth Enpeace @@ -0,0 +1,111 @@ +// :SHOW:1 +// :CATEGORY:NPC +// :NAME:Hypergrid Story One +// :AUTHOR:Ferd Frederix +// :KEYWORDS:Game, Collider +// :CREATED:2015-11-24 20:36:34 +// :EDITED:2015-11-24 19:36:34 +// :ID:1089 +// :NUM:1855 +// :REV:3.0 +// :WORLD:Opensim +// :DESCRIPTION: +// Sample NPC Card +// :CODE: + + + + serial + 0 + height + 1.6885325908660889 + wearables + + + + item + 98bc81c9-7ff5-40df-924f-9d17eac6ede0 + asset + 66c41e39-38f9-f75a-024e-585989bfab73 + + + + + item + 162d13a2-f3bb-42d2-910f-330b8eb7f902 + asset + 77c41e39-38f9-f75a-024e-585989bbabbb + + + + + item + bd8d42c6-f40f-4906-879c-aae4cfe5f33d + asset + d342e6c0-b9d2-11dc-95ff-0800200c9a66 + + + + + item + 436a7dea-3484-42b3-812b-7d307b353d4f + asset + 4bb6fa4d-1cd2-498a-a84c-95c1a0e745a7 + + + + + item + 2afd4704-daae-4c78-8f7b-a79d7c1514d9 + asset + 00000000-38f9-1111-024e-222222111110 + + + + + item + f937460d-207b-4e82-b6c0-7789afc0aebc + asset + 00000000-38f9-1111-024e-222222111120 + + + + + + + + + + + + + textures + + 46697265-7374-6f72-6d00-000000000000 + c228d1cf-4b5d-4ba8-84f4-899a0796aa97 + c228d1cf-4b5d-4ba8-84f4-899a0796aa97 + c228d1cf-4b5d-4ba8-84f4-899a0796aa97 + c228d1cf-4b5d-4ba8-84f4-899a0796aa97 + c228d1cf-4b5d-4ba8-84f4-899a0796aa97 + c228d1cf-4b5d-4ba8-84f4-899a0796aa97 + c228d1cf-4b5d-4ba8-84f4-899a0796aa97 + 84711871-c585-4be0-8ec5-0d8998769be0 + cbd23982-104f-4719-9366-feda92ce5d12 + 47ec3452-f2f5-4d25-9b00-5b9a3867c1ab + 9de5ef41-40a3-4997-a006-153188aecca7 + c228d1cf-4b5d-4ba8-84f4-899a0796aa97 + c228d1cf-4b5d-4ba8-84f4-899a0796aa97 + c228d1cf-4b5d-4ba8-84f4-899a0796aa97 + c228d1cf-4b5d-4ba8-84f4-899a0796aa97 + c228d1cf-4b5d-4ba8-84f4-899a0796aa97 + c228d1cf-4b5d-4ba8-84f4-899a0796aa97 + c228d1cf-4b5d-4ba8-84f4-899a0796aa97 + c228d1cf-4b5d-4ba8-84f4-899a0796aa97 + 9dfd63c3-f823-49ef-90dd-313fbd040ab6 + + visualparams + IT1VFzp/P1U/KgBVPyRVX5k/IgA/bViEP4hRVWeIfwDLAAB/AAAAAAB/AAD/f3J/Yz9/jH9/AAAAvwBoAAAAAAAAAAAAkdiFAH8Af6oAAH9/bVV/fz9VKmTY1szMzDMZWUzMAH8AAJBVf4R/VQB/f39/f387f1V/f2ovT39/zAKNQgAAf38AAAAAfwCfAACyfyRVg39/f5lfAIxKG39/ANbMxgAAPx5/pdHGf3+ZzDMz////zAD//////////////wD//////wB/f/8ZZP////9UAAAAM4T///8AAAAAAAAzABkXMwAAAAAZFzMAAAAAGRczABkXMwAZFzMAfw== + attachments + + + diff --git a/Hypergrid Story One/HyperGrid Story One/Object/Namaka b/Hypergrid Story One/HyperGrid Story One/Object/Namaka new file mode 100644 index 00000000..0d5c6407 --- /dev/null +++ b/Hypergrid Story One/HyperGrid Story One/Object/Namaka @@ -0,0 +1,129 @@ +// :SHOW:1 +// :CATEGORY:NPC +// :NAME:Hypergrid Story One +// :AUTHOR:Ferd Frederix +// :KEYWORDS:Game, Collider +// :CREATED:2015-11-24 20:36:34 +// :EDITED:2015-11-24 19:36:34 +// :ID:1089 +// :NUM:1854 +// :REV:3.0 +// :WORLD:Opensim +// :DESCRIPTION: +// Sample NPC Card +// :CODE: + + + + serial + 0 + height + 1.7412686347961426 + wearables + + + + item + 44fc1681-6782-4053-87d8-435ebf69aca9 + asset + 1e6cec92-51dd-f06e-a96e-6e54fe7e38f5 + + + + + item + 90a7592b-c5a4-4e85-9bd5-f1243d9b7fd2 + asset + a87582b6-ad37-0a21-ac7b-2ffa5e75df77 + + + + + item + 726b9feb-15ea-4016-adf5-1de384c90529 + asset + 597937e4-c9a2-1cb6-a7aa-5875cc636ef9 + + + + + item + f72f2605-631c-4fed-8bba-060690dac8ee + asset + c7f3d2f2-8c72-9491-b9a5-541fc40f537c + + + + + + + + + + + + + + item + a46584db-c771-4753-9727-baa9106c6a38 + asset + 85d86e68-d863-2855-5a93-8159ea32f17a + + + + + textures + + 46697265-7374-6f72-6d00-000000000000 + c228d1cf-4b5d-4ba8-84f4-899a0796aa97 + c228d1cf-4b5d-4ba8-84f4-899a0796aa97 + c228d1cf-4b5d-4ba8-84f4-899a0796aa97 + c228d1cf-4b5d-4ba8-84f4-899a0796aa97 + c228d1cf-4b5d-4ba8-84f4-899a0796aa97 + c228d1cf-4b5d-4ba8-84f4-899a0796aa97 + c228d1cf-4b5d-4ba8-84f4-899a0796aa97 + 3a367d1c-bef1-6d43-7595-e88c1e3aadb3 + 3a367d1c-bef1-6d43-7595-e88c1e3aadb3 + 3a367d1c-bef1-6d43-7595-e88c1e3aadb3 + 3a367d1c-bef1-6d43-7595-e88c1e3aadb3 + c228d1cf-4b5d-4ba8-84f4-899a0796aa97 + c228d1cf-4b5d-4ba8-84f4-899a0796aa97 + c228d1cf-4b5d-4ba8-84f4-899a0796aa97 + c228d1cf-4b5d-4ba8-84f4-899a0796aa97 + c228d1cf-4b5d-4ba8-84f4-899a0796aa97 + c228d1cf-4b5d-4ba8-84f4-899a0796aa97 + c228d1cf-4b5d-4ba8-84f4-899a0796aa97 + c228d1cf-4b5d-4ba8-84f4-899a0796aa97 + 3a367d1c-bef1-6d43-7595-e88c1e3aadb3 + + visualparams + PzXWfzFEET1eRBG7cAAApXo6ZwAAfV6cP/8APYRWIwDL/wCRAAB/AAB/AAAAf3J/Yz9/jH9/AAAAvwAACgAAAAAAAAAAkdiFADoA2GgAAI5Rd1V/fwwZAGTY1szMzDMZWUzMAFQAAJ5OT9F/PQB/Qn9/f3A7LkJ/f2FRT14KWT8AAAAAf38AAAAAfwCfAACyfzNVg166SXp9AABMAIJmANbMxgAARCZo4v/G////////////zAD//////////////wD//////wB/Qv8ZZP////9UAAAAMwD///8AABkAGRczABkXMwAAGQAZFzMAABkAGRczABkXMwAZFzMAfw== + attachments + + + point + 9 + item + 7ff842f2-0ef7-4805-bb04-ec3a61152205 + asset + 3ef0463a-248c-43d8-bdc4-83bcf16e3edf + + + point + 9 + item + b497f9c0-b3ba-41a3-9c3c-f943bf6c5f08 + asset + 62c1f804-57e4-4af8-a43f-7b46bd442c75 + + + point + 30 + item + 4ebc9791-d7e4-4acf-8b1c-34ae471428a1 + asset + 8a6a4219-db7d-4171-a335-7c7bbd084af1 + + + + diff --git a/Hypergrid Story One/HyperGrid Story One/Object/Sequencer.lsl b/Hypergrid Story One/HyperGrid Story One/Object/Sequencer.lsl new file mode 100644 index 00000000..0c701f09 --- /dev/null +++ b/Hypergrid Story One/HyperGrid Story One/Object/Sequencer.lsl @@ -0,0 +1,395 @@ +// :SHOW:1 +// :CATEGORY:NPC +// :NAME:Hypergrid Story One +// :AUTHOR:Ferd Frederix +// :KEYWORDS:Game, Collider +// :CREATED:2015-11-24 20:36:34 +// :EDITED:2015-11-24 19:36:34 +// :ID:1089 +// :NUM:1856 +// :REV:3.0 +// :WORLD:Opensim +// :DESCRIPTION: +// Sample sequencer script for NPC animator. It sequences multiple NPCs in order thru a scenarios +// each 'things' entry is the NPC number, a (float) time to take between sending commands ( 0 is not allowed, but a small number is() +///and a @command that is sent to the NPC. + +// :CODE: + +// Rev: 2 fixes the Linux bug for collisions. +// +// Rev: 3 Neo Cortex: modified to become a standalone sequencer for Scene one +// +// Rev: 4 Aine Caoimhe: added support for custom appearance change slave scripts for finale sequence with 2 new commands: +// @dumpkeys must be called when all NPCs have been rezzed (ie when npcList has been populated with their keys) +// this relays the key data to the slave scripts which are numbered from 01 to 12 +// @slaveap=target|appearance_notecard_name where: +// target 1 = namaka (for final one) +// target 2 = mirror namakas (for final one) +// target 3 = all namaka (for all the other ones for her) +// target -1 = dylan (for the final one) +// target -2 = mirror dylans (for the final one) +// target -3 = all dylans (for all the ones for him) +// in both of the above commands you can use any non-zero npc number but it's ignored so I'd just use 1 each time +// it will respect a time delay value if you set one +// example: +// after all NPCs have been rezzed you need to call (but you only need to do it once): +// things += [1,0,"@dumpkeys"]; +// then to have all of the dylans change to appearance notecard "dylan scene 2" with no delay after it you would do: +// things += [1,0,"@slaveap=-3|dylan scene 2"]; + +string myStoryNotecard = "!Story"; + +integer debug = FALSE; +integer LSLEditor = FALSE; // set to to TRUE to working in LSLEditor, FALSE for in-world. +integer iTitleText = TRUE; // set to TRUE to see debug info in text above the controller + +integer SENSE = FALSE; // sensor for an avatar +float RATE = 5; // every 5 seconds +float RANGE = 2; // a very short range as this goes into a ring around the avatar +integer COLLIDE = TRUE; // if they collide, trigger the sequence + +integer isRunning = FALSE; // used to stop collision from firing twice + +list npcList = [ (key) "00000000-0000-0000-0000-000000000000", "empty npc0", // this one is ignored + (key) "00000000-0000-0000-0000-000000000000", "empty npc1", + (key) "00000000-0000-0000-0000-000000000000", "empty npc2", + (key) "00000000-0000-0000-0000-000000000000", "empty npc3", + (key) "00000000-0000-0000-0000-000000000000", "empty npc4", + (key) "00000000-0000-0000-0000-000000000000", "empty npc5", + (key) "00000000-0000-0000-0000-000000000000", "empty npc6", + (key) "00000000-0000-0000-0000-000000000000", "empty npc7", + (key) "00000000-0000-0000-0000-000000000000", "empty npc8", + (key) "00000000-0000-0000-0000-000000000000", "empty npc9", + (key) "00000000-0000-0000-0000-000000000000", "empty npc10", + (key) "00000000-0000-0000-0000-000000000000", "empty npc11", + (key) "00000000-0000-0000-0000-000000000000", "empty npc12"]; + +list myObjects = []; +list myObjectKeys = []; +integer myObjectsInitialized = FALSE; + + +string npcAction = ""; +string npcParams = ""; +key npcKey = ""; +integer NPCOptions = OS_NPC_CREATOR_OWNED; // only the owner of this box can control this NPC. + +key oldCollider; //remember who collided last +key newCollider; + +// CODE follows + +// For rezzing in +RezIn() +{ + things = [1,5,"@stop"]; + things += [2,5,"@stop"]; + things += [3,10,"@stop"]; + Speak(); +} + +DoIt() +{ + things = myStory; + isRunning = TRUE; + llVolumeDetect(FALSE); + llSleep(0.1); +} + + +list things ; +list myStory; + +// DEBUG(string) will chat a string or display it as hovertext if debug == TRUE +DEBUG(string str) { + if (debug && ! LSLEditor) + llOwnerSay( str); // Send the owner debug info + if (debug && LSLEditor) + llSay(0, str); // Send to the Console in LSLEDitor + if (iTitleText) { + llSetText(str,<1.0,1.0,1.0>,1.0); // show hovertext + + } +} + +list ListStridedUpdate(list dest, list src, integer start, integer end, integer stride) { + return llListReplaceList(dest, src, start * stride, ((end + 1) * stride) - 1 ); +} + +spawn(integer who,string msg) { + DEBUG("spawning " + msg); + list data = llParseString2List(msg, ["|"], []); + //DEBUG((string) data); + list npcName = llParseString2List(llList2String(data,0), [" "], []); + npcKey = osNpcCreate(llList2String(npcName, 0), llList2String(npcName, 1), llList2Vector(data,1), llList2String(data, 2), NPCOptions); + npcList=ListStridedUpdate(npcList,[npcKey,npcName],who,who,2); + //DEBUG("npcList " + (string) npcList); +} + +delete(integer who) { + DEBUG(llList2String(npcList,who*2+1) + " is removed"); + osNpcRemove (llList2Key(npcList,who*2)); + npcList=ListStridedUpdate(npcList,[(key) "00000000-0000-0000-0000-000000000000","removed NPC"],who,who,2); + //llSay(0, "npcList " + (string) npcList); +} + +say(integer who,string msg) { + DEBUG(llList2String(npcList,who*2+1) + " says " + msg); + osNpcSay(llList2Key(npcList,who*2),0, msg); +} + +sit(integer who,string msg) { + DEBUG(llList2String(npcList,who*2+1) + " sits on " + (string) msg); + // look up object named "msg" in myObjects, get key from myObjectsKeys, make "who" NPC sit on it + osNpcSit(llList2Key(npcList,who*2), llList2Key(myObjectKeys,llListFindList(myObjects,[msg])), OS_NPC_SIT_NOW); +} + +touchit(integer who,string msg) { + DEBUG(llList2String(npcList,who*2+1) + " touches " + (string) msg); + // look up object named "msg" in myObjects, get key from myObjectsKeys, make "who" NPC touch it + osNpcTouch(llList2Key(npcList,who*2),llList2Key(myObjectKeys,llListFindList(myObjects,[msg])), LINK_THIS); +} + +stand(integer who) { + DEBUG(llList2String(npcList,who*2+1) + " stands"); + osNpcStand(llList2Key(npcList,who*2)); + list anToStop=llGetAnimationList(llList2Key(npcList,who*2)); + integer stop=llGetListLength(anToStop); + while (--stop>-1) { osAvatarStopAnimation(llList2Key(npcList,who*2),llList2Key(anToStop,stop)); } + osNpcPlayAnimation(llList2Key(npcList,who*2),"Stand"); +} + +walk(integer who,vector pos) { + DEBUG(llList2String(npcList,who*2+1) + " walks to " + (string) pos); + osNpcMoveToTarget(llList2Key(npcList,who*2),pos,OS_NPC_NO_FLY); + osNpcPlayAnimation(llList2Key(npcList,who*2),"Walk"); +} + +animate(integer who, string ani) { + DEBUG(llList2String(npcList,who*2+1) + " plays " + (string) ani); + osNpcPlayAnimation(llList2Key(npcList,who*2),ani); +} + +appearance(integer who, string app) { + DEBUG(llList2String(npcList,who*2+1) + " changes appearance to " + (string) app); + osNpcLoadAppearance(llList2Key(npcList,who*2),app); +} + +rotate(integer who,float rot) { + DEBUG(llList2String(npcList,who*2+1) + " rotates " + (string) rot); + osNpcSetRot(llList2Key(npcList,who*2),llEuler2Rot(<0,0,rot> * DEG_TO_RAD)); +} + +// <<<< Added by Aine +dumpKeys() { + llMessageLinked(LINK_THIS,0,"NPC_UUID_LIST",llDumpList2String(llList2ListStrided(npcList,0,-1,2),"|")); +} +slaveap(string com) { + list parse=llParseString2List(com,["|"],[]); + llMessageLinked(LINK_THIS,llList2Integer(parse,0),"NPC_CHANGE_APPEARANCE",llList2String(parse,1)); +} +// <<<< End added by Aine +Speak() { + + integer npc = llList2Integer(things,0); + float time = llList2Float(things,1); + string msg = llList2String(things,2); + DEBUG("npc:" + (string) npc + " time:" + (string) time + " Msg:" + msg); + if (npc) { + things = llDeleteSubList(things,0,2); +// llMessageLinked(npc,0, msg,""); // <<<<< Aine: I don't see any need for having this here still since this script is handling it + list data = llParseString2List(msg, ["="], []); + npcAction = llToLower(llStringTrim(llList2String(data, 0), STRING_TRIM)); + DEBUG("Action:" + npcAction); + npcParams = llStringTrim(llList2String(data, 1), STRING_TRIM); + DEBUG("Params:" + npcParams); + if (npcAction == "@say") { + say(npc,npcParams); + } else if (npcAction == "@spawn") { + spawn(npc,npcParams); + } else if (npcAction == "@delete") { + delete(npc); + } else if (npcAction == "@sit") { + sit(npc,npcParams); + } else if (npcAction == "@touch") { + touchit(npc,npcParams); + } else if (npcAction == "@stand") { + stand(npc); + } else if (npcAction == "@walk") { + walk(npc,npcParams); + } else if (npcAction == "@animate") { + animate(npc,npcParams); + } else if (npcAction == "@appearance") { + appearance(npc,npcParams); + } else if (npcAction == "@rotate") { + rotate(npc,npcParams); + // >>>> Added by Aine + } else if (npcAction == "@dumpkeys") { + dumpKeys(); + } else if (npcAction == "@slaveap") { + slaveap(npcParams); + } // >>>> end of add by Aine + if (time > 0) { + llSetTimerEvent(time); + } else { + llOwnerSay("Whooops, time = 0!"); + DEBUG("npc:" + (string) npc + " time:" + (string) time + " Msg:" + msg); + llSetTimerEvent(0); + } + } else { + DEBUG("Done"); + isRunning = FALSE; + Reset(); + llSetTimerEvent(0); + if (SENSE) + llSensorRepeat("","",AGENT,RANGE,PI,RATE); + } +} + +Reset() +{ + llSetStatus(STATUS_PHANTOM, FALSE); // Rev 2 + llVolumeDetect(FALSE); + llSleep(0.1); + llVolumeDetect(TRUE); +} + +default +{ + state_entry() + { + DEBUG("entering state default"); + + if (! myObjectsInitialized){ + state initial; + } + + llSetText("",<1,1,1>,1.0); + Reset(); + llSetTimerEvent(0); + if (SENSE) + llSensorRepeat("","",AGENT,RANGE,PI,RATE); + } + + sensor(integer n) { + DEBUG("Bumped"); + if (! osIsNpc(llDetectedKey(0))) { + DEBUG("Sensed avatar"); + llSensorRemove(); + RezIn(); + } + } + + timer() + { + Speak(); + } + + collision_start(integer n) { + newCollider = llKey2Name(llDetectedKey(0)); + DEBUG("Collided with " + llKey2Name(llDetectedKey(0))); + + if (! osIsNpc(llDetectedKey(0))) + { + if ( newCollider != oldCollider) { + DoIt(); + oldCollider = llKey2Name(llDetectedKey(0)); + Speak(); + } + } + } + + collision_end(integer num_detected) { + DEBUG(llDetectedName(0) + " has stopped colliding with me!"); + if (! isRunning && (newCollider == oldCollider) ) { + oldCollider = NULL_KEY; + } + } + + touch_start(integer n) { + DEBUG("Touched by " + llKey2Name(llDetectedKey(0))); + if (isRunning) { + //insert restart code here + } + if (! osIsNpc(llDetectedKey(0))) { + DoIt(); + Speak(); + } + } + + + + on_rez(integer p) + { + llResetScript(); + } + + changed(integer what) + { + if (what & CHANGED_REGION_START|CHANGED_INVENTORY) + { + llResetScript(); + } + } +} + +state initial +{ + state_entry() + { + DEBUG("entering state initial"); + myStory = []; + list myStoryLines = llParseString2List(osGetNotecard(myStoryNotecard), ["\n"], []); + DEBUG("text = " + (string) myStoryLines); + DEBUG("lines = " + (string) llGetListLength(myStoryLines)); + integer i; + for (i=0; i + + diff --git a/Hypergrid Story Three/EndGame/End Game.png b/Hypergrid Story Three/EndGame/End Game.png new file mode 100644 index 00000000..c491f321 Binary files /dev/null and b/Hypergrid Story Three/EndGame/End Game.png differ diff --git a/Hypergrid Story Three/EndGame/EndGame.prj b/Hypergrid Story Three/EndGame/EndGame.prj new file mode 100644 index 00000000..55d032e4 --- /dev/null +++ b/Hypergrid Story Three/EndGame/EndGame.prj @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + diff --git a/Hypergrid Story Three/EndGame/Mortar and Pestel/End Game Photo.png b/Hypergrid Story Three/EndGame/Mortar and Pestel/End Game Photo.png new file mode 100644 index 00000000..c491f321 Binary files /dev/null and b/Hypergrid Story Three/EndGame/Mortar and Pestel/End Game Photo.png differ diff --git a/Hypergrid Story Three/EndGame/Mortar and Pestel/Game prim script 5.5.lsl b/Hypergrid Story Three/EndGame/Mortar and Pestel/Game prim script 5.5.lsl new file mode 100644 index 00000000..b4884def --- /dev/null +++ b/Hypergrid Story Three/EndGame/Mortar and Pestel/Game prim script 5.5.lsl @@ -0,0 +1,201 @@ +// :SHOW:1 +// :CATEGORY:Gaming +// :NAME:Game Prim Script for5 Game sequence Web site. +// :AUTHOR:Ferd Frederix +// :KEYWORDS:Game, conteoller +// :REV:5.5 +// :WORLD:OpenSim, Second life +// :DESCRIPTION: +// A chat system and controller based on a web site notecards replacement system +// :CODE: + +// fred@mitsi.com +// Game Prim script + +// v 5.5 NPC uses the server to send text to a NPC +// LOGIN URL = http://www.outworldz.com/game + +string Version = "5.5"; // helps spot old scripts at the web site + +// fiddly bits +integer debug = FALSE; // if TRUE, should be in Spanish + +// SET ONE OR MORE OF THESE +integer COLLIDE = FALSE; // set to TRUE if you want them to collide with the prim +integer TOUCH = TRUE; // set to TRUE if they can touch it to trigger this +integer SIT = FALSE; // set to TRUE if this goes into a SEAT +integer CollideObjects = FALSE; // set to TRUE to allow things to bump this prim (unlikely) + +integer SPEACHOPTION = 3; // Set to 0 for chat, 1 for IM +vector OFFSET = <0,0,0>; // offset to rez items with channel = 2. <0,0,2> will rez the object 2 meters above the prim. Max = 10 meters. + +// not fiddly bits +list stack; // place to store HTTP traffic +integer HTTPSTRIDE = 2; // 3 items in a stack = 2 here as it starts option 0 + + + +Ping(string AvatarName,key AvatarKey) +{ + string Language = llEscapeURL(llGetAgentLanguage(AvatarKey)); + + if (debug) llSay(0,"Language: " + Language); + + string Me = llEscapeURL(llGetObjectName()); + string Them = llEscapeURL(AvatarKey); + string Theirname = llEscapeURL(AvatarName); + + string url = "http://www.outworldz.com/cgi/llgame.plx" + + "?PrimName=" + (string) Me + + "&Ver=" + Version + + "&AvatarKey=" + (string)Them + + "&Language=" + Language; + + if (AvatarKey != NULL_KEY) + url += "&Avatar=" + (string)Theirname ; + if (debug ) llOwnerSay(url); + + stack += AvatarKey; + stack += Theirname; + stack += llHTTPRequest(url, [], ""); +} + + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// * The real start of the universe. +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +default +{ + state_entry() + { + llSetTimerEvent(1); + + if (COLLIDE == TRUE) + { + llSetAlpha(0,ALL_SIDES); + llVolumeDetect(FALSE); + llVolumeDetect(TRUE); + } + else { + llVolumeDetect(FALSE); + llSetAlpha(1,ALL_SIDES); + } + } + + timer() + { + llSetTimerEvent(3600); // hourly + Ping("Ping", NULL_KEY); + } + + // save the Web server data for this prim + http_response(key request_id, integer status, list metadata, string body) + { + if (debug) llSay(0,"Response Body:" + body); + integer where = llListFindList(stack, [request_id]) ; + if (where > -1) + { + stack = llDeleteSubList(stack,where-HTTPSTRIDE,where); // key, name, request ID + + if (llGetListLength(stack) > 10 * HTTPSTRIDE ) // 10 at a time, the rest can go phooey + stack = llDeleteSubList( stack, 0, HTTPSTRIDE ); // kill off 1st element, is old + + // $status|$display|$sound|$Channel|$ChannelText|$AvatarKey + + + list my_detail = llParseString2List( body,["|"],[]); + if (debug) llSay(0,"Dump:" + llDumpList2String(my_detail,",")); + + string statusType = llList2String(my_detail,0); + string storyText = llList2String(my_detail,1); + string SoundUUID = llList2String(my_detail,2); + integer ActionChannel = (integer) llList2String(my_detail,3); + string ActionText = llList2String(my_detail,4); + string AvatarKey = llList2String(my_detail,5); + + + if (statusType == "ACK" && llStringLength(AvatarKey) > 1 ) + { + // play sound, if any + if (llStringLength(SoundUUID) > 1 ) { + if (debug) llSay(0,"Playing sound " + SoundUUID); + llTriggerSound(SoundUUID,1.0); + } + + // They had the requirements, or there was no dependency, and there was text in the Channel + if (ActionChannel && llStringLength(storyText) > 1) + { + if (debug) llSay(0,"Sending Region command " + storyText + " on channel " + (string) ActionChannel); + llRegionSay(ActionChannel,storyText); // pass it to other scripts. + } + } + } + } + + collision_start(integer total_number) + { + + if (COLLIDE) { + + if (debug) + llSay(0," prim collided "); + + integer i; + for (; i < total_number; i++) + { + if (debug) llSay(0,"Collided by " + (string) llDetectedKey(i)); + // if (! osIsNpc(llDetectedKey(i))) + // { + if (CollideObjects) + { + list reject = llGetObjectDetails(llDetectedKey(i), [OBJECT_CREATOR]); + key x = llList2Key(reject,0); + if (x == NULL_KEY) + Ping(llDetectedName(i), llDetectedKey(i)); + } else { + Ping(llDetectedName(i), llDetectedKey(i)); + } + // } + } + } + } + touch_start(integer total_number) + { + + if (TOUCH) { + + if (debug) + llSay(0," prim touched "); + + integer i; + for (; i < total_number; i++) + { + if (debug) llSay(0,"Touched by " + (string) llDetectedKey(i)); + Ping(llDetectedName(i), llDetectedKey(i)); + } + } + } + + changed(integer mask) + { + if (mask & CHANGED_INVENTORY) + { + llResetScript(); + } + if ((mask & CHANGED_LINK) && SIT) + { + key avatarKey = llAvatarOnSitTarget(); // see if they sat down + if (avatarKey != NULL_KEY) + { + Ping(llKey2Name(avatarKey), avatarKey); + } + } + if (mask & CHANGED_REGION_START) + { + llResetScript(); + } + } + + +} \ No newline at end of file diff --git a/Hypergrid Story Three/EndGame/Mortar and Pestel/Modified Hypergate to level four.lsl b/Hypergrid Story Three/EndGame/Mortar and Pestel/Modified Hypergate to level four.lsl new file mode 100644 index 00000000..0efd56f7 --- /dev/null +++ b/Hypergrid Story Three/EndGame/Mortar and Pestel/Modified Hypergate to level four.lsl @@ -0,0 +1,92 @@ +// :SHOW:1 +// :CATEGORY:Hypergate +// :NAME:Hypergate when you fall in +// :AUTHOR:Ferd Frederix +// :KEYWORDS:Game, conteoller +// :REV:5.5 +// :WORLD:OpenSim, Second life +// :DESCRIPTION: +// requires oSTeleport functions to be enabled +// :CODE: + +//hop://www.outworldz.com:9000/Hypergrid Story Five/191/160/22 + +// Hypergate script by Ferd Frederix + +vector LandingPoint ; // from the desscription +string DestName = "Hypergrid Story Five"; // Where you want the Avatar to arrive at + +vector LookAt = <0.0,0.0,-1.0>; // which way you want them facing. +list LastFewAgents; +string TargetAddress ; // In-Grid Teleport (Region Na + +PerformTeleport( key AgentToTP ) +{ + integer CurrentTime = llGetUnixTime(); + integer AgentIndex = llListFindList( LastFewAgents, [ AgentToTP ] ); // Is the agent we're teleporting already in the list? + if (AgentIndex != -1) // If yes, check to make sure it's been > 5 seconds + { + integer PreviousTime = llList2Integer( LastFewAgents, AgentIndex+1 ); // Get the last time they were teleported + if (PreviousTime >= (CurrentTime - 5)) return; // Less than five seconds ago? Exit without teleporting + LastFewAgents = llDeleteSubList( LastFewAgents, AgentIndex, AgentIndex+1); // Delete the agent from the list + } + LastFewAgents += [ AgentToTP, CurrentTime ]; // Add the agent and current time to the list + osTeleportAgent( AgentToTP, TargetAddress, LandingPoint, LookAt ); // Teleport agent to their target + LastFewAgents = llDeleteSubList( LastFewAgents, 20, 100); // Delete the agent from the list +} + +RESET_VOL_DET() +{ + llVolumeDetect( 0 ); // turn off volume detection + llVolumeDetect( 1 ); // turn it back on again +} + + +default +{ + on_rez(integer int) + { + + llResetScript(); + } + state_entry() + { + llSetTextureAnim(FALSE, ALL_SIDES, 0, 0, 0.0, 0.0, 1.0); + LandingPoint = (vector) llGetObjectDesc(); + TargetAddress = DestName; + RESET_VOL_DET(); + } + + link_message(integer n, integer v,string text, key id ) // total_number is the number of avatars detected. + { + // llOwnerSay("link:" + (string) v); + if (v == -1 ) { + // llOwnerSay("Teleport"); + llSetTimerEvent(30); + } + + + } + + timer() + { + llSay(0, "You follow the cyberbeings into another dimension..... to " + DestName); + TargetAddress = DestName; + + + // PerformTeleport( llDetectedKey( 0 )); + llSetTimerEvent(0); + } + changed(integer change) // something changed, take action + { + if(change & CHANGED_OWNER) + { + llOwnerSay("Owner Changed, Resetting Script"); + llResetScript(); + } + else if (change & CHANGED_REGION_START) // that bit is set during a region restart + { + llResetScript(); + } + } +} \ No newline at end of file diff --git a/Hypergrid Story Three/EndGame/Mortar and Pestel/readme.txt b/Hypergrid Story Three/EndGame/Mortar and Pestel/readme.txt new file mode 100644 index 00000000..c8c42f61 --- /dev/null +++ b/Hypergrid Story Three/EndGame/Mortar and Pestel/readme.txt @@ -0,0 +1,16 @@ +// :SHOW:1 +// :CATEGORY:Gaming +// :NAME:Game Prim notes +// :AUTHOR:Ferd Frederix +// :KEYWORDS:Game, conteoller +// :REV:5.5 +// :WORLD:OpenSim, Second life +// :DESCRIPTION: +// A chat system and controller based on a web site notecards replacement system +// :CODE: + +A touch-sequence game set up by the scripts in the www.Outworldz.com/game web site. + +The "Game prim script" has been modified to make the NPC say the things instead of an IM + +These scripts in this folder are common to all game prims. Almost all the setup is done by naming each prim differently and editing the web site \ No newline at end of file diff --git a/Hypergrid Story Three/EndGame/Sensor/NotASensor.lsl b/Hypergrid Story Three/EndGame/Sensor/NotASensor.lsl new file mode 100644 index 00000000..0b6fd610 --- /dev/null +++ b/Hypergrid Story Three/EndGame/Sensor/NotASensor.lsl @@ -0,0 +1,82 @@ +// :SHOW:1 +// :CATEGORY:Gaming +// :NAME:Does not use a Sensor to delete or start a NPC +// :AUTHOR:Ferd Frederix +// :KEYWORDS:Game, Sensor +// :REV:1.0 +// :WORLD:OpenSim +// :DESCRIPTION: +// A part of a controller set for NPC's +// When an avatar (the owner is not counted) gets close to this, it sends a @go command to the controller. This will make it continue from an @stop. +// when no one is around, it sends a @stop to remove the NPC. It sends these once per avatar presence. +// Does not use a laggy sensor. Will not detect the owner. +// :CODE: + + +integer debug = FALSE; // set to TRUE or FALSE for debug chat on various actions +integer iTitleText = FALSE; // set to TRUE or FALSE for debug hovertext above the prim + +float distance = 75; // keep this distance short - in my case, we are on a broad plain and we need to notice the avatar from a long distance, but not often. +float time = 10; // keep this big - the smaller, the laggier it gets + +// toggle if a person is there or not so we can detect the edge condition +integer isPersonDetected = FALSE; + +// send a link message once, when no one is detected +nobodyHome() { + if (isPersonDetected) { + llMessageLinked(LINK_SET,1,"@stop",""); + } + isPersonDetected = FALSE; +} + +// send a link message once, when someone is detected +somebodyHome() { + if (! isPersonDetected) + llMessageLinked(LINK_SET,1,"@go",""); + + isPersonDetected = TRUE; +} + +default { + state_entry(){ + llSetTimerEvent(time); + } + + timer(){ + list avatarsInRegion = osGetAvatarList(); + // A srided list of the UUID, position, and name of each avatar in the region except the owner. + + integer i; + integer present; + for (i = 0; i < llGetListLength(avatarsInRegion)/3;i++) + { + key avatarUUID = llList2Key(avatarsInRegion,i); + if (! osIsNpc(avatarUUID)) { + + vector avatarLoc = llList2Vector(avatarsInRegion,i+1); // location + float dist = llVecDist(llGetPos(),avatarLoc); + if (dist < distance) + present++; + } + } + + if (present) + somebodyHome(); + else + nobodyHome(); + + } + + on_rez(integer p) { + llResetScript(); + } + + + changed(integer what){ + if (what & CHANGED_REGION_START){ + llResetScript(); + } + } + +} \ No newline at end of file diff --git a/Hypergrid Story Three/EndGame/Sensor/Sensor.lsl b/Hypergrid Story Three/EndGame/Sensor/Sensor.lsl new file mode 100644 index 00000000..5614b34d --- /dev/null +++ b/Hypergrid Story Three/EndGame/Sensor/Sensor.lsl @@ -0,0 +1,57 @@ +// :SHOW:1 +// :CATEGORY:Gaming +// :NAME:Sensor to delete or start a NPC +// :AUTHOR:Ferd Frederix +// :KEYWORDS:Game, Sensor +// :REV:1.0 +// :WORLD:OpenSim +// :DESCRIPTION: +// A part of a controller set for NPC's +// When an avatar gets near this (75 meters, a looong way, it sends a @pause command to the controller. This will make it continue from an @stop. +// when no one is araound, it sends a @delete to remove the NPC. It sends these once per avatar presence. + +// :CODE: + +float distance = 75; // keep this distance short - in my case, we are on a broad plain and we need to notice the avatar from a long distance, but not often. +float time = 30; // keep this long. + +// toggle if a person is there or not so we can detect the edge condition +integer isperson = FALSE; + +default +{ + state_entry() + { + llSensorRepeat("","",AGENT,distance,PI, time); + } + + sensor(integer n) + { + if (! isperson) + llMessageLinked(LINK_SET,1,"@pause=1",""); + + isperson = TRUE; + } + + no_sensor() + { + if (isperson) + llMessageLinked(LINK_SET,1,"@delete",""); + + isperson = FALSE; + } + + on_rez(integer p) { + llResetScript(); + } + + + changed(integer what) + { + if (what & CHANGED_REGION_START) + { + llResetScript(); + } + } + +} \ No newline at end of file diff --git a/Hypergrid Story Three/Fairy Helpers/Collision Prim/Collider.lsl b/Hypergrid Story Three/Fairy Helpers/Collision Prim/Collider.lsl new file mode 100644 index 00000000..9a4ba0f4 --- /dev/null +++ b/Hypergrid Story Three/Fairy Helpers/Collision Prim/Collider.lsl @@ -0,0 +1,58 @@ +// :SHOW:1 +// :CATEGORY:Gaming +// :NAME:Collider for All in One NPC Controller +// :AUTHOR:Ferd Frederix +// :KEYWORDS:Game, Collider +// :REV:2.0 +// :WORLD:OpenSim +// :DESCRIPTION: +// Triggers the NPC controller to play the Greet notecard when collided. +// :CODE: + + +Reset() +{ + llSetStatus(STATUS_PHANTOM, FALSE); + llVolumeDetect(FALSE); + llSleep(0.1); + llVolumeDetect(TRUE); +} + + +default +{ + state_entry() + { + Reset(); + llSetTimerEvent(3600); + } + + collision_start(integer n) + { + if (osIsNpc(llDetectedKey(0))) + { + return; + } + + llMessageLinked(LINK_SET,0,"@notecard=Greet",""); + + } + + on_rez(integer p) + { + llResetScript(); + } + + timer() + { + Reset(); + } + + changed(integer what) + { + if (what & CHANGED_REGION_START) + { + llResetScript(); + } + } +} \ No newline at end of file diff --git a/Hypergrid Story Three/Fairy Helpers/Fairy Helpers.prj b/Hypergrid Story Three/Fairy Helpers/Fairy Helpers.prj new file mode 100644 index 00000000..bbb6cf86 --- /dev/null +++ b/Hypergrid Story Three/Fairy Helpers/Fairy Helpers.prj @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + diff --git a/Hypergrid Story Three/Fairy Helpers/NPC Root Prim/!Appearance b/Hypergrid Story Three/Fairy Helpers/NPC Root Prim/!Appearance new file mode 100644 index 00000000..baad4cc5 --- /dev/null +++ b/Hypergrid Story Three/Fairy Helpers/NPC Root Prim/!Appearance @@ -0,0 +1,10 @@ +// :SHOW:1 +// :CATEGORY:Gaming +// :NAME:Placeholder +// :AUTHOR:Ferd Frederix +// :KEYWORDS:Game, conteoller +// :REV:1.0 +// :WORLD:OpenSim +// :DESCRIPTION: +// A Placeholder for a notecard +// :CODE: \ No newline at end of file diff --git a/Hypergrid Story Three/Fairy Helpers/NPC Root Prim/!Path b/Hypergrid Story Three/Fairy Helpers/NPC Root Prim/!Path new file mode 100644 index 00000000..a740a2d8 --- /dev/null +++ b/Hypergrid Story Three/Fairy Helpers/NPC Root Prim/!Path @@ -0,0 +1,15 @@ +// :SHOW:1 +// :CATEGORY:Gaming +// :NAME:A fairy notecard +// :AUTHOR:Ferd Frederix +// :KEYWORDS:Game, conteoller +// :REV:1.0 +// :WORLD:OpenSim +// :DESCRIPTION: +// A notecard to hold fairy info. They rez in, stop and await more commands +// :CODE: + + +@spawn=Dune Twinkleflash|<204.20844, 84.20403, 26.89724> +@pause=1 +@stop diff --git a/Hypergrid Story Three/Fairy Helpers/NPC Root Prim/Greet b/Hypergrid Story Three/Fairy Helpers/NPC Root Prim/Greet new file mode 100644 index 00000000..37e15ef9 --- /dev/null +++ b/Hypergrid Story Three/Fairy Helpers/NPC Root Prim/Greet @@ -0,0 +1,20 @@ +// :SHOW:1 +// :CATEGORY:Gaming +// :NAME:Greet Notecard +// :AUTHOR:Ferd Frederix +// :KEYWORDS:Game, conteoller +// :REV:1.0 +// :WORLD:OpenSim +// :DESCRIPTION: +// A greeter notecard +// :CODE: + +This notecard plays in an endless loop when someone is sent back by falling into the water, or by walking down the path. +The collider issues a @notecard=Greet each bump + +@walk=<218.33400, 84.96158, 21.94824> +@animate=avatar_type|4 +@say=Hello! Need some help? Watch for the rat. They are smarter than most of the people who go past me. +@pause=3 +@walk=<204.20844, 84.20403, 26.89724> +@stop diff --git a/Hypergrid Story Three/Fairy Helpers/NPC Root Prim/NPC Controller - All In One Rev 4.0.lsl b/Hypergrid Story Three/Fairy Helpers/NPC Root Prim/NPC Controller - All In One Rev 4.0.lsl new file mode 100644 index 00000000..b4c342a3 --- /dev/null +++ b/Hypergrid Story Three/Fairy Helpers/NPC Root Prim/NPC Controller - All In One Rev 4.0.lsl @@ -0,0 +1,1594 @@ +// :SHOW:1 +// :CATEGORY:NPC +// :NAME:All In One NPC Recorder and Player +// :AUTHOR:Ferd Frederix +// :KEYWORDS:NPC, Puppeteer +// :CREATED:2013-09-08 18:27:47 +// :REV:4.0 +// :WORLD:OpenSim +// :DESCRIPTION: +// All in one NPC recorder player. +// Supports both absolute and relative paths and many new commands +// Add animations named "Fly, Walk, Stand and Run" +// Click Prim to use. +// Should be worn as a HUD to record. +// Put it on the ground and click Sensor or Start NPC when done. +// :CODE: +// This is Rev 3.7 08/11/2015 which added @attach + +// Revision History +// Rev 1.1 10-2-2014 @Sit did not work. Minor tweaks to casting for lslEditor +// Rev 1.2 10-14-2014 @ sit had wrong type. +// Rev 1.3 relative movement fixed for @fly +// Rev 1.4 4-3-2014 allow anyone to use this, non owners and non group members can only start and stop. +// Rev 1.5 5-17-2014 set sensor to auto start on reboot of sim +// Rev 1.6 5-24-2014 move menu so you can get it by touching, removed many of the KeyValues to RAM for efficiency +// Rev 1.7 CHANGED_REGION_START, not CHANGED_REGION_START (Opensim difference) +// Rev 1.8 tuned up Kill NPC, added more flexible upgrader +// Rev 1.9 Better script injection by link message// Rev 2.0 Added osSetSpeed so you can speed up or slow down an NPC. +// Rev 2.1 No laggy sensor used exept to sit on stuff +// Rev 2.2 Various sensor fixes +// Rev 2.3 Sets No Sensor in menu, must be started by hand +// Rev 2.4 - reserved for patches to 2.3 if needed +// Rev 3.0 Refactor out into subs, not states to make command injection easier +// New command: @appearance=Notecardname so you can switch to a new notecard on the fly +// New command: @speed=1.0 which slows up ( < 1 ) or speeds up ( > 1) +// Rev 3.1 Commands are not interruptible by Link Message +// Rev 3.2 Sensor patches for consistency in removing the NPC +// Rev 3.3 Added Touch command by Neo.Cortex@hbase42/hopto/org:8002 +// Added Menu 3 for notecard and appearance commands +// Rev 3.4 animation timer cannot be zero or it shuts off timer tweaked +// solves the NPC starting up when no sensor is set. +// Rev 3.5 fixes saving to !Path notecard +// Rev 3.6 08-11-2015 @delete acts like @stop. TYjhe NPC now rezzes after an @go back in where it was deleted +// Rev 3.7 08-11-2015 @attach command added to load an attachment from the inventory to the NPC +// Rev 3.8 08-17-2015 process queued commands one at a time without calling ProcessNPCLine on link message +//*******************************************************************// + +// Instructions on how to use this is at http://www.outworldz.com/opensim/posts/NPC/ +// This is an OpenSim-only script. +// Author: Ferd Frederix aka Fred Beckhusen - fred@mitsi.com + +//////////////////////////////////////////////////////////////////////////////////////////// +// Original code was Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // +/////////////////////////////////////////////////////////////////////////////////////////// +// Please see: http://www.gnu.org/licenses/gpl.html for legal details, // +// rights of fair usage, the disclaimer and warranty conditions. // +/////////////////////////////////////////////////////////////////////////////////////////// +// The original NPC controller was from http://was.fm/opensim:npc +// Extensive additions and bug fixes by Fred Beckhusem, aka Ferd Frederix, fred@mitsi.com +// llSensor had two params swapped +// @Wander would wander where it had rezzed, not where it was. +// There was no 'no_sensor' event in sit, so if a @sit failed, the NPC got stuck +// The animation and walks always stopped old, then started new. It should be start new, then stop old so the default stand would be suppressed. +// New code: +// Merged with new Route recorder and notecard writer +// If the NPC failed to reach a destination it never moved on. Added WAIT global to tune this +// Exposed many tunable variables and ported the code to LSLEditor. +// Added floating point to times in notecard. + +// Added @sound, @randsound, @whisper, @shout, and @cmd controls. +// +// notecards integers are not floats for better control +// +// Link Messages may be used to perform external control by injecting @commands into the stream of actions +// Example: +// To chat something, such as with a chat robot +// llMessageLinked(LINK_SET,0,"@npc_say=Hello",""); + +// This script assumes that NPCs and OSSl scripting is enabled in the OpenSim configuration. +// In order to enable them, the following changes must be made in the OpenSim.ini configuration file: +// +// ; Turn on OSSL +// AllowOSFunctions = true +// OSFunctionThreatLevel = Severe + +//[NPC] +// ;# {Enabled} {} {Enable Non Player Character (NPC) facilities} {true false} +// Enabled = true +// +// and then the server has to be restarted. + + +// Commands: All commands begin with an @ sign. All other lines are ignored +// @commands may have optional parameters. The syntax is always: +// @cmd=parm1|parm2 +// NaN in the table below meand Not a Number. This means there is no parameter + +//Command First Parameter Second Parameter Description +//@spawn name location (vector) Rezzes an NPC with name at a location. +//@appearance NoteCardName NaN switch the NPC appearance to a new notecard +//@walk destination (vector) NaN Makes the NPC walk to destination. +//@fly destination (vector) NaN Makes the NPC fly to destination. +//@land destination (vector) NaN Makes the NPC land at destination. +//@say string NaN Makes the NPC speak a phrase. +//@whisper string NaN Makes the NPC whisper a phrase. +//@shout string NaN Makes the NPC shout a phrase. +//@pause seconds (float) NaN Makes the NPC wait for a multiple of seconds. +//@wander radius (float) cycles (integer) Makes the NPC wander in radius, for cycles seconds. +//@delete NaN NaN Removes the NPC. Requires a link message to continue +//@goto label (string) NaN Jump to the label label in the script. +//@animate animation (string) time (float) Makes the NPC trigger the animation animation for time seconds. +//@sound sound_name NaN plays a sound from inventory +//@randsound NaN NaN Plays a random sound from inventory +//@rotate degrees (float) NaN Rotate the NPC degrees around the Z axis. +//@sit primitive name NaN Sit on a primitive with a given name. +//@touch primitive name NaN Touch on a primitive with a given name. +//@stand NaN NaN If sitting on a primitive, stand up. +//@cmd channel (integer) string Says string on channel, for controlling external gadgets +//@stop NaN NaN Halts the NPC script indefinitely. Can be started with a link message +//@go NaN NaN Continues on next notecard line, for use in link messages +//@speed speed (float) NaN from 0 to N, where 1.0 ius a normal speed of an avatar. 0.2 is a turtle. +//@notecard notename (string) NaN load a new Path notecard +//@attach InventoryName attachmentPoint load an attachment from the inventory to the NPC onto point + +// Constant attachmentPoint Comment +// ATTACH_CHEST 1 chest/sternum +// ATTACH_HEAD 2 head +// ATTACH_LSHOULDER 3 left shoulder +// ATTACH_RSHOULDER 4 right shoulder +// ATTACH_LHAND 5 left hand +// ATTACH_RHAND 6 right hand +// ATTACH_LFOOT 7 left foot +// ATTACH_RFOOT 8 right foot +// ATTACH_BACK 9 back +// ATTACH_PELVIS 10 pelvis +// ATTACH_MOUTH 11 mouth +// ATTACH_CHIN 12 chin +// ATTACH_LEAR 13 left ear +// ATTACH_REAR 14 right ear +// ATTACH_LEYE 15 left eye +// ATTACH_REYE 16 right eye +// ATTACH_NOSE 17 nose +// ATTACH_RUARM 18 right upper arm +// ATTACH_RLARM 19 right lower arm +// ATTACH_LUARM 20 left upper arm +// ATTACH_LLARM 21 left lower arm +// ATTACH_RHIP 22 right hip +// ATTACH_RULEG 23 right upper leg +// ATTACH_RLLEG 24 right lower leg +// ATTACH_LHIP 25 left hip +// ATTACH_LULEG 26 left upper leg +// ATTACH_LLLEG 27 left lower leg +// ATTACH_BELLY 28 belly/stomach/tummy +// ATTACH_LEFT_PEC 29 left pectoral +// ATTACH_RIGHT_PEC 30 right pectoral +// ATTACH_HUD_CENTER_2 31 HUD Center 2 +// ATTACH_HUD_TOP_RIGHT 32 HUD Top Right +// ATTACH_HUD_TOP_CENTER33 HUD Top +// ATTACH_HUD_TOP_LEFT 34 HUD Top Left +// ATTACH_HUD_CENTER_1 35 HUD Center +// ATTACH_HUD_BOTTOM_LEFT 36 HUD Bottom Left +// ATTACH_HUD_BOTTOM 37 HUD Bottom +// ATTACH_HUD_BOTTOM_RIGHT 38 HUD Bottom Right +// ATTACH_NECK 39 neck +// ATTACH_AVATAR_CENTER 40 avatar center/root + + + +////////////////////////////////////////////////////////// +// DEBUG // +////////////////////////////////////////////////////////// +integer debug = FALSE; // set to TRUE or FALSE for debug chat on various actions +integer Editor = FALSE; // set to to TRUE to working in LSLEditor, FALSE for in-world. + // you must also include the NPC commands found in the other script since LSLEditor does not support OpenSim +integer iTitleText = TRUE; // set to TRUE to see debug info in text above the controller + +////////////////////////////////////////////////////////// +// TUNABLE CONFIGURATION // +////////////////////////////////////////////////////////// +float TIMER = 2; // how often the system checks the distance traveled. Fastest you can go is 0.5 seconds +float QUICK = 1; // when we need to move to the next state, we use a QUICK timer +string Appearance = "!Appearance"; // The name of the recorded Appearance notecard +string Notecard = "!Path"; // The name of the recorded routes +integer allowUsers = FALSE; // If true, any user can get a Start NPC and Stop NPC menu. Only groups and owners can get all commands if TRUE, or FALSE +float MAXDIST = 2.0; // how close a NPC has to get to a dest pos to continue to next state. Do not lower this too much, as it may miss the target +integer WANDERRAND = TRUE; // set to TRUE and they will pause during wanders a random number of seconds +float WANDERTIME = 3.0; // how long they stand after each @wander,if WANDERRAND is FALSE. If WANDERRAND is TRUE, this is the max time +integer WAIT = 30; // wait for this number of seconds for the NPC to reach a destination (for safety). If it fails to reach a target, it will move on after this time. +float RANGE = 50; // 1 to N meters - anyone this close to the controller will start NPCS if Sensor button is clicked +float REZTIME = 2.0; // wait this long for NPC to rez in, then start the process +string STAND = "Stand"; // the name of the default Stand animation +string WALK = "Walk"; // the name of the default Walk animation +string FLY = "Fly"; // the name of the default Fly animation +string RUN = "Run"; // the name of the default Run animation +string LAND = "Land"; // the name of the default land animation ( for birds only) +float OffsetZ = 0.5; // appear 0.5 meter above ground, this is added to all destinations to keep them from sinking in. +float SPEEDMULT =0.5; // 1.0 = regular avatar speed. Smaller numbers slow down walks. Large numbers speed them up. +integer FLIGHT = 299; // For controlling wings. A channel that is shouted at when flight starts and ends. "flying" or "landing" + +// DESCRIPTIONS FIELDS HAVE TO SURVIVE A RESET +// These vars are stored by saving them with KeyValueSet +// "pr" is a 0 if it is set for Owner Only, 1 for Group control +// "se" is "on" if Started +// "co" = "R" or "A" for relative or absolute addressing mode +// "key" = NPC key + +// These Globals used to be stored in description. Moved to RAM in V1.6 +float RAMPause; // @pause param +float RAMwd ; // @wander distance +integer RAMwc; // @wander count +float RAMrot; // @rotate +string RAMsit; // @sit primname +string RAMtouch; // @touch primname +string RAManimationName; // @animate animation (string) time (float) +float RAManimationTime; + +// other globals section +integer iChannel; // a listen channel, randomly assigned +integer iHandle; // the handle to it + +// NPC controls +vector newDest ; // tmp storage for the walks +integer iWaitCounter ; // wait for this number of seconds for the NPC to reach a desrtination +string sNPCName; // the name of the NPC that may be in world. So we can remove it. +integer bNPC_STOP = FALSE; // boolean to reuse a listener +integer Stopped = FALSE; // set to TRUE by link messages so we do not remember them +float fTimerVal ; // how long we wait when wandering (calculated) +float NPCEnabled; // true if the NPC is suppodes to be running + +// OS_NPC_CREATOR_OWNED will create an 'owned' NPC that will only respond to osNpc* commands issued from scripts that have the same owner as the one that created the NPC. +// OS_NPC_NOT_OWNED will create an 'unowned' NPC that will respond to any script that has OSSL permissions to call osNpc* commands. +integer NPCOptions = OS_NPC_CREATOR_OWNED; // only yhe owner of this box can control this NPC. + +integer walkstate = 0; // helps us reshare the walk state for run, fly and land - a bit of a hack, but it saves RAM. Has to be done this way because some bits of NPCWalkOption are asserted as 0 + +integer NPCWalkOption; // Some notes for what happens to NPCWalkOption: +// OS_NPC_FLY - Fly the avatar to the given position. The avatar will not land unless the OS_NPC_LAND_AT_TARGET option is also given. +// OS_NPC_NO_FLY - Do not fly to the target. The NPC will attempt to walk to the location. If it's up in the air then the avatar will keep bouncing hopeless until another move target is given or the move is stopped +//OS_NPC_LAND_AT_TARGET - If given and the avatar is flying, then it will land when it reaches the target. If OS_NPC_NO_FLY is given then this option has no effect. +// OS_NPC_RUNNING - if given, NPC avatar moves at running/fast flying speed, otherwise moves at walking/slow flying speed. + +// menus +string mSensor="Sense is Off"; // Sensor or "No Sensor" + +list lAtButtons = ["Menu","-", ">>", "@run", "@walk", "@fly", "@land", "@wander", "@sit", "@stand","@animate","@rotate"]; +list lMenu2 = ["<<", "@comment", ">>>", "@stop", "@say", "@whisper","@shout","@sound","@randsound","@cmd", "@pause", "@delete"]; +list lMenu3 = ["<<<","@notecard","@appearance", "@touch", "@speed", "@attach", "-","-", "-", "-", "-", "-" ]; + +string sCommand; // place to store a command for two-prompted ones +string sParam2; // place to store a prompt for two-prompted ones +string priPub = "Owner Only"; // Private or Group +key kUserKey; // the person who is controlling the avatar, not the Owner +// the command lists +list lCommands; // commands are stored here +list lNPCScript; // Storage for the NPC script. +string npcAction; // Storage for the next action. @cmd=0|hello, this becomes @cmd +string npcParams; // Storage for the param, @cmd=0|hello, this becomes 0|hello + +// misc vars +string sNotecard; // commands are stored here temporarily for dumping +vector vWanderPos; // a place to wander to +string lastANIM ; // last animation run +// Sensor +integer avatarPresent; // Sensor sets this flag when people are within Range. + +// Coordinate control +vector vInitialPos ; // Vector that will be filled by the script with the initial starting position in region coordinates. +vector vDestPos = ZERO_VECTOR; // Storage for destination position. +string relAbs = "Relative"; // absolute vs relative positioning +vector lastKnownPos; // last known NPC position when we deleted it + +// STATES +integer MENU ; // processing a dialog box state, may be concurrent with STATE +integer STATE; // state storage +integer MakeNotecard = 1; // displaying a text box for NPC name +integer RecordPath = 2; // displaying a path notecard menu +integer NobodyHome = 3; // looking for an avatar +integer Spawning = 4; // spawning an avatar +integer Animate = 5; // animation timer needed +integer Walking = 6; // Hey! I am walking here! +integer Wander = 7; // Wandering around neeeds a timer, too +integer WanderHold = 8; // We reached a wander point +integer DoProcess = 9; // Set this to make it process a new command + +key gNpcKey = NULL_KEY; // global key storage for the one NPC, to save CPU cycles +list Stack ; // a command stack from link message input + +integer SensorFunc = 0; // define which function shall be triggered inside the sensor function + // 0 means none, 1 sit, 2 touch +/////////////////////////////////////////////////////////////////////////// +// FUNCTIONS // +/////////////////////////////////////////////////////////////////////////// + +// Do* functions are much like states from the old V2 scripts. + +// Save a Path notecard +DoSave() +{ + STATE = MakeNotecard; + makeText("Stand where you want the NPC to appear, and enter the NPC Name"); +} + +// This function is used to record the path for the NPC +// Each command can take 0, 1, or 2 params +DoMenuForCommands() { + makeMenu(lAtButtons); +} + + +// No one is here when sensors were on, so we kill off the NPC +DoNobodyHome() +{ + DEBUG("Nobody Home"); + STATE = NobodyHome; + if (NPCKey() != NULL_KEY) { + osNpcRemove(NPCKey()); + SaveKey(NULL_KEY); + } + TimerEvent(5); // keep ticking to sense avatars +} + +// Create a NPC +DoSpawn() { + DEBUG("state spawn"); + NPCEnabled = TRUE; // in world + // see if there is already one out there. + if (NPCKey() != NULL_KEY) { + DEBUG("Already living"); + return; + } + + STATE = Spawning; + + list name = llParseString2List(sNPCName, [" "], []); + + if (relAbs == "Relative"){ + vInitialPos += llGetPos(); + } + + DEBUG("Rezzing the NPC:" + (string) vInitialPos); + key aKey = osNpcCreate(llList2String(name, 0), llList2String(name, 1), vInitialPos, Appearance, NPCOptions); + + SaveKey(aKey ); // save in desceription and global, too + + osSetSpeed(aKey,SPEEDMULT); // 1.9 speed multiplier + TimerEvent(REZTIME); + NPCAnimate(STAND); +} + +DoRotate() { + DEBUG("@rotate=" + (string) RAMrot); + osNpcSetRot(NPCKey(), llEuler2Rot(<0,0,RAMrot> * DEG_TO_RAD)); +} + +DoSit() { + DEBUG ("state sit - looking for " + RAMsit); + SensorFunc = 1; //triggers osNpcSit + llSensor(RAMsit, "", PASSIVE|ACTIVE|SCRIPTED, 96, PI); +} + +DoTouch() { + DEBUG ("state touch - looking for " + RAMtouch); + SensorFunc = 2; //triggers osNpcTouch + llSensor(RAMtouch, "", PASSIVE|ACTIVE|SCRIPTED, 96, PI); +} + +DoStand() { + DEBUG("state stand"); + osNpcStand(NPCKey()); +} + + +DoAnimate() { + + DEBUG("state animate"); + STATE = Animate; + NPCAnimate(RAManimationName); + if (RAManimationTime <=0 ) // V 3.4 tweak + RAManimationTime = 1; + TimerEvent(RAManimationTime); +} + +DoWalk() { + + DEBUG("NPCWalkOption = " + (string) NPCWalkOption); + STATE = Walking; + + // walk, fly, run, land + if (walkstate == 1) { + NPCAnimate(WALK); + } else if (walkstate == 2) { + llShout(FLIGHT,"flying"); + NPCAnimate(FLY); + } else if (walkstate == 3) { + NPCAnimate(RUN); + } else if (walkstate == 4) { + NPCAnimate(LAND); + } + newDest = vDestPos ; + newDest.z += OffsetZ; + + // notecard is stored as offsets from this box with relative addressing. Convert to absolute + if (relAbs == "Relative"){ + newDest += llGetPos(); + } + + DEBUG("Moveto:" + (string) newDest); + osNpcMoveToTarget(NPCKey(), newDest, NPCWalkOption); + iWaitCounter = WAIT; // wait 60 seconds to get to a destination. + TimerEvent(TIMER); +} + + +DoWander(){ + DEBUG("state wander"); + STATE = Wander; + + vector point = CirclePoint(RAMwd); + DEBUG("CirclePoint:" + (string) point); + vWanderPos = vDestPos + point; + DEBUG("vWanderPos:" + (string) vWanderPos); + + fTimerVal = WANDERTIME; // default time to pause after each wander + if (WANDERRAND) + fTimerVal = llFrand(WANDERTIME) + 1; // override, they want random times + + NPCAnimate(WALK); + + DEBUG("Wander to:" + (string) vWanderPos); + + osNpcMoveToTarget(NPCKey(), vWanderPos, NPCWalkOption); + iWaitCounter = WAIT; // wait 60 seconds to get to a destination. + TimerEvent(TIMER); +} + +DoWanderhold() { + + DEBUG("Wander Hold"); + STATE = WanderHold; + + // now that we have reached a wander spot, slow the timer down to the desired value + TimerEvent(fTimerVal); +} + +// @pause=10 will do nothing for 10 seconds +DoPause() { + if (RAMPause < 0.1) + RAMPause = 0.1; + DEBUG("@pause=" + (string)RAMPause); + TimerEvent(RAMPause); +} + + +// @stop makes the NPC stop moving in whatever state it is in. You have to linkmessage to get moving again +DoStop() { + DEBUG("NPC is Stopped"); + Stopped = TRUE; // Link controlled - we mnust have a @go to continue with notecards + TimerEvent(0); + Stack = []; // v3.8 +} + +// @delete removes the NPC forever. Next command starts it up again at the beginning +DoDelete() { + DEBUG("state delete"); + + osNpcRemove(NPCKey()); + SaveKey(NULL_KEY); + Stopped = TRUE; // Link controlled - we mnust have a @go to continue with notecards + TimerEvent(0); + Stack = []; // v3.8 +} + +// change the appearance of the NPC +DoAppearance(string notecard) { + DEBUG("state appearance"); + if (llGetInventoryType(notecard) == INVENTORY_NOTECARD){ + DEBUG("Load appearance " + notecard); + osNpcLoadAppearance(NPCKey(),notecard); + } +} + +// Change the avatar speed +DoSpeed(string speed) { + float newspeed = (float) speed; + if (newspeed > 0.1 && newspeed < 5.0) // sanity check + osSetSpeed(NPCKey(),newspeed); +} +DoNewNote (string card) { + DEBUG("Load Notecard " + card); + NPCReadNoteCard(card); + Stopped = FALSE; +} +DoAttach(string params) { + + list Data = llParseString2List(params, ["|"], []); + string itemName = llList2String(Data, 0); + integer attachmentPoint = (integer) llList2String(Data, 1); + if (attachmentPoint > 0 + && attachmentPoint < 40 + && llGetInventoryType(itemName) == INVENTORY_OBJECT + ) + { + osForceAttachToOtherAvatarFromInventory(NPCKey(),itemName,attachmentPoint); + } +} + +// This loops over the notecard, processing each command +DoProcessNPCLine() { + DEBUG("ProcessNPCLine"); + STATE = 0; + + // auto load a notecard + if (! llGetListLength(lNPCScript)) { + DEBUG("Read Notecard"); + NPCReadNoteCard(Notecard); + Stopped = FALSE; + } + + // look for link messages on the stack + string next = llList2String(Stack,0); // lets see if there is anithing from a link message + if (llStringLength(next)) + { + Stack = llDeleteSubList(Stack,0,0); + ProcessCmd(next); //lets do this command instead. + return; + } + + // @stop issued? + if (Stopped) { + TimerEvent(0); + DEBUG("Waiting for input"); + return; + } + + // No, we have an @go for liftoff + next = llList2String(lNPCScript, 0); // get the next command + DEBUG("Execute:" + next); + lNPCScript = llDeleteSubList(lNPCScript, 0, 0); // delete it + + if (llGetListLength(lNPCScript) == 0) { + DEBUG("EOF"); + } + ProcessCmd(next); + +} + + + +ProcessCmd(string cmd) { + + DEBUG("ProcessCmd:" + cmd); + + if (llGetSubString(cmd, 0, 0) != "@") { + DEBUG("ignoring"); + STATE = DoProcess; + TimerEvent(QUICK); // this is so we do not recurse the stack + return; + } + + list data = llParseString2List(cmd, ["="], []); + npcAction = llToLower(llStringTrim(llList2String(data, 0), STRING_TRIM)); + + DEBUG("Action:" + npcAction); + npcParams = llStringTrim(llList2String(data, 1), STRING_TRIM); + + @commands; + + ProcessSensor(); + + + if(npcAction == "@spawn") { + DEBUG("@spawn"); + list spawnData = llParseString2List(npcParams, ["|"], []); + sNPCName =llList2String(spawnData, 0); // V 1.6 name in RAM + + list spawnDest = llParseString2List(llList2String(spawnData, 1), ["<", ",", ">"], []); + vInitialPos.x = llList2Float(spawnDest, 0); + vInitialPos.y = llList2Float(spawnDest, 1); + vInitialPos.z = llList2Float(spawnDest, 2); + + DEBUG("Coords for NPC at " + (string) vInitialPos); + DoSpawn(); + return; + } + + if (! avatarPresent){ + DoNobodyHome(); + DEBUG("No avatar nearby"); + return; + } else { + if ( NPCKey() == NULL_KEY) { + DoSpawn(); + } + } + + if(npcAction == "@stop") { + DoStop(); + return; + } + else if(npcAction == "@goto") { + DEBUG("goto"); + integer lastIdx = llGetListLength(lNPCScript)-1; + lNPCScript = llDeleteSubList(lNPCScript, lastIdx, lastIdx); + // Wind commands till goto label. + @wind; + string next1 = llList2String(lNPCScript, 0); + lNPCScript = llDeleteSubList(lNPCScript, 0, 0); + lNPCScript += next1; + if(next1 != npcParams) jump wind; + // Wind the label too. + next1 = llList2String(lNPCScript, 0); + lNPCScript = llDeleteSubList(lNPCScript, 0, 0); + lNPCScript += next1; + // Get next command. + list data1 = llParseString2List(next1, ["="], []); + npcAction = llToLower(llStringTrim(llList2String(data1, 0), STRING_TRIM)); + npcParams = llStringTrim(llList2String(data1, 1), STRING_TRIM); + // Reschedule. + jump commands; + } + else if(npcAction == "@sound") { + DEBUG("sound"); + llTriggerSound(npcParams,1.0); + } + else if(npcAction == "@randsound") { + DEBUG("@randsound"); + integer N = llGetInventoryNumber(INVENTORY_SOUND); + integer rand = llCeil(llFrand(N)) -1; // pick a random sound + string toPlay = llGetInventoryName(INVENTORY_SOUND,rand); + llTriggerSound(toPlay,1.0); + } + else if(npcAction == "@walk") { + DEBUG("@walk"); + GetDest(npcParams); + walkstate = 1;// walking + NPCWalkOption = OS_NPC_NO_FLY ; + DoWalk(); + return; + } + else if(npcAction == "@fly") { + GetDest(npcParams); + walkstate = 2;// flying + NPCWalkOption = OS_NPC_FLY ; + DoWalk(); + return; + } + else if(npcAction == "@run") { + DEBUG("@run"); + GetDest(npcParams); + walkstate = 3;// running + NPCWalkOption = OS_NPC_NO_FLY | OS_NPC_RUNNING; + DoWalk(); + return; + } + else if(npcAction == "@land") { + DEBUG("@land"); + GetDest(npcParams); + walkstate = 4;// landing + NPCWalkOption= OS_NPC_FLY | OS_NPC_LAND_AT_TARGET ; + DoWalk(); + return; + } + else if(npcAction == "@say") { + DEBUG("@say " + npcParams); + osNpcSay(NPCKey(), 0, npcParams); + } + else if(npcAction == "@shout") { + DEBUG("@shout"); + osNpcShout(NPCKey(),0, npcParams); + } + else if(npcAction == "@whisper") { + DEBUG("@whisper " + npcParams); + osNpcWhisper(NPCKey(),0, npcParams); + } + // speak a command on a channel, so you can open doors and control stuff. + else if(npcAction == "@cmd") { + DEBUG("@cmd"); + list dataToSpeak = llParseString2List(npcParams, ["|"], []); + string channel = llList2String(dataToSpeak,0); + DEBUG("Channel:"+(string) channel); + integer iChannel = (integer) channel; + string stringToSpeak = llList2String(dataToSpeak,1); + llSay(iChannel, stringToSpeak); + } + // stop everything + else if(npcAction == "@pause") { + RAMPause = (float) npcParams; + DoPause(); + return; + } + else if(npcAction == "@wander") { + list wanderData = llParseString2List(npcParams, ["|"], []); + RAMwd = (float) llList2String(wanderData, 0); + RAMwc = (integer) llList2String(wanderData, 1); + vDestPos = osNpcGetPos(NPCKey()); // set the wander start + DEBUG("Starting at " + (string) vDestPos); + DoWander(); + return; + } + else if(npcAction == "@rotate") { + RAMrot = (float) npcParams; + DoRotate(); + } + else if(npcAction == "@sit") { + RAMsit= npcParams; + DoSit(); + return; + } + else if(npcAction == "@touch") { + RAMtouch= npcParams; + DoTouch(); + return; + } + else if(npcAction == "@stand") { + DoStand(); + } + else if(npcAction == "@delete") { + DoDelete(); + return; + } + else if(npcAction == "@animate") { + list animateData = llParseString2List(npcParams, ["|"], []); + RAManimationName = llList2String(animateData, 0); + RAManimationTime = (float) llList2String(animateData, 1); + DoAnimate(); + return; + } + else if(npcAction == "@appearance" ){ + DoAppearance(npcParams); + } + else if (npcAction =="@speed") { + DoSpeed(npcParams); + } + else if (npcAction =="@notecard") { + DoNewNote(npcParams); + Notecard = npcParams; + } + else if (npcAction == "@attach") + { + DoAttach(npcParams); + } + + STATE = DoProcess; + TimerEvent(QUICK); // yeah I know, not possible this fast, we just go as fast as we can go - this is so we do not recurse the stack +} + + + +/////////////////// UTILITY Functions, not state-like ////////////////// + +// DEBUG(string) will chat a string or display it as hovertext if debug == TRUE +DEBUG(string str) { + if (debug) + llOwnerSay( str); // Send the owner debug info so you can chase NPCS + if (iTitleText) { + llSetText(str,<1.0,1.0,1.0>,1.0); // show hovertext + + } +} + +GetDest(string npcParams) { + list dest = llParseString2List(npcParams, ["<", ",", ">"], []); + vDestPos.x = llList2Float(dest, 0); + vDestPos.y = llList2Float(dest, 1); + vDestPos.z = llList2Float(dest, 2); +} + +NPCReadNoteCard(string Note) { + DEBUG("NPCReadNoteCard"); + lNPCScript = llParseString2List(osGetNotecard(Note), ["\n"], []); +} + +integer SenseAvatar() +{ + //Returns a strided list of the UUID, position, and name of each avatar in the region + list avatars = llGetAgentList(AGENT_LIST_REGION ,[]); + integer numOfAvatars = llGetListLength(avatars); + if (numOfAvatars == 0) + { + DEBUG("No people"); + return 0; + } + //DEBUG("Located " + (string)numOfAvatars + " avatars and NPC's"); + + integer nAvatars; + integer i; + for( i = 0;i < numOfAvatars; i++) { + key aviKey = llList2Key(avatars,i); + if (!osIsNpc(aviKey)) { + list detail = llGetObjectDetails(aviKey,[OBJECT_POS]); + vector pos = llList2Vector(detail,0); + float dist = llVecDist(pos, llGetPos()); + if (dist < RANGE) + { + nAvatars++; + DEBUG("In range:" + llKey2Name(aviKey)); + } + } + } + //DEBUG("Located " + (string) nAvatars + " avatars"); + return nAvatars; +} + +// return TRUE if the avatar is owner when private is set, or TRUE if the avatar is in the same group and GROUP is set. +integer checkPerms() { + + integer group = (integer) KeyValueGet("pr"); + if (! group) + priPub = "Owner Only"; + else + priPub = "Group"; + + + if (llDetectedKey(0) == llGetOwner()){ + kUserKey = llDetectedKey(0); + return TRUE; + } + + if ( group && llDetectedGroup(0)) { + kUserKey = llDetectedKey(0); + return TRUE; + } + kUserKey = llDetectedKey(0); + return FALSE; +} + + + +NPCAnimate(string anim) +{ + DEBUG("Start Anim: " + anim); + if (llGetInventoryType(anim) == INVENTORY_ANIMATION ) { + + if (lastANIM != anim) { + if(llStringLength(lastANIM)) { + osNpcStopAnimation(NPCKey(), lastANIM); + } + osNpcPlayAnimation(NPCKey(), anim); + lastANIM = anim; + } + } else { + llSay(DEBUG_CHANNEL, "No animation named " + anim); + } +} + + +TimerEvent(float timesent) +{ + DEBUG("Setting timer: " + (string) timesent); + llSetTimerEvent(timesent); +} + +// Kill a NPC by Name +Kill(string param) +{ + integer count; + list avatars = osGetAvatarList(); // Returns a strided list of the UUID, position, and name of each avatar in the region except the owner.\ + integer i; + integer j = llGetListLength(avatars); + for (i=0 ; i <= j; i+=3){ + + string desired = llList2String(avatars,i+2); + desired = llStringTrim(desired,STRING_TRIM); // should not be needed but is needed + + if (desired == param){ + vector v = llList2Vector(avatars,i+1); + key target = llList2Key(avatars,i); // get the UUID of the avatar + osNpcRemove(target); + SaveKey(NULL_KEY ); + llOwnerSay("Removed " + param+ " at location " + (string) v); + count++; + } + } + + NPCEnabled = FALSE; // not in world + + if (count) + llOwnerSay("Removed " + (string) count + " NPC's"); + else + llOwnerSay("Could not locate " + param); +} + + +// return a String for the position we are at. Strings used as the caller wants strings +string Pos() +{ + vector where = llGetPos(); // find the box position + + where.z += OffsetZ; // use the ground position + an offset + + if (Editor) + where = <128,128,31 + llFrand(1)>; // center of sim for editing + + // if attached the height will be too high by 1/2 the agent size + if (llGetAttached()) { + vector size = llGetAgentSize(llGetOwner()); + float Z = size.z; + where.z -= Z/2; + } + + // DEBUG("Pos= " + (string) where); + return (string) where; +} + +// setup a menu with a timer for timeouts, called by all make*() +menu() +{ + llListenRemove(iHandle); + iChannel = llCeil(llFrand(100000) + 20000); + iHandle = llListen(iChannel,"","",""); + TimerEvent(30.0); + MENU = TRUE; +} + +// make a text box +makeText(string Param) +{ + menu(); + llTextBox(kUserKey, Param, iChannel); +} + +// top level menu +makeMainMenu() +{ + menu(); + list buttons = ["Appearance","Recording","Save","Help","-","Erase RAM", priPub,relAbs,"-","Stop NPC",mSensor,"Start NPC"]; + llDialog(kUserKey,(string) llGetListLength(lCommands) + " Records",buttons,iChannel); +} + + +// Rev 1.4 +// top level menu for non group/ non owners +makeUserMenu() +{ + if (!allowUsers) return; + + menu(); + list buttons = ["Start NPC","Stop NPC"]; + llDialog(kUserKey,"Choose",buttons,iChannel); +} + + + +// programmable menu for @commands +makeMenu(list buttons) +{ + menu(); + llDialog(kUserKey,(string) llGetListLength(lCommands) + " Record",buttons,iChannel); +} + + +// make one or two text boxes with prompts +Text(string cmd, string p1, string p2) +{ + sCommand = cmd; + sParam2 = ""; + if (llStringLength(p2)) + sParam2 = p2; + + makeText(p1); +} + +// Set the Avatar Present flag - if sensors are off and we are forece run, there will be one present. +ProcessSensor() +{ + integer SensorOn; + if ("on" == KeyValueGet("se")) + { + SensorOn = TRUE; // we need to scan for avatars + } else { + SensorOn = FALSE; // we need to scan for avatars + } + DEBUG("Sensor:" + (string) SensorOn); + + integer n = SenseAvatar(); + + DEBUG("Avatars:" + (string) n); + if (SensorOn && n) + avatarPresent = TRUE; // someone is here and we need to tell the system to run + else if (SensorOn && !n) + avatarPresent = FALSE; // someone is not here and we need to tell the system to stop + else { // sensor is off, lete see if there is a NPC. If so, we are ON + DEBUG("NPCEnabled:" + (string) NPCEnabled); + if (NPCEnabled) + avatarPresent = TRUE; + else + avatarPresent = FALSE; + } + + // start up from when when no one is near + if (avatarPresent && STATE == NobodyHome) + STATE = 0; + + //DEBUG("Avatar Present: " + (string) avatarPresent); +} + +vector CirclePoint(float radius) { + float x = llFrand(radius *2) - radius; // +/- radius, randomized + float y = llFrand(radius *2) - radius; // +/- radius, randomized + return ; // so this should always happen +} + +string KeyValueGet(string var) { + list dVars = llParseString2List(llGetObjectDesc(), ["&"], []); + do { + list data = llParseString2List(llList2String(dVars, 0), ["="], []); + string k = llList2String(data, 0); + if(k != var) jump continue; + //DEBUG("got " + var + " = " + llList2String(data, 1)); + return llList2String(data, 1); + @continue; + dVars = llDeleteSubList(dVars, 0, 0); + } while(llGetListLength(dVars)); + return ""; +} + +KeyValueSet(string var, string val) { + + //DEBUG("set " + var + " = " + val); + list dVars = llParseString2List(llGetObjectDesc(), ["&"], []); + if(llGetListLength(dVars) == 0) + { + llSetObjectDesc(var + "=" + val); + return; + } + list result = []; + do { + list data = llParseString2List(llList2String(dVars, 0), ["="], []); + string k = llList2String(data, 0); + if(k == "") jump continue; + if(k == var && val == "") jump continue; + if(k == var) { + result += k + "=" + val; + val = ""; + jump continue; + } + string v = llList2String(data, 1); + if(v == "") jump continue; + result += k + "=" + v; + @continue; + dVars = llDeleteSubList(dVars, 0, 0); + } while(llGetListLength(dVars)); + if(val != "") result += var + "=" + val; + llSetObjectDesc(llDumpList2String(result, "&")); +} + + +// clear RAM +Clr() { + + lCommands = []; + llOwnerSay("RAM Memory cleared. Notecards, if any, are not modified."); + makeMainMenu(); +} + +integer checkNoteCards() +{ + // Check that they have saved an Appeaance and Path notecard + integer num = llGetInventoryNumber(INVENTORY_NOTECARD); // how many notecards overall + + integer i; + integer count; + for (; i < num; i++){ + if (llGetInventoryName(INVENTORY_NOTECARD,i) == Notecard) + count++; + if (llGetInventoryName(INVENTORY_NOTECARD,i) == Appearance) + count++; + } + DEBUG("Checked " + (string) count + " Notecards"); + // if we have both, run the NPC + return count; +} + +Update(string SName) { + + // delete all NPC*scripts except myself + integer i; + integer j = llGetInventoryNumber(INVENTORY_SCRIPT); + for (i = 0; i < j; i++) { + string name = llGetInventoryName(INVENTORY_SCRIPT,i); + string match = llGetSubString(name,0,2); + if (match == SName && llGetScriptName() != name) + { + llRemoveInventory(name); + llOwnerSay("Upgraded"); + } + } + +} + +// Get all default saved params from the Description +GetSwitches() +{ + string rA = KeyValueGet("co"); // Get the remembered menu setting for Abs Vs Relative + if (rA == "A") + relAbs = "Absolute"; + else if (rA == "R") + relAbs = "Relative"; + else + relAbs = "Absolute"; + + + // reenable NPC if sensor is on. + if ("on" == KeyValueGet("se")) + { + NPCEnabled = TRUE; + mSensor = "Sense is On"; + ProcessSensor(); // fake 1 avatar to get it rezzed + } else { + mSensor = "Sense is Off"; + } + } + + +SaveKey(key akey) +{ + DEBUG("Saving Key of " + (string) akey); + KeyValueSet("key", akey); + if (akey != (key) KeyValueGet("key") ) + { + DEBUG("Fatal error, cannot save key"); + } + gNpcKey = akey; +} + + +key NPCKey() +{ + key akey = gNpcKey; // from cached copy + // gNpcKey saves a lot of CPU processing by caching the key, if blank we get it from the description + if (gNpcKey == NULL_KEY) + { + //DEBUG("Get DKey"); + akey = KeyValueGet("key"); // from Description of the prim + } + // DEBUG("NPC KEY:" + (string) akey); + return akey; +} + + +/////////////////// CODE BEGINS ////////////////// + + +default +{ + changed(integer change) { + if(change & CHANGED_REGION_START) { + llResetScript(); + } + } + + on_rez(integer start_param) + { + llResetScript(); + } + + state_entry() { + + llSetText("",<1,1,1>,1.0); // clr all hovertext- we may not be using it. + DoDelete(); // kill any NPC that is out running + Update("NPC"); // If dragged and ropped into a prim with any script named "NPC...", this will replace it. + GetSwitches(); // Get all default saved params from the Description + llSetTimerEvent(TIMER); + } + + + touch_start(integer n) + { // if touched, make a menu + + if (checkPerms()) { + if (RecordPath == STATE) { + makeMenu(lAtButtons); + } else { + makeMainMenu(); + } + } else { + makeUserMenu(); + } + } + + // menu listener + listen(integer iChannel, string name, key id, string message) { + + if (MENU) { + llListenRemove(iHandle); + MENU = 0; // menu is off + iHandle = 0; + } + + if (message == "Stop NPC") + { + lNPCScript = []; // force reload of notecard + NPCEnabled = FALSE; + if (NPCKey() != NULL_KEY){ + Kill(sNPCName); + sNPCName = ""; + } else { + bNPC_STOP = TRUE; + makeText("Enter name of an NPC to stop"); + } + } + else if (message == "Menu" ) { + makeMainMenu(); + } + else if (message == "Erase RAM"){ + Clr(); + } + else if (message == "Relative"){ + relAbs = "Absolute"; + KeyValueSet("co","A"); // remember coordinates = A + Clr(); + } + else if (message == "Absolute"){ + relAbs = "Relative"; + KeyValueSet("co","R"); // remember coordinates = R + Clr(); + } + else if (message == "Recording"){ + DoMenuForCommands(); // show them the recording menu + } + else if (message == "Owner Only") { + priPub = "Group"; + KeyValueSet("pr","1"); + + llOwnerSay("Group members have control"); + makeMainMenu(); + } + else if (message == "Group") { + priPub = "Owner Only"; + KeyValueSet("pr","0"); + llOwnerSay("Only you have control"); + makeMainMenu(); + } + else if (message == "Sense is On") { + mSensor ="Sense is Off"; + KeyValueSet("se", "off"); + llOwnerSay(mSensor); + makeMainMenu(); + } + else if (message == "Sense is Off") { + mSensor ="Sense is On"; + llOwnerSay(mSensor); + KeyValueSet("se", "on"); + + NPCEnabled = FALSE; + + integer count = checkNoteCards(); + if (count >= 2) { + DEBUG("Notecards approved , calling DoProcessNPCLine"); + DoProcessNPCLine(); + return; + } + if (Editor) { + DoProcessNPCLine(); + return; + } + + llOwnerSay("You have not saved a recording and/or appearance, so you cannot start a NPC"); + makeMainMenu(); + } + else if (message == "Appearance") { + llRemoveInventory(Appearance); // delete the notecard + osAgentSaveAppearance(kUserKey,Appearance); // make the ntecard + llOwnerSay("Your outfit has been saved"); + makeMainMenu(); + } + else if (message == "Save") { + if (llGetListLength(lCommands) == 0) { + llOwnerSay("Nothing recorded, you need to make a recording first"); + makeMainMenu(); + return; + } + DoSave(); + } + else if (message == "Help"){ + llLoadURL(kUserKey,"Click to view help","http://www.outworldz.com/opensim/posts/NPC/"); + makeMainMenu(); + } + else if (message == "Start NPC") { + integer count = checkNoteCards(); + Stopped = FALSE; // Let's run the notecard + NPCEnabled = TRUE; + + if (Editor) { + DoProcessNPCLine(); + return; + } + + if (count >= 2) { + DEBUG("Notecards approved , calling DoProcessNPCLine"); + Stopped = FALSE; // Let's run the notecard + DoProcessNPCLine(); + return; + } + + llOwnerSay("You have not saved a recording or maybe an appearance, so we cannot start a NPC"); + + } + else if (bNPC_STOP){ + bNPC_STOP = FALSE; + Kill(message); + } + else if (message == ">>"){ + makeMenu(lMenu2); + } + else if (message == ">>>"){ + makeMenu(lMenu3); + } + else if (message == "<<") { + makeMenu(lAtButtons); + } + else if (message == "<<<") { + makeMenu(lMenu2); + } + else if (message == "@comment"){ + Text("# ","Enter a comment",""); + } + else if (message == "@stop"){ + lCommands += "@stop"+ "\n"; + makeMenu(lAtButtons); + } + else if (message == "@run"){ + lCommands += "@run=" + Pos() + "\n"; + llOwnerSay("Recorded position: " + Pos()); + makeMenu(lAtButtons); + } + else if (message == "@fly"){ + lCommands += "@fly=" + Pos() + "\n"; + llOwnerSay("Recorded position: " + Pos()); + makeMenu(lAtButtons); + } + else if (message == "@land"){ + lCommands += "@land=" + Pos() + "\n"; + llOwnerSay("Recorded position: " + Pos()); + makeMenu(lAtButtons); + } + else if (message == "@walk") { + lCommands += "@walk=" + Pos() + "\n"; + llOwnerSay("Recorded position: " + Pos()); + makeMenu(lAtButtons); + } + else if (message == "@stop"){ + lCommands += "@stop"+ "\n"; + makeMenu(lAtButtons); + } + else if (message == "@sound"){ + Text("@sound=","Enter a sound name or UUID to trigger",""); + } + else if (message == "@randsound"){ + lCommands += "@randsound"+ "\n"; + makeMenu(lAtButtons); + } + else if (message == "@say") { + Text("@say=","Enter what the NPC will say",""); + } + else if (message == "@whisper"){ + Text("@whisper=","Enter what the NPC will whisper",""); + } + else if (message == "@shout"){ + Text("@shout=","Enter what the NPC will shout",""); + } + else if (message == "@wander") { + Text("@wander=","Enter radius to wander","Enter number of wanders"); + } + else if (message == "@pause") { + Text("@pause=","Enter time to pause",""); + } + else if (message == "@rotate") { + Text("@rotate=","Enter degrees to rotate",""); + } + else if (message == "@sit"){ + Text("@sit=","Enter name of object to sit on",""); + } + else if (message == "@touch"){ + Text("@touch=","Enter name of object to touch",""); + } + else if (message == "@cmd"){ + Text("@cmd=","Enter cjhannel to speak on","Enter text to speak"); + } + else if (message == "@stand"){ + lCommands += "@stand\n"; + llOwnerSay("Stand Recorded"); + makeMenu(lAtButtons); + } + else if (message == "@animate"){ + Text("@animate=","Enter animation name to play","Enter time to play the animation"); + } + else if (message == "@attach"){ + Text("@animate=","Enter inventory name to attach","Enter number of the attachment point (1-40)"); + } + else if (message == "@speed"){ + Text("@speed=","Enter a speed for the NPC, 1=100% normal speed, 0.5=50% speed",""); + } + + + // Save NPC name + else if (MakeNotecard == STATE) { + sNPCName = message; // in case we need to kill it. + + vector vDest = (vector) Pos(); + + if (relAbs == "Relative") + { + vDest -= llGetPos(); // just an offset for relative + } + sNotecard = "@spawn=" + message + "|" + (string) vDest + "\n"; + integer i; + integer j = llGetListLength(lCommands); + for (; i < j; i++){ + // get the command to save to the notecard + string line = llList2String(lCommands,i); + if (relAbs == "Absolute") { + sNotecard += line; // add the un-modified string to the notecard + } else { + // since we have to record absolute coords since we do not know where the box goes until they press Save, + // we process the absolute to relative conversion for walks here + list parts = llParseString2List(line,["="],[]); //get the @command + + if (llList2String(parts,0) == "@walk") { + vector vec = (vector) llList2String(parts,1) - llGetPos(); + sNotecard += "@walk=" + (string) vec + "\n"; + } + else if (llList2String(parts,0) == "@fly") { + vector vec = (vector) llList2String(parts,1) - llGetPos(); + sNotecard += "@fly=" + (string) vec + "\n"; + } + else if (llList2String(parts,0) == "@run") { + vector vec = (vector) llList2String(parts,1) - llGetPos(); + sNotecard += "@run=" + (string) vec + "\n"; + } + else if (llList2String(parts,0) == "@land") { + vector vec = (vector) llList2String(parts,1) - llGetPos(); + sNotecard += "@land=" + (string) vec + "\n"; + } + else { + sNotecard += line; // add the un-modified string to the notecard + } + } + } + llRemoveInventory(Notecard); // delete the old notecard + osMakeNotecard(Notecard,sNotecard); // Makes the notecard. + llSay(0,sNotecard); + llOwnerSay("Commands notecard has been written"); + STATE = 0; + } // MakeNotecard + + else if (! llStringLength(sParam2)) { + lCommands += sCommand + message + "\n"; + llOwnerSay("Recorded"); + makeMenu(lAtButtons); + } + else if (llStringLength(sParam2)){ + sCommand = sCommand + message + "|"; + llOwnerSay("Recorded"); + makeText(sParam2); + sParam2 = ""; + } + + } + + + + timer(){ + // DEBUG("tick"); + + // if llDialog is up, kill the listener for the dialog box. + if (iHandle) { + llOwnerSay("Menu timed out"); + llListenRemove(iHandle); + iHandle = 0; + return; // ^^^^^^^^^^^^^^^^^^^^^^^ + } + // if NoBodyHome, we are sensing for an avatar + else if (NobodyHome == STATE) { + ProcessSensor(); + return; + } + // if we are spawning, we need time to rez the NPC, then start processing NPC Commands. + else if (Spawning == STATE) { + STATE = 0; + TimerEvent(TIMER); + } + // We end aniamtions with a timer + else if (Animate == STATE){ + NPCAnimate(STAND); + TimerEvent(TIMER); + } + + else if (Walking == STATE) { + if (--iWaitCounter) { + if (llVecDist(osNpcGetPos(NPCKey()), newDest) > MAXDIST) { + return; + } + } + + DEBUG("At Destination: " + (string) newDest); + + // walk, fly, run, land + if (walkstate == 1) { + NPCAnimate(STAND); + NPCWalkOption = OS_NPC_NO_FLY; + } else if (walkstate == 2) { + // nothing + } else if (walkstate == 3) { + NPCAnimate(STAND); + NPCWalkOption = OS_NPC_NO_FLY; + } else if (walkstate == 4) { + llShout(FLIGHT,"landing"); + NPCAnimate(STAND); + NPCWalkOption = OS_NPC_NO_FLY; + } + } + // Wandering timer + else if (Wander == STATE) { + if (--iWaitCounter) { // wait 60 seconds to get to a destination. + if (llVecDist(osNpcGetPos(NPCKey()), vWanderPos) > MAXDIST) + return; + } + + // see if wander counter == 0, if so, stop walking, go to stand and process next line + if(RAMwc == 0) { + NPCAnimate(STAND); + DEBUG("Wander ended, calling DoProcessNPCLine"); + STATE = 0; + DoProcessNPCLine(); + return; + } + // one less time to wander around + RAMwc--; + NPCAnimate(STAND); + TimerEvent(TIMER); + DoWanderhold(); + return; + } + // Wandering requires us to re-wander when we reach a destination + else if (WanderHold == STATE) { + DoWander(); + TimerEvent(TIMER); + return; + } + else if (DoProcess == STATE) { + TimerEvent(QUICK); + } + + STATE = 0; + + // We always process a NPC line at end of timer. + DEBUG("Tick end, calling DoProcessNPCLine"); + DoProcessNPCLine(); + } + + // sensors are used for sitting on prims + // Neo Cortex: added different SensorFunc states to trigger sit or touch + sensor(integer num) { + if (SensorFunc == 1) { + osNpcSit(NPCKey(), llDetectedKey(0), OS_NPC_SIT_NOW); + DEBUG("Seated, calling DoProcessNPCLine"); + SensorFunc = 0; + } else if (SensorFunc == 2) { + osNpcTouch(NPCKey(), llDetectedKey(0), LINK_THIS); + DEBUG("Touched, calling DoProcessNPCLine"); + SensorFunc = 0; + } + DoProcessNPCLine(); + } + no_sensor(){ + DEBUG ("no target prim located, calling DoProcessNPCLine"); + SensorFunc = 0; + DoProcessNPCLine(); + } + + + link_message(integer sender, integer num, string str, key id){ + DEBUG("Command In:" + str); + if (str=="@go") { + Stopped = FALSE; // Let's run the notecard + DEBUG("@go approved, calling DoProcessNPCLine"); + DoProcessNPCLine(); + } else { + Stack += [str]; // take anything, the controller will filter away non @ stuff + if (llGetListLength(Stack) == 1) { // 3.8 + DEBUG("Stack=1"); + DoProcessNPCLine(); + } + } + } + +} + + + + + + diff --git a/Hypergrid Story Three/Falling Bridge/Falling Bridge.prj b/Hypergrid Story Three/Falling Bridge/Falling Bridge.prj new file mode 100644 index 00000000..ba718d53 --- /dev/null +++ b/Hypergrid Story Three/Falling Bridge/Falling Bridge.prj @@ -0,0 +1,6 @@ + + + + + diff --git a/Hypergrid Story Three/Falling Bridge/Falling Part/FallingWalkway.lsl b/Hypergrid Story Three/Falling Bridge/Falling Part/FallingWalkway.lsl new file mode 100644 index 00000000..016cfdb2 --- /dev/null +++ b/Hypergrid Story Three/Falling Bridge/Falling Part/FallingWalkway.lsl @@ -0,0 +1,53 @@ +// :SHOW:1 +// :CATEGORY:Gaming +// :NAME:Falling walkway script +// :AUTHOR:Ferd Frederix +// :KEYWORDS:Game, Bridge +// :REV:2.0 +// :WORLD:OpenSim +// :DESCRIPTION: +// A part of a bridge or walkway that falls when walked on. +// Put in a walkway and reset it. When an avatar walks on it, it will move downward 2 meters for one minute, then restore. +// Place Hypergate water below the prim for them to fall into. +// :CODE: + + +Reset() +{ + llSetStatus(STATUS_PHANTOM, FALSE); + llVolumeDetect(FALSE); + llSleep(0.1); + llVolumeDetect(TRUE); +} + +vector initPos; +default +{ + state_entry() + { + initPos = llGetPos(); + Reset(); + } + + collision_start(integer num) { + if (osIsNpc(llDetectedKey(0))) + return; + vector newPos = initPos; + newPos.z -= 2; + llSetPos(newPos); + llSetTimerEvent(60); + } + + timer() + { + Reset(); + llSetPos(initPos); + llSetTimerEvent(0); + } + + + changed(integer what) { + if (what & CHANGED_REGION_START) + llResetScript(); + } +} \ No newline at end of file diff --git a/Hypergrid Story Three/HyperGrid Story Three.sol b/Hypergrid Story Three/HyperGrid Story Three.sol new file mode 100644 index 00000000..4ddf0de4 --- /dev/null +++ b/Hypergrid Story Three/HyperGrid Story Three.sol @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/Hypergrid Story Three/Hypergrid Water/Hypergrid Water.prj b/Hypergrid Story Three/Hypergrid Water/Hypergrid Water.prj new file mode 100644 index 00000000..2f404d90 --- /dev/null +++ b/Hypergrid Story Three/Hypergrid Water/Hypergrid Water.prj @@ -0,0 +1,6 @@ + + + + + diff --git a/Hypergrid Story Three/Hypergrid Water/Water prim/HG water.lsl b/Hypergrid Story Three/Hypergrid Water/Water prim/HG water.lsl new file mode 100644 index 00000000..0261c885 --- /dev/null +++ b/Hypergrid Story Three/Hypergrid Water/Water prim/HG water.lsl @@ -0,0 +1,84 @@ +// :SHOW:1 +// :CATEGORY:Gaming +// :NAME:Hypergate water by Ferd Frederix +// :AUTHOR:Ferd Frederix +// :KEYWORDS:Water, Game, Hypergate +// :REV:2.0 +// :WORLD:OpenSim +// :DESCRIPTION: +// teleport a game player back a step when they violate a game rule. +// Useful when placed under water, or over their heads to stop flying, and next to obstacles to keep them from going around it. +// To use, put the destination coordinate vector in the Description of the prim. When collided, they get tp'ed away. +// Requires osTeleportAgent to be enabled +// :CODE: + +// Rev 2.0 with Linux reset bug patched + +string msg = "You fell in!"; + +vector LandingPoint; // Where you want the Avatar to arrive at comes from the description +vector LookAt = <0.0,0.0,-1.0>; // which way you want them facing. +list LastFewAgents; +string TargetAddress ; // In-Grid Teleport (Region Na + +PerformTeleport( key AgentToTP ) +{ + integer CurrentTime = llGetUnixTime(); + integer AgentIndex = llListFindList( LastFewAgents, [ AgentToTP ] ); // Is the agent we're teleporting already in the list? + if (AgentIndex != -1) // If yes, check to make sure it's been > 5 seconds + { + integer PreviousTime = llList2Integer( LastFewAgents, AgentIndex+1 ); // Get the last time they were teleported + if (PreviousTime >= (CurrentTime - 5)) return; // Less than five seconds ago? Exit without teleporting + LastFewAgents = llDeleteSubList( LastFewAgents, AgentIndex, AgentIndex+1); // Delete the agent from the list + } + LastFewAgents += [ AgentToTP, CurrentTime ]; // Add the agent and current time to the list + osTeleportAgent( AgentToTP, TargetAddress, LandingPoint, LookAt ); // Teleport agent to their target + llSleep(1.0); + llResetScript(); +} + +RESET_VOL_DET() +{ + llSetStatus(STATUS_PHANTOM, FALSE); // Rev 2. + llVolumeDetect(FALSE); + llSleep(0.1); + llVolumeDetect(TRUE); +} + + +default +{ + on_rez(integer int) + { + llResetScript(); + } + state_entry() + { + LandingPoint = (vector) llGetObjectDesc(); + TargetAddress = llGetRegionName(); + RESET_VOL_DET(); + } + + collision_start(integer total_number) // total_number is the number of avatars detected. + { + if (osIsNpc(llDetectedKey(0))) + return; + + llSay(0, msg); + TargetAddress = llGetRegionName(); + PerformTeleport( llDetectedKey(0)); + RESET_VOL_DET(); + } + + changed(integer change) // something changed, take action + { + if(change & CHANGED_OWNER) + { + llResetScript(); + } + else if (change & CHANGED_REGION_START) // that bit is set during a region restart + { + llResetScript(); + } + } +} \ No newline at end of file diff --git a/Hypergrid Story Three/Landing Zone/1. Start of Game/Sequence.lsl b/Hypergrid Story Three/Landing Zone/1. Start of Game/Sequence.lsl new file mode 100644 index 00000000..b113ed68 --- /dev/null +++ b/Hypergrid Story Three/Landing Zone/1. Start of Game/Sequence.lsl @@ -0,0 +1,200 @@ +// :SHOW:1 +// :CATEGORY:Gaming +// :NAME:Hypergrid Story Three +// :AUTHOR:Ferd Frederix +// :KEYWORDS:Game, Collider +// :CREATED:2015-11-24 20:36:34 +// :EDITED:2015-11-24 19:36:34 +// :ID:1090 +// :NUM:1857 +// :REV:2.0 +// :WORLD:Opensim +// :DESCRIPTION: +// Sample sequencer script for NPC animator. It sequeunces multiple NPCs in order thru a scenarios +// each 'things' entry is the NPC number, a (float) time to take between sending commands ( 0 is not allowed, but a small number is() +///and a @command that is sent to the NPC. + +// :CODE: +// Notes: Link messages arte as follows +// 1 = Racoon +// 2 = Namaka +// 3 = Dylan + +// Rev: 2 fixes the Linux bug for collisions. + +integer busy = FALSE; + +integer debug = FALSE; +integer SENSE = TRUE; +float RATE = 5; +integer COLLIDE = TRUE; +float RANGE = 2; + + +list things ; +RezIn() +{ + things = [1,5,"@stop"]; + things += [2,5,"@stop"]; + things += [3,10,"@stop"]; + Speak(); +} + +DoIt() +{ + things = [-1,1,"@pause=1"]; + + things += [1,1,"@animate=avatar_jumpforjoy|1"]; + things += [1,1,"@say=Hello! What happened to you two?"]; + things += [1,1,"@run=<123.70119, 124.20567, 37.52779>"]; + things += [1,1,"@walk=<124.75295, 125.30030, 37.52779>"]; + + + + // rezpoint + things += [2,1,"@walk=<125.02877, 121.68277, 37.39204>"]; + things += [2,1,"@animate=SwayInBreeze|2"]; + things += [2,2,"@animate=avatar_type|2"]; + things += [2,2,"@say=We are cyber beings that were transformed into these appearances."]; + + things += [3,1,"@walk=<122.39304, 121.43833, 37.50384>"]; + + things += [3,1,"@animate=SwayInBreeze|2"]; + + things += [3,2,"@animate=avatar_type|3"]; + things += [3,1,"@say=Fire and wood do not mix. I cannot be with you, my love, while in this form."]; + + things += [2,2,"@animate=avatar_type|2"]; + things += [2,2,"@say=We must seek help to transform ourselves. I cannot live without you"]; + + things += [2,2,"@pause=2"]; + + + things += [2,3,"@animate=avatar_type|3"]; + things += [2,1,"@say=Now we are on a journey to find our home, and find ourselves again"]; + things += [2,2,"@pause=2"]; + + things += [1,3,"@animate=avatar_type|3"]; + things += [1,1,"@say=I see your friends have been changed by magic."]; + things += [1,2,"@pause=2"]; + + + things += [1,3,"@animate=avatar_type|3"]; + things += [1,1,"@stand"]; + things += [1,2,"@say=I can show a place that has potions to cure that."]; + + things += [1,2,"@animate=avatar_type|2"]; + things += [1,2,"@say=But you need be careful here, the swamp is a dangerous place "]; + things += [1,1,"@stand"]; + + things += [1,2,"@animate=avatar_type|2"]; + things += [1,1,"@say=I can show you the way. Follow me"]; + things += [1,1,"@walk=<119.90541, 124.44883, 37.51564>"]; + things += [1,1,"@walk=<121.84517, 124.44883, 37.51564>"]; + + things += [3,1,"@animate=avatar_type|3"]; + things += [3,1,"@whisper=I do not trust this racoon. He has a mask and could mislead us. "]; + + + + things += [2,1,"@animate=avatar_type|3"]; + things += [2,1,"@say=We must stay here, my love, where we do not burn the woods down."]; + + + things += [2,1,"@animate=avatar_type|2"]; + things += [2,1,"@say=Be careful, friend!"]; + + things += [3,1,"@animate=avatar_type|3"]; + things += [3,1,"@say=Thank you for helping us"]; + + things += [1,1,"@shout=Follow me!"]; + + + Speak(); +} + +Speak() { + + integer prim = llList2Integer(things,0); + float time = llList2Float(things,1); + string msg = llList2String(things,2); + if (debug) llOwnerSay("Prim:" + (string) prim + " time:" + (string) time + " Msg:" + msg); + if (prim) { + things = llDeleteSubList(things,0,2); + llMessageLinked(prim,0, msg,""); + if (time > 0) { + llSetTimerEvent(time); + } else { + llOwnerSay("Whooops, time = 0!"); + if (debug) llOwnerSay("Prim:" + (string) prim + " time:" + (string) time + " Msg:" + msg); + llSetTimerEvent(0); + Reset(); + busy = FALSE; + } + } else { + if (debug) llOwnerSay("Done"); + busy = FALSE; + Reset(); + llSetTimerEvent(0); + if (SENSE) + llSensorRepeat("","",AGENT,RANGE,PI,RATE); + } +} + +Reset() +{ + llSetStatus(STATUS_PHANTOM, FALSE); + llVolumeDetect(FALSE); + llSleep(0.1); + llVolumeDetect(TRUE); +} + +default +{ + state_entry() + { + llSetText("",<1,1,1>,1.0); + Reset(); + llSetTimerEvent(0); + if (SENSE) + llSensorRepeat("","",AGENT,RANGE,PI,RATE); + } + + sensor(integer n) { + if (debug) llOwnerSay("Bumped"); + if (! osIsNpc(llDetectedKey(0))) { + if (debug) llOwnerSay("Sensed avatar"); + llSensorRemove(); + RezIn(); + } + } + + timer() + { + Speak(); + } + + collision_start(integer n) { + if (debug) llOwnerSay("Collided with " + llKey2Name(llDetectedKey(0))); + if (busy) + return; + if (! osIsNpc(llDetectedKey(0))) + { + DoIt(); + } + + } + + on_rez(integer p) + { + llResetScript(); + } + + changed(integer what) + { + if (what & CHANGED_REGION_START) + { + llResetScript(); + } + } +} diff --git a/Hypergrid Story Three/Landing Zone/2. Advance Racoon to Tree/Advance to tree .lsl b/Hypergrid Story Three/Landing Zone/2. Advance Racoon to Tree/Advance to tree .lsl new file mode 100644 index 00000000..90518460 --- /dev/null +++ b/Hypergrid Story Three/Landing Zone/2. Advance Racoon to Tree/Advance to tree .lsl @@ -0,0 +1,70 @@ +// :SHOW:1 +// :CATEGORY:NPC +// :NAME:Hypergrid Story Three +// :AUTHOR:Ferd Frederix +// :KEYWORDS: +// :CREATED:2015-11-24 20:36:34 +// :EDITED:2015-11-24 19:36:34 +// :ID:1090 +// :NUM:1858 +// :REV:1 +// :WORLD:Opensim +// :DESCRIPTION: +// Sample collision script for NPC animator +// :CODE: + +integer debug = FALSE; + +DoIt() +{ + llMessageLinked(1,5, "@walk=<121.16252, 121.84969, 37.72606>",""); + llMessageLinked(1,1, "@say=The route is easy and not dangerous. Just follow me. ",""); + llMessageLinked(1,1, "@walk=<103, 103, 39>",""); +} + +Reset() +{ + llVolumeDetect(FALSE); + llSetStatus(STATUS_PHANTOM, FALSE); + llSleep(0.1); + llVolumeDetect(TRUE); +} + +default +{ + state_entry() + { + llSetText("",<1,1,1>,1.0); + llSetTimerEvent(3600); + Reset(); + } + + collision_start(integer n) { + + if (debug) llOwnerSay("Collided with " + llKey2Name(llDetectedKey(0))); + if (! osIsNpc(llDetectedKey(0))) + { + if (debug) llOwnerSay("Collided with " + llKey2Name(llDetectedKey(0))); + DoIt(); + } + } + + timer() + { + Reset(); + llSetTimerEvent(3600); + } + + on_rez(integer p) + { + llResetScript(); + } + + changed(integer what) + { + if (what & CHANGED_REGION_START) + { + llResetScript(); + } + } +} diff --git a/Hypergrid Story Three/Landing Zone/3. Advance to Edge/Advance to Edge.lsl b/Hypergrid Story Three/Landing Zone/3. Advance to Edge/Advance to Edge.lsl new file mode 100644 index 00000000..e697d1bb --- /dev/null +++ b/Hypergrid Story Three/Landing Zone/3. Advance to Edge/Advance to Edge.lsl @@ -0,0 +1,82 @@ +// :SHOW:1 +// :CATEGORY:NPC +// :NAME:Hypergrid Story Three +// :AUTHOR:Ferd Frederix +// :KEYWORDS:Game, NPC +// :CREATED:2015-11-24 20:36:34 +// :EDITED:2015-11-24 19:36:34 +// :ID:1090 +// :NUM:1859 +// :REV:2 +// :WORLD:Opensim +// :DESCRIPTION: +// Sample collision script for NPC animator. Moves the raccoon to the edge of the cliff +// :CODE: + +// Rev: 2 fixes the Linux bug for collisions. + + +integer debug = FALSE; +Reset() +{ + llSetStatus(STATUS_PHANTOM, FALSE); + llVolumeDetect(FALSE); + llSleep(0.1); + llVolumeDetect(TRUE); +} + + +DoIt() +{ + if (debug) llOwnerSay("Racoon halfway"); + llMessageLinked(1,0, "@say=Follow me. The bear will not hurt me.",""); + + llMessageLinked(1,0, "@walk=<88.10030, 74.18887, 38.53304>",""); + +} + + +default +{ + state_entry() + { + llSetText("",<1,1,1>,1.0); + Reset(); + llSetTimerEvent(3600); + + } + + collision_start(integer n) { + + if (! osIsNpc(llDetectedKey(0))) + { + if (debug) llOwnerSay("Collided with " + llKey2Name(llDetectedKey(0))); + + DoIt(); + + + } + + } + on_rez(integer p) + { + llResetScript(); + } + touch_start(integer p) + { + DoIt(); + } + timer() + { + Reset(); + llSetTimerEvent(3600); + } + + changed(integer what) + { + if (what & CHANGED_REGION_START) + { + llResetScript(); + } + } +} diff --git a/Hypergrid Story Three/Landing Zone/4. Edge of CLiff/Fall of edge of cliff.lsl b/Hypergrid Story Three/Landing Zone/4. Edge of CLiff/Fall of edge of cliff.lsl new file mode 100644 index 00000000..cca96c64 --- /dev/null +++ b/Hypergrid Story Three/Landing Zone/4. Edge of CLiff/Fall of edge of cliff.lsl @@ -0,0 +1,74 @@ +// :SHOW:1 +// :CATEGORY:NPC +// :NAME:Hypergrid Story Three +// :AUTHOR:Ferd Frederix +// :KEYWORDS:Game, NPC +// :CREATED:2015-11-24 20:36:34 +// :EDITED:2015-11-24 19:36:34 +// :ID:1090 +// :NUM:1860 +// :REV:2 +// :WORLD:Opensim +// :DESCRIPTION: +// Sample collision script for NPC animator./ Makes the racoon run off the cliff edge +// :CODE: + +// Rev: 2 fixes the Linux bug for collisions. +integer debug = FALSE; + +DoIt() +{ + // llOwnerSay("Racoon edge of cliff"); + llMessageLinked(1,0, "@say=Follow me down. I will wait for you there. ",""); + llMessageLinked(1,0, "@walk=<82.71397, 39.95649, 21.40128>",""); + llMessageLinked(1,0, "@walk=<81.58669, 46.91171, 21.47848>",""); + llMessageLinked(1,0, "@delete",""); + +} + +Reset() +{ + llSetStatus(STATUS_PHANTOM, FALSE); + llVolumeDetect(FALSE); + llSleep(0.1); + llVolumeDetect(TRUE); +} + + +default +{ + state_entry() + { + llSetText("",<1,1,1>,1.0); + Reset(); + llSetTimerEvent(3600); + } + + on_rez(integer p) + { + llResetScript(); + } + + collision_start(integer n) { + + if (! osIsNpc(llDetectedKey(0))) + { + if (debug) llOwnerSay("Collided with " + llKey2Name(llDetectedKey(0))); + + DoIt(); + } + } + timer() + { + Reset(); + llSetTimerEvent(3600); + } + + changed(integer what) + { + if (what & CHANGED_REGION_START) + { + llResetScript(); + } + } +} diff --git a/Hypergrid Story Three/Landing Zone/5. NPC Poseball 1,2,3 etc/NPC Rev 3.9.lsl b/Hypergrid Story Three/Landing Zone/5. NPC Poseball 1,2,3 etc/NPC Rev 3.9.lsl new file mode 100644 index 00000000..e1ed53c1 --- /dev/null +++ b/Hypergrid Story Three/Landing Zone/5. NPC Poseball 1,2,3 etc/NPC Rev 3.9.lsl @@ -0,0 +1,3200 @@ +// :SHOW:1 +// :CATEGORY:NPC +// :NAME:Hypergrid Story Three +// :AUTHOR:Ferd Frederix +// :KEYWORDS:NPC, Puppeteer +// :CREATED:2013-09-08 18:27:47 +// :EDITED:2015-11-24 19:36:34 +// :ID:1090 +// :NUM:1861 +// :REV:4.0 +// :WORLD:OpenSim +// :DESCRIPTION: +// All in one NPC recorder player. +// Supports both absolute and relative paths and many new commands +// Add animations named "Fly, Walk, Stand and Run" +// Click Prim to use. +// Should be worn as a HUD to record. +// Put it on the ground and click Sensor or Start NPC when done. +// :CODE: +// This is Rev 3.9 08/23/2015 + +// Revision History +// Rev 1.1 10-2-2014 @Sit did not work. Minor tweaks to casting for lslEditor +// Rev 1.2 10-14-2014 @ sit had wrong type. +// Rev 1.3 relative movement fixed for @fly +// Rev 1.4 4-3-2014 allow anyone to use this, non owners and non group members can only start and stop. +// Rev 1.5 5-17-2014 set sensor to auto start on reboot of sim +// Rev 1.6 5-24-2014 move menu so you can get it by touching, removed many of the KeyValues to RAM for efficiency +// Rev 1.7 CHANGED_REGION_START, not CHANGED_REGION_START (Opensim difference) +// Rev 1.8 tuned up Kill NPC, added more flexible upgrader +// Rev 1.9 Better script injection by link message// Rev 2.0 Added osSetSpeed so you can speed up or slow down an NPC. +// Rev 2.1 No laggy sensor used exept to sit on stuff +// Rev 2.2 Various sensor fixes +// Rev 2.3 Sets No Sensor in menu, must be started by hand +// Rev 2.4 - reserved for patches to 2.3 if needed +// Rev 3.0 Refactor out into subs, not states to make command injection easier +// New command: @appearance=Notecardname so you can switch to a new notecard on the fly +// New command: @speed=1.0 which slows up ( < 1 ) or speeds up ( > 1) +// Rev 3.1 Commands are not interruptible by Link Message +// Rev 3.2 Sensor patches for consistency in removing the NPC +// Rev 3.3 Added Touch command by Neo.Cortex@hbase42/hopto/org:8002 +// Added Menu 3 for notecard and appearance commands +// Rev 3.4 animation timer cannot be zero or it shuts off timer tweaked +// solves the NPC starting up when no sensor is set. +// Rev 3.5 fixes saving to !Path notecard +// Rev 3.6 08-11-2015 @delete acts like @stop. TYjhe NPC now rezzes after an @go back in where it was deleted +// Rev 3.7 08-11-2015 @attach command added to load an attachment from the inventory to the NPC +// Rev 3.8 08-17-2015 process queued commands one at a time without calling ProcessNPCLine on link message +// Rev 3.9 08-23-2011 Queued command fixes including @delete which were not always working +//*******************************************************************// + +// Instructions on how to use this is at http://www.outworldz.com/opensim/posts/NPC/ +// This is an OpenSim-only script. +// Author: Ferd Frederix aka Fred Beckhusen - fred@mitsi.com + +//////////////////////////////////////////////////////////////////////////////////////////// +// Original code was Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // +/////////////////////////////////////////////////////////////////////////////////////////// +// Please see: http://www.gnu.org/licenses/gpl.html for legal details, // +// rights of fair usage, the disclaimer and warranty conditions. // +/////////////////////////////////////////////////////////////////////////////////////////// +// The original NPC controller was from http://was.fm/opensim:npc +// Extensive additions and bug fixes by Fred Beckhusem, aka Ferd Frederix, fred@mitsi.com +// llSensor had two params swapped +// @Wander would wander where it had rezzed, not where it was. +// There was no 'no_sensor' event in sit, so if a @sit failed, the NPC got stuck +// The animation and walks always stopped old, then started new. It should be start new, then stop old so the default stand would be suppressed. +// New code: +// Merged with new Route recorder and notecard writer +// If the NPC failed to reach a destination it never moved on. Added WAIT global to tune this +// Exposed many tunable variables and ported the code to LSLEditor. +// Added floating point to times in notecard. + +// Added @sound, @randsound, @whisper, @shout, and @cmd controls. +// +// notecards integers are not floats for better control +// +// Link Messages may be used to perform external control by injecting @commands into the stream of actions +// Example: +// To chat something, such as with a chat robot +// llMessageLinked(LINK_SET,0,"@npc_say=Hello",""); + +// This script assumes that NPCs and OSSl scripting is enabled in the OpenSim configuration. +// In order to enable them, the following changes must be made in the OpenSim.ini configuration file: +// +// ; Turn on OSSL +// AllowOSFunctions = true +// OSFunctionThreatLevel = Severe + +//[NPC] +// ;# {Enabled} {} {Enable Non Player Character (NPC) facilities} {true false} +// Enabled = true +// +// and then the server has to be restarted. + + +// Commands: All commands begin with an @ sign. All other lines are ignored +// @commands may have optional parameters. The syntax is always: +// @cmd=parm1|parm2 +// NaN in the table below meand Not a Number. This means there is no parameter + +//Command First Parameter Second Parameter Description +//@spawn name location (vector) Rezzes an NPC with name at a location. +//@appearance NoteCardName NaN switch the NPC appearance to a new notecard +//@walk destination (vector) NaN Makes the NPC walk to destination. +//@fly destination (vector) NaN Makes the NPC fly to destination. +//@land destination (vector) NaN Makes the NPC land at destination. +//@say string NaN Makes the NPC speak a phrase. +//@whisper string NaN Makes the NPC whisper a phrase. +//@shout string NaN Makes the NPC shout a phrase. +//@pause seconds (float) NaN Makes the NPC wait for a multiple of seconds. +//@wander radius (float) cycles (integer) Makes the NPC wander in radius, for cycles seconds. +//@delete NaN NaN Removes the NPC. Requires a link message to continue +//@goto label (string) NaN Jump to the label label in the script. +//@animate animation (string) time (float) Makes the NPC trigger the animation animation for time seconds. +//@sound sound_name NaN plays a sound from inventory +//@randsound NaN NaN Plays a random sound from inventory +//@rotate degrees (float) NaN Rotate the NPC degrees around the Z axis. +//@sit primitive name NaN Sit on a primitive with a given name. +//@touch primitive name NaN Touch on a primitive with a given name. +//@stand NaN NaN If sitting on a primitive, stand up. +//@cmd channel (integer) string Says string on channel, for controlling external gadgets +//@stop NaN NaN Halts the NPC script indefinitely. Can be started with a link message +//@go NaN NaN Continues on next notecard line, for use in link messages +//@speed speed (float) NaN from 0 to N, where 1.0 ius a normal speed of an avatar. 0.2 is a turtle. +//@notecard notename (string) NaN load a new Path notecard +//@attach InventoryName attachmentPoint load an attachment from the inventory to the NPC onto point + +// Constant attachmentPoint Comment +// ATTACH_CHEST 1 chest/sternum +// ATTACH_HEAD 2 head +// ATTACH_LSHOULDER 3 left shoulder +// ATTACH_RSHOULDER 4 right shoulder +// ATTACH_LHAND 5 left hand +// ATTACH_RHAND 6 right hand +// ATTACH_LFOOT 7 left foot +// ATTACH_RFOOT 8 right foot +// ATTACH_BACK 9 back +// ATTACH_PELVIS 10 pelvis +// ATTACH_MOUTH 11 mouth +// ATTACH_CHIN 12 chin +// ATTACH_LEAR 13 left ear +// ATTACH_REAR 14 right ear +// ATTACH_LEYE 15 left eye +// ATTACH_REYE 16 right eye +// ATTACH_NOSE 17 nose +// ATTACH_RUARM 18 right upper arm +// ATTACH_RLARM 19 right lower arm +// ATTACH_LUARM 20 left upper arm +// ATTACH_LLARM 21 left lower arm +// ATTACH_RHIP 22 right hip +// ATTACH_RULEG 23 right upper leg +// ATTACH_RLLEG 24 right lower leg +// ATTACH_LHIP 25 left hip +// ATTACH_LULEG 26 left upper leg +// ATTACH_LLLEG 27 left lower leg +// ATTACH_BELLY 28 belly/stomach/tummy +// ATTACH_LEFT_PEC 29 left pectoral +// ATTACH_RIGHT_PEC 30 right pectoral +// ATTACH_HUD_CENTER_2 31 HUD Center 2 +// ATTACH_HUD_TOP_RIGHT 32 HUD Top Right +// ATTACH_HUD_TOP_CENTER33 HUD Top +// ATTACH_HUD_TOP_LEFT 34 HUD Top Left +// ATTACH_HUD_CENTER_1 35 HUD Center +// ATTACH_HUD_BOTTOM_LEFT 36 HUD Bottom Left +// ATTACH_HUD_BOTTOM 37 HUD Bottom +// ATTACH_HUD_BOTTOM_RIGHT 38 HUD Bottom Right +// ATTACH_NECK 39 neck +// ATTACH_AVATAR_CENTER 40 avatar center/root + + + +////////////////////////////////////////////////////////// +// DEBUG // +////////////////////////////////////////////////////////// +integer debug = FALSE; // set to TRUE or FALSE for debug chat on various actions +integer Editor = FALSE; // set to to TRUE to working in LSLEditor, FALSE for in-world. + // you must also include the NPC commands found in the other script since LSLEditor does not support OpenSim +integer iTitleText = FALSE; // set to TRUE to see debug info in text above the controller + +////////////////////////////////////////////////////////// +// TUNABLE CONFIGURATION // +////////////////////////////////////////////////////////// +float TIMER = 2; // how often the system checks the distance traveled. Fastest you can go is 0.5 seconds +float QUICK = 1; // when we need to move to the next state, we use a QUICK timer +string Appearance = "!Appearance"; // The name of the recorded Appearance notecard +string Notecard = "!Path"; // The name of the recorded routes +integer allowUsers = FALSE; // If true, any user can get a Start NPC and Stop NPC menu. Only groups and owners can get all commands if TRUE, or FALSE +float MAXDIST = 2.0; // how close a NPC has to get to a dest pos to continue to next state. Do not lower this too much, as it may miss the target +integer WANDERRAND = TRUE; // set to TRUE and they will pause during wanders a random number of seconds +float WANDERTIME = 3.0; // how long they stand after each @wander,if WANDERRAND is FALSE. If WANDERRAND is TRUE, this is the max time +integer WAIT = 30; // wait for this number of seconds for the NPC to reach a destination (for safety). If it fails to reach a target, it will move on after this time. +float RANGE = 50; // 1 to N meters - anyone this close to the controller will start NPCS if Sensor button is clicked +float REZTIME = 2.0; // wait this long for NPC to rez in, then start the process +string STAND = "Stand"; // the name of the default Stand animation +string WALK = "Walk"; // the name of the default Walk animation +string FLY = "Fly"; // the name of the default Fly animation +string RUN = "Run"; // the name of the default Run animation +string LAND = "Land"; // the name of the default land animation ( for birds only) +float OffsetZ = 0.5; // appear 0.5 meter above ground, this is added to all destinations to keep them from sinking in. +float SPEEDMULT =0.5; // 1.0 = regular avatar speed. Smaller numbers slow down walks. Large numbers speed them up. +integer FLIGHT = 299; // For controlling wings. A channel that is shouted at when flight starts and ends. "flying" or "landing" + +// DESCRIPTIONS FIELDS HAVE TO SURVIVE A RESET +// These vars are stored by saving them with KeyValueSet +// "pr" is a 0 if it is set for Owner Only, 1 for Group control +// "se" is "on" if Started +// "co" = "R" or "A" for relative or absolute addressing mode +// "key" = NPC key + +// These Globals used to be stored in description. Moved to RAM in V1.6 +float RAMPause; // @pause param +float RAMwd ; // @wander distance +integer RAMwc; // @wander count +float RAMrot; // @rotate +string RAMsit; // @sit primname +string RAMtouch; // @touch primname +string RAManimationName; // @animate animation (string) time (float) +float RAManimationTime; + +// other globals section +integer iChannel; // a listen channel, randomly assigned +integer iHandle; // the handle to it + +// NPC controls +vector newDest ; // tmp storage for the walks +integer iWaitCounter ; // wait for this number of seconds for the NPC to reach a desrtination +string sNPCName; // the name of the NPC that may be in world. So we can remove it. +integer bNPC_STOP = FALSE; // boolean to reuse a listener +integer Stopped = FALSE; // set to TRUE by link messages so we do not remember them +float fTimerVal ; // how long we wait when wandering (calculated) +float NPCEnabled; // true if the NPC is suppodes to be running + +// OS_NPC_CREATOR_OWNED will create an 'owned' NPC that will only respond to osNpc* commands issued from scripts that have the same owner as the one that created the NPC. +// OS_NPC_NOT_OWNED will create an 'unowned' NPC that will respond to any script that has OSSL permissions to call osNpc* commands. +integer NPCOptions = OS_NPC_CREATOR_OWNED; // only yhe owner of this box can control this NPC. + +integer walkstate = 0; // helps us reshare the walk state for run, fly and land - a bit of a hack, but it saves RAM. Has to be done this way because some bits of NPCWalkOption are asserted as 0 + +integer NPCWalkOption; // Some notes for what happens to NPCWalkOption: +// OS_NPC_FLY - Fly the avatar to the given position. The avatar will not land unless the OS_NPC_LAND_AT_TARGET option is also given. +// OS_NPC_NO_FLY - Do not fly to the target. The NPC will attempt to walk to the location. If it's up in the air then the avatar will keep bouncing hopeless until another move target is given or the move is stopped +//OS_NPC_LAND_AT_TARGET - If given and the avatar is flying, then it will land when it reaches the target. If OS_NPC_NO_FLY is given then this option has no effect. +// OS_NPC_RUNNING - if given, NPC avatar moves at running/fast flying speed, otherwise moves at walking/slow flying speed. + +// menus +string mSensor="Sense is Off"; // Sensor or "No Sensor" + +list lAtButtons = ["Menu","-", ">>", "@run", "@walk", "@fly", "@land", "@wander", "@sit", "@stand","@animate","@rotate"]; +list lMenu2 = ["<<", "@comment", ">>>", "@stop", "@say", "@whisper","@shout","@sound","@randsound","@cmd", "@pause", "@delete"]; +list lMenu3 = ["<<<","@notecard","@appearance", "@touch", "@speed", "@attach", "-","-", "-", "-", "-", "-" ]; + +string sCommand; // place to store a command for two-prompted ones +string sParam2; // place to store a prompt for two-prompted ones +string priPub = "Owner Only"; // Private or Group +key kUserKey; // the person who is controlling the avatar, not the Owner +// the command lists +list lCommands; // commands are stored here +list lNPCScript; // Storage for the NPC script. +string npcAction; // Storage for the next action. @cmd=0|hello, this becomes @cmd +string npcParams; // Storage for the param, @cmd=0|hello, this becomes 0|hello + +// misc vars +string sNotecard; // commands are stored here temporarily for dumping +vector vWanderPos; // a place to wander to +string lastANIM ; // last animation run +// Sensor +integer avatarPresent; // Sensor sets this flag when people are within Range. + +// Coordinate control +vector vInitialPos ; // Vector that will be filled by the script with the initial starting position in region coordinates. +vector vDestPos = ZERO_VECTOR; // Storage for destination position. +string relAbs = "Relative"; // absolute vs relative positioning +vector lastKnownPos; // last known NPC position when we deleted it + +// STATES +integer MENU ; // processing a dialog box state, may be concurrent with STATE +integer STATE; // state storage +integer MakeNotecard = 1; // displaying a text box for NPC name +integer RecordPath = 2; // displaying a path notecard menu +integer NobodyHome = 3; // looking for an avatar +integer Spawning = 4; // spawning an avatar +integer Animate = 5; // animation timer needed +integer Walking = 6; // Hey! I am walking here! +integer Wander = 7; // Wandering around neeeds a timer, too +integer WanderHold = 8; // We reached a wander point +integer DoProcess = 9; // Set this to make it process a new command +integer Touch = 10; // Timer is busy sensing something to touch +integer Sit = 11; // Timer is busy sensing something to sit on +integer Paused = 12; // Timer is busy pausing + +key gNpcKey = NULL_KEY; // global key storage for the one NPC, to save CPU cycles +list Stack ; // a command stack from link message input + +integer SensorFunc = 0; // define which function shall be triggered inside the sensor function + // 0 means none, 1 sit, 2 touch +/////////////////////////////////////////////////////////////////////////// +// FUNCTIONS // +/////////////////////////////////////////////////////////////////////////// + + +SetStop(integer what) +{ + DEBUG("Stopped set to " + (string ) what); + Stopped = what; +} +// Do* functions are much like states from the old V2 scripts. + +// Save a Path notecard +DoSave() +{ + STATE = MakeNotecard; + makeText("Stand where you want the NPC to appear, and enter the NPC Name"); +} + +// This function is used to record the path for the NPC +// Each command can take 0, 1, or 2 params +DoMenuForCommands() { + makeMenu(lAtButtons); +} + + +// No one is here when sensors were on, so we kill off the NPC +DoNobodyHome() +{ + DEBUG("Nobody Home"); + STATE = NobodyHome; + if (NPCKey() != NULL_KEY) { + osNpcRemove(NPCKey()); + SaveKey(NULL_KEY); + } + TimerEvent(5); // keep ticking to sense avatars +} + +// Create a NPC +StateSpawn() { + DEBUG("state spawn"); + STATE = Spawning; + + + NPCEnabled = TRUE; // in world + // see if there is already one out there. + if (NPCKey() != NULL_KEY) { + DEBUG("Already living"); + return; + } + + + list name = llParseString2List(sNPCName, [" "], []); + + if (relAbs == "Relative"){ + vInitialPos += llGetPos(); + } + + DEBUG("Rezzing the NPC:" + (string) vInitialPos); + key aKey = osNpcCreate(llList2String(name, 0), llList2String(name, 1), vInitialPos, Appearance, NPCOptions); + + SaveKey(aKey ); // save in desceription and global, too + + osSetSpeed(aKey,SPEEDMULT); // 1.9 speed multiplier + TimerEvent(REZTIME); + NPCAnimate(STAND); +} + +DoRotate() { + DEBUG("@rotate=" + (string) RAMrot); + osNpcSetRot(NPCKey(), llEuler2Rot(<0,0,RAMrot> * DEG_TO_RAD)); +} + +StateSit() { + DEBUG ("state sit - looking for " + RAMsit); + STATE=Sit; + SensorFunc = 1; //triggers osNpcSit + llSensor(RAMsit, "", PASSIVE|ACTIVE|SCRIPTED, 96, PI); +} + +StateTouch() { + DEBUG ("state touch - looking for " + RAMtouch); + STATE = Touch; + SensorFunc = 2; //triggers osNpcTouch + llSensor(RAMtouch, "", PASSIVE|ACTIVE|SCRIPTED, 96, PI); +} + +DoStand() { + DEBUG("state stand"); + osNpcStand(NPCKey()); +} + + +StateAnimate() { + + DEBUG("state animate"); + STATE = Animate; + NPCAnimate(RAManimationName); + if (RAManimationTime <=0 ) // V 3.4 tweak + RAManimationTime = 1; + TimerEvent(RAManimationTime); +} + +StateWalk() { + + DEBUG("NPCWalkOption = " + (string) NPCWalkOption); + STATE = Walking; + + // walk, fly, run, land + if (walkstate == 1) { + NPCAnimate(WALK); + } else if (walkstate == 2) { + llShout(FLIGHT,"flying"); + NPCAnimate(FLY); + } else if (walkstate == 3) { + NPCAnimate(RUN); + } else if (walkstate == 4) { + NPCAnimate(LAND); + } + newDest = vDestPos ; + newDest.z += OffsetZ; + + // notecard is stored as offsets from this box with relative addressing. Convert to absolute + if (relAbs == "Relative"){ + newDest += llGetPos(); + } + + DEBUG("Moveto:" + (string) newDest); + osNpcMoveToTarget(NPCKey(), newDest, NPCWalkOption); + iWaitCounter = WAIT; // wait 60 seconds to get to a destination. + TimerEvent(TIMER); +} + + +StateWander(){ + DEBUG("state wander"); + STATE = Wander; + + vector point = CirclePoint(RAMwd); + DEBUG("CirclePoint:" + (string) point); + vWanderPos = vDestPos + point; + DEBUG("vWanderPos:" + (string) vWanderPos); + + fTimerVal = WANDERTIME; // default time to pause after each wander + if (WANDERRAND) + fTimerVal = llFrand(WANDERTIME) + 1; // override, they want random times + + NPCAnimate(WALK); + + DEBUG("Wander to:" + (string) vWanderPos); + + osNpcMoveToTarget(NPCKey(), vWanderPos, NPCWalkOption); + iWaitCounter = WAIT; // wait 60 seconds to get to a destination. + TimerEvent(TIMER); +} + +StateWanderhold() { + + DEBUG("Wander Hold"); + STATE = WanderHold; + + // now that we have reached a wander spot, slow the timer down to the desired value + TimerEvent(fTimerVal); +} + +// @pause=10 will do nothing for 10 seconds +DoPause() { + STATE =Paused; + if (RAMPause < 0.1) + RAMPause = 0.1; + DEBUG("@pause=" + (string)RAMPause); + TimerEvent(RAMPause); +} + + +// @stop makes the NPC stop moving in whatever state it is in. You have to linkmessage to get moving again +DoStop() { + DEBUG("NPC is Stopped"); + STATE = 0; // accept commands + SetStop(TRUE); // Link controlled - we mnust have a @go to continue with notecards + TimerEvent(0); + Stack = []; // v3.8 +} + +// @delete removes the NPC forever. Next command starts it up again at the beginning +DoDelete() { + DEBUG("state delete"); + STATE = 0; // accept commands + osNpcRemove(NPCKey()); + SaveKey(NULL_KEY); + + TimerEvent(0); + Stack = []; // v3.8 +} + +// change the appearance of the NPC +DoAppearance(string notecard) { + DEBUG("state appearance"); + if (llGetInventoryType(notecard) == INVENTORY_NOTECARD){ + DEBUG("Load appearance " + notecard); + osNpcLoadAppearance(NPCKey(),notecard); + } +} + +// Change the avatar speed +DoSpeed(string speed) { + float newspeed = (float) speed; + if (newspeed > 0.1 && newspeed < 5.0) {// sanity check + osSetSpeed(NPCKey(),newspeed); + } +} +DoNewNote (string card) { + DEBUG("Load Notecard " + card); + NPCReadNoteCard(card); + SetStop(FALSE); +} +DoAttach(string params) { + + list Data = llParseString2List(params, ["|"], []); + string itemName = llList2String(Data, 0); + integer attachmentPoint = (integer) llList2String(Data, 1); + if (attachmentPoint > 0 + && attachmentPoint < 40 + && llGetInventoryType(itemName) == INVENTORY_OBJECT + ) + { + osForceAttachToOtherAvatarFromInventory(NPCKey(),itemName,attachmentPoint); + } +} + +// This loops over the notecard, processing each command +DoProcessNPCLine() { + DEBUG("ProcessNPCLine, stopped = " + (string) Stopped); + STATE = DoProcess; + + // auto load a notecard + if (! llGetListLength(lNPCScript)) { + DEBUG("Read Notecard"); + NPCReadNoteCard(Notecard); + } + + // look for link messages on the stack + string next = llList2String(Stack,0); // lets see if there is anithing from a link message + if (llStringLength(next)) + { + Stack = llDeleteSubList(Stack,0,0); + ProcessCmd(next); //lets do this command instead. + return; + } + + // @stop issued? + if (Stopped) { + TimerEvent(0); + DEBUG("Stopped, waiting for input"); + STATE = 0; + return; + } + + // No, we have an @go for liftoff + next = llList2String(lNPCScript, 0); // get the next command + DEBUG("Execute:" + next); + lNPCScript = llDeleteSubList(lNPCScript, 0, 0); // delete it + + if (llGetListLength(lNPCScript) == 0) { + DEBUG("EOF"); + } + ProcessCmd(next); +} + + + +ProcessCmd(string cmd) { + + DEBUG("ProcessCmd:" + cmd); + + if (llGetSubString(cmd, 0, 0) != "@") { + DEBUG("ignoring"); + STATE = DoProcess; + TimerEvent(QUICK); // this is so we do not recurse the stack + STATE = 0; + return; + } + + list data = llParseString2List(cmd, ["="], []); + npcAction = llToLower(llStringTrim(llList2String(data, 0), STRING_TRIM)); + + DEBUG("Action:" + npcAction); + npcParams = llStringTrim(llList2String(data, 1), STRING_TRIM); + + @commands; + + ProcessSensor(); + + + if(npcAction == "@spawn") { + DEBUG("@spawn"); + list spawnData = llParseString2List(npcParams, ["|"], []); + sNPCName =llList2String(spawnData, 0); // V 1.6 name in RAM + + list spawnDest = llParseString2List(llList2String(spawnData, 1), ["<", ",", ">"], []); + vInitialPos.x = llList2Float(spawnDest, 0); + vInitialPos.y = llList2Float(spawnDest, 1); + vInitialPos.z = llList2Float(spawnDest, 2); + + DEBUG("Coords for NPC at " + (string) vInitialPos); + StateSpawn(); + return; + } + + if (! avatarPresent){ + DoNobodyHome(); + DEBUG("No avatar nearby"); + STATE = 0; + return; + } else { + if ( NPCKey() == NULL_KEY) { + StateSpawn(); + } + } + + if(npcAction == "@stop") { + DoStop(); + STATE = 0; + return; + } + else if(npcAction == "@goto") { + DEBUG("goto"); + integer lastIdx = llGetListLength(lNPCScript)-1; + lNPCScript = llDeleteSubList(lNPCScript, lastIdx, lastIdx); + // Wind commands till goto label. + @wind; + string next1 = llList2String(lNPCScript, 0); + lNPCScript = llDeleteSubList(lNPCScript, 0, 0); + lNPCScript += next1; + if(next1 != npcParams) jump wind; + // Wind the label too. + next1 = llList2String(lNPCScript, 0); + lNPCScript = llDeleteSubList(lNPCScript, 0, 0); + lNPCScript += next1; + // Get next command. + list data1 = llParseString2List(next1, ["="], []); + npcAction = llToLower(llStringTrim(llList2String(data1, 0), STRING_TRIM)); + npcParams = llStringTrim(llList2String(data1, 1), STRING_TRIM); + // Reschedule. + jump commands; + } + else if(npcAction == "@sound") { + DEBUG("sound"); + llTriggerSound(npcParams,1.0); + } + else if(npcAction == "@randsound") { + DEBUG("@randsound"); + integer N = llGetInventoryNumber(INVENTORY_SOUND); + integer rand = llCeil(llFrand(N)) -1; // pick a random sound + string toPlay = llGetInventoryName(INVENTORY_SOUND,rand); + llTriggerSound(toPlay,1.0); + } + else if(npcAction == "@walk") { + DEBUG("@walk"); + GetDest(npcParams); + walkstate = 1;// walking + NPCWalkOption = OS_NPC_NO_FLY ; + StateWalk(); + return; + } + else if(npcAction == "@fly") { + GetDest(npcParams); + walkstate = 2;// flying + NPCWalkOption = OS_NPC_FLY ; + StateWalk(); + return; + } + else if(npcAction == "@run") { + DEBUG("@run"); + GetDest(npcParams); + walkstate = 3;// running + NPCWalkOption = OS_NPC_NO_FLY | OS_NPC_RUNNING; + StateWalk(); + return; + } + else if(npcAction == "@land") { + DEBUG("@land"); + GetDest(npcParams); + walkstate = 4;// landing + NPCWalkOption= OS_NPC_FLY | OS_NPC_LAND_AT_TARGET ; + StateWalk(); + return; + } + else if(npcAction == "@say") { + DEBUG("@say " + npcParams); + osNpcSay(NPCKey(), 0, npcParams); + } + else if(npcAction == "@shout") { + DEBUG("@shout"); + osNpcShout(NPCKey(),0, npcParams); + } + else if(npcAction == "@whisper") { + DEBUG("@whisper " + npcParams); + osNpcWhisper(NPCKey(),0, npcParams); + } + // speak a command on a channel, so you can open doors and control stuff. + else if(npcAction == "@cmd") { + DEBUG("@cmd"); + list dataToSpeak = llParseString2List(npcParams, ["|"], []); + string channel = llList2String(dataToSpeak,0); + DEBUG("Channel:"+(string) channel); + integer iChannel = (integer) channel; + string stringToSpeak = llList2String(dataToSpeak,1); + llSay(iChannel, stringToSpeak); + } + // stop everything + else if(npcAction == "@pause") { + RAMPause = (float) npcParams; + DoPause(); + return; + } + else if(npcAction == "@wander") { + list wanderData = llParseString2List(npcParams, ["|"], []); + RAMwd = (float) llList2String(wanderData, 0); + RAMwc = (integer) llList2String(wanderData, 1); + vDestPos = osNpcGetPos(NPCKey()); // set the wander start + DEBUG("Starting at " + (string) vDestPos); + StateWander(); + return; + } + else if(npcAction == "@rotate") { + RAMrot = (float) npcParams; + DoRotate(); + } + else if(npcAction == "@sit") { + RAMsit= npcParams; + StateSit(); + return; + } + else if(npcAction == "@touch") { + RAMtouch= npcParams; + StateTouch(); + return; + } + else if(npcAction == "@stand") { + DoStand(); + } + else if(npcAction == "@delete") { + DoDelete(); + SetStop(TRUE); // Link controlled - we mnust have a @go to continue with notecards + return; + } + else if(npcAction == "@animate") { + list animateData = llParseString2List(npcParams, ["|"], []); + RAManimationName = llList2String(animateData, 0); + RAManimationTime = (float) llList2String(animateData, 1); + StateAnimate(); + return; + } + else if(npcAction == "@appearance" ){ + DoAppearance(npcParams); + } + else if (npcAction =="@speed") { + DoSpeed(npcParams); + } + else if (npcAction =="@notecard") { + DoNewNote(npcParams); + Notecard = npcParams; + } + else if (npcAction == "@attach") + { + DoAttach(npcParams); + } + + STATE = 0; + TimerEvent(QUICK); // yeah I know, not possible this fast, we just go as fast as we can go - this is so we do not recurse the stack +} + + + +/////////////////// UTILITY Functions, not state-like ////////////////// + +// DEBUG(string) will chat a string or display it as hovertext if debug == TRUE +DEBUG(string str) { + if (debug) + llOwnerSay( str); // Send the owner debug info so you can chase NPCS + if (iTitleText) { + llSetText(str,<1.0,1.0,1.0>,1.0); // show hovertext + + } +} + +GetDest(string npcParams) { + list dest = llParseString2List(npcParams, ["<", ",", ">"], []); + vDestPos.x = llList2Float(dest, 0); + vDestPos.y = llList2Float(dest, 1); + vDestPos.z = llList2Float(dest, 2); +} + +NPCReadNoteCard(string Note) { + DEBUG("NPCReadNoteCard"); + lNPCScript = llParseString2List(osGetNotecard(Note), ["\n"], []); +} + +integer SenseAvatar() +{ + //Returns a strided list of the UUID, position, and name of each avatar in the region + list avatars = llGetAgentList(AGENT_LIST_REGION ,[]); + integer numOfAvatars = llGetListLength(avatars); + if (numOfAvatars == 0) + { + DEBUG("No people"); + return 0; + } + //DEBUG("Located " + (string)numOfAvatars + " avatars and NPC's"); + + integer nAvatars; + integer i; + for( i = 0;i < numOfAvatars; i++) { + key aviKey = llList2Key(avatars,i); + if (!osIsNpc(aviKey)) { + list detail = llGetObjectDetails(aviKey,[OBJECT_POS]); + vector pos = llList2Vector(detail,0); + float dist = llVecDist(pos, llGetPos()); + if (dist < RANGE) + { + nAvatars++; + DEBUG("In range:" + llKey2Name(aviKey)); + } + } + } + //DEBUG("Located " + (string) nAvatars + " avatars"); + return nAvatars; +} + +// return TRUE if the avatar is owner when private is set, or TRUE if the avatar is in the same group and GROUP is set. +integer checkPerms() { + + integer group = (integer) KeyValueGet("pr"); + if (! group) + priPub = "Owner Only"; + else + priPub = "Group"; + + + if (llDetectedKey(0) == llGetOwner()){ + kUserKey = llDetectedKey(0); + return TRUE; + } + + if ( group && llDetectedGroup(0)) { + kUserKey = llDetectedKey(0); + return TRUE; + } + kUserKey = llDetectedKey(0); + return FALSE; +} + + + +NPCAnimate(string anim) +{ + DEBUG("Start Anim: " + anim); + if (llGetInventoryType(anim) == INVENTORY_ANIMATION ) { + + if (lastANIM != anim) { + if(llStringLength(lastANIM)) { + osNpcStopAnimation(NPCKey(), lastANIM); + } + osNpcPlayAnimation(NPCKey(), anim); + lastANIM = anim; + } + } else { + llSay(DEBUG_CHANNEL, "No animation named " + anim); + } +} + + +TimerEvent(float timesent) +{ + DEBUG("Setting timer: " + (string) timesent); + llSetTimerEvent(timesent); +} + +// Kill a NPC by Name +Kill(string param) +{ + integer count; + list avatars = osGetAvatarList(); // Returns a strided list of the UUID, position, and name of each avatar in the region except the owner.\ + integer i; + integer j = llGetListLength(avatars); + for (i=0 ; i <= j; i+=3){ + + string desired = llList2String(avatars,i+2); + desired = llStringTrim(desired,STRING_TRIM); // should not be needed but is needed + + if (desired == param){ + vector v = llList2Vector(avatars,i+1); + key target = llList2Key(avatars,i); // get the UUID of the avatar + osNpcRemove(target); + SaveKey(NULL_KEY ); + llOwnerSay("Removed " + param+ " at location " + (string) v); + count++; + } + } + + NPCEnabled = FALSE; // not in world + + if (count) + llOwnerSay("Removed " + (string) count + " NPC's"); + else + llOwnerSay("Could not locate " + param); +} + + +// return a String for the position we are at. Strings used as the caller wants strings +string Pos() +{ + vector where = llGetPos(); // find the box position + + where.z += OffsetZ; // use the ground position + an offset + + if (Editor) + where = <128,128,31 + llFrand(1)>; // center of sim for editing + + // if attached the height will be too high by 1/2 the agent size + if (llGetAttached()) { + vector size = llGetAgentSize(llGetOwner()); + float Z = size.z; + where.z -= Z/2; + } + + // DEBUG("Pos= " + (string) where); + return (string) where; +} + +// setup a menu with a timer for timeouts, called by all make*() +menu() +{ + llListenRemove(iHandle); + iChannel = llCeil(llFrand(100000) + 20000); + iHandle = llListen(iChannel,"","",""); + TimerEvent(30.0); + MENU = TRUE; +} + +// make a text box +makeText(string Param) +{ + menu(); + llTextBox(kUserKey, Param, iChannel); +} + +// top level menu +makeMainMenu() +{ + menu(); + list buttons = ["Appearance","Recording","Save","Help","-","Erase RAM", priPub,relAbs,"-","Stop NPC",mSensor,"Start NPC"]; + llDialog(kUserKey,(string) llGetListLength(lCommands) + " Records",buttons,iChannel); +} + + +// Rev 1.4 +// top level menu for non group/ non owners +makeUserMenu() +{ + if (!allowUsers) return; + + menu(); + list buttons = ["Start NPC","Stop NPC"]; + llDialog(kUserKey,"Choose",buttons,iChannel); +} + + + +// programmable menu for @commands +makeMenu(list buttons) +{ + menu(); + llDialog(kUserKey,(string) llGetListLength(lCommands) + " Record",buttons,iChannel); +} + + +// make one or two text boxes with prompts +Text(string cmd, string p1, string p2) +{ + sCommand = cmd; + sParam2 = ""; + if (llStringLength(p2)) + sParam2 = p2; + + makeText(p1); +} + +// Set the Avatar Present flag - if sensors are off and we are forece run, there will be one present. +ProcessSensor() +{ + integer SensorOn; + if ("on" == KeyValueGet("se")) + { + SensorOn = TRUE; // we need to scan for avatars + } else { + SensorOn = FALSE; // we need to scan for avatars + } + DEBUG("Sensor:" + (string) SensorOn); + + integer n = SenseAvatar(); + + DEBUG("Avatars:" + (string) n); + if (SensorOn && n) + avatarPresent = TRUE; // someone is here and we need to tell the system to run + else if (SensorOn && !n) + avatarPresent = FALSE; // someone is not here and we need to tell the system to stop + else { // sensor is off, lete see if there is a NPC. If so, we are ON + DEBUG("NPCEnabled:" + (string) NPCEnabled); + if (NPCEnabled) + avatarPresent = TRUE; + else + avatarPresent = FALSE; + } + + // start up from when when no one is near + if (avatarPresent && STATE == NobodyHome) + STATE = 0; + + //DEBUG("Avatar Present: " + (string) avatarPresent); +} + +vector CirclePoint(float radius) { + float x = llFrand(radius *2) - radius; // +/- radius, randomized + float y = llFrand(radius *2) - radius; // +/- radius, randomized + return ; // so this should always happen +} + +string KeyValueGet(string var) { + list dVars = llParseString2List(llGetObjectDesc(), ["&"], []); + do { + list data = llParseString2List(llList2String(dVars, 0), ["="], []); + string k = llList2String(data, 0); + if(k != var) jump continue; + //DEBUG("got " + var + " = " + llList2String(data, 1)); + return llList2String(data, 1); + @continue; + dVars = llDeleteSubList(dVars, 0, 0); + } while(llGetListLength(dVars)); + return ""; +} + +KeyValueSet(string var, string val) { + + //DEBUG("set " + var + " = " + val); + list dVars = llParseString2List(llGetObjectDesc(), ["&"], []); + if(llGetListLength(dVars) == 0) + { + llSetObjectDesc(var + "=" + val); + return; + } + list result = []; + do { + list data = llParseString2List(llList2String(dVars, 0), ["="], []); + string k = llList2String(data, 0); + if(k == "") jump continue; + if(k == var && val == "") jump continue; + if(k == var) { + result += k + "=" + val; + val = ""; + jump continue; + } + string v = llList2String(data, 1); + if(v == "") jump continue; + result += k + "=" + v; + @continue; + dVars = llDeleteSubList(dVars, 0, 0); + } while(llGetListLength(dVars)); + if(val != "") result += var + "=" + val; + llSetObjectDesc(llDumpList2String(result, "&")); +} + + +// clear RAM +Clr() { + + lCommands = []; + llOwnerSay("RAM Memory cleared. Notecards, if any, are not modified."); + makeMainMenu(); +} + +integer checkNoteCards() +{ + // Check that they have saved an Appeaance and Path notecard + integer num = llGetInventoryNumber(INVENTORY_NOTECARD); // how many notecards overall + + integer i; + integer count; + for (; i < num; i++){ + if (llGetInventoryName(INVENTORY_NOTECARD,i) == Notecard) + count++; + if (llGetInventoryName(INVENTORY_NOTECARD,i) == Appearance) + count++; + } + DEBUG("Checked " + (string) count + " Notecards"); + // if we have both, run the NPC + return count; +} + +Update(string SName) { + + // delete all NPC*scripts except myself + integer i; + integer j = llGetInventoryNumber(INVENTORY_SCRIPT); + for (i = 0; i < j; i++) { + string name = llGetInventoryName(INVENTORY_SCRIPT,i); + string match = llGetSubString(name,0,2); + if (match == SName && llGetScriptName() != name) + { + llRemoveInventory(name); + llOwnerSay("Upgraded"); + } + } + +} + +// Get all default saved params from the Description +GetSwitches() +{ + string rA = KeyValueGet("co"); // Get the remembered menu setting for Abs Vs Relative + if (rA == "A") + relAbs = "Absolute"; + else if (rA == "R") + relAbs = "Relative"; + else + relAbs = "Absolute"; + + + // reenable NPC if sensor is on. + if ("on" == KeyValueGet("se")) + { + NPCEnabled = TRUE; + mSensor = "Sense is On"; + ProcessSensor(); // fake 1 avatar to get it rezzed + } else { + mSensor = "Sense is Off"; + } + } + + +SaveKey(key akey) +{ + DEBUG("Saving Key of " + (string) akey); + KeyValueSet("key", akey); + if (akey != (key) KeyValueGet("key") ) + { + DEBUG("Fatal error, cannot save key"); + } + gNpcKey = akey; +} + + +key NPCKey() +{ + key akey = gNpcKey; // from cached copy + // gNpcKey saves a lot of CPU processing by caching the key, if blank we get it from the description + if (gNpcKey == NULL_KEY) + { + //DEBUG("Get DKey"); + akey = KeyValueGet("key"); // from Description of the prim + } + // DEBUG("NPC KEY:" + (string) akey); + return akey; +} + + +/////////////////// CODE BEGINS ////////////////// + + +default +{ + changed(integer change) { + if(change & CHANGED_REGION_START) { + llResetScript(); + } + } + + on_rez(integer start_param) + { + llResetScript(); + } + + state_entry() { + + llSetText("",<1,1,1>,1.0); // clr all hovertext- we may not be using it. + DoDelete(); // kill any NPC that is out running + Update("NPC"); // If dragged and ropped into a prim with any script named "NPC...", this will replace it. + GetSwitches(); // Get all default saved params from the Description + llSetTimerEvent(TIMER); + } + + + touch_start(integer n) + { // if touched, make a menu + + if (checkPerms()) { + if (RecordPath == STATE) { + makeMenu(lAtButtons); + } else { + makeMainMenu(); + } + } else { + makeUserMenu(); + } + } + + // menu listener + listen(integer iChannel, string name, key id, string message) { + + if (MENU) { + llListenRemove(iHandle); + MENU = 0; // menu is off + iHandle = 0; + } + + if (message == "Stop NPC") + { + lNPCScript = []; // force reload of notecard + NPCEnabled = FALSE; + if (NPCKey() != NULL_KEY){ + Kill(sNPCName); + sNPCName = ""; + } else { + bNPC_STOP = TRUE; + makeText("Enter name of an NPC to stop"); + } + } + else if (message == "Menu" ) { + makeMainMenu(); + } + else if (message == "Erase RAM"){ + Clr(); + } + else if (message == "Relative"){ + relAbs = "Absolute"; + KeyValueSet("co","A"); // remember coordinates = A + Clr(); + } + else if (message == "Absolute"){ + relAbs = "Relative"; + KeyValueSet("co","R"); // remember coordinates = R + Clr(); + } + else if (message == "Recording"){ + DoMenuForCommands(); // show them the recording menu + } + else if (message == "Owner Only") { + priPub = "Group"; + KeyValueSet("pr","1"); + + llOwnerSay("Group members have control"); + makeMainMenu(); + } + else if (message == "Group") { + priPub = "Owner Only"; + KeyValueSet("pr","0"); + llOwnerSay("Only you have control"); + makeMainMenu(); + } + else if (message == "Sense is On") { + mSensor ="Sense is Off"; + KeyValueSet("se", "off"); + llOwnerSay(mSensor); + makeMainMenu(); + } + else if (message == "Sense is Off") { + mSensor ="Sense is On"; + llOwnerSay(mSensor); + KeyValueSet("se", "on"); + + NPCEnabled = FALSE; + + integer count = checkNoteCards(); + if (count >= 2) { + DEBUG("Notecards approved , calling DoProcessNPCLine"); + DoProcessNPCLine(); + return; + } + if (Editor) { + DoProcessNPCLine(); + return; + } + + llOwnerSay("You have not saved a recording and/or appearance, so you cannot start a NPC"); + makeMainMenu(); + } + else if (message == "Appearance") { + llRemoveInventory(Appearance); // delete the notecard + osAgentSaveAppearance(kUserKey,Appearance); // make the ntecard + llOwnerSay("Your outfit has been saved"); + makeMainMenu(); + } + else if (message == "Save") { + if (llGetListLength(lCommands) == 0) { + llOwnerSay("Nothing recorded, you need to make a recording first"); + makeMainMenu(); + return; + } + DoSave(); + } + else if (message == "Help"){ + llLoadURL(kUserKey,"Click to view help","http://www.outworldz.com/opensim/posts/NPC/"); + makeMainMenu(); + } + else if (message == "Start NPC") { + integer count = checkNoteCards(); + + NPCEnabled = TRUE; + + if (Editor) { + DoProcessNPCLine(); + return; + } + + if (count >= 2) { + DEBUG("Notecards approved , calling DoProcessNPCLine"); + SetStop(FALSE); // Let's run the notecard + DoProcessNPCLine(); + return; + } + + llOwnerSay("You have not saved a recording or maybe an appearance, so we cannot start a NPC"); + + } + else if (bNPC_STOP){ + bNPC_STOP = FALSE; + Kill(message); + } + else if (message == ">>"){ + makeMenu(lMenu2); + } + else if (message == ">>>"){ + makeMenu(lMenu3); + } + else if (message == "<<") { + makeMenu(lAtButtons); + } + else if (message == "<<<") { + makeMenu(lMenu2); + } + else if (message == "@comment"){ + Text("# ","Enter a comment",""); + } + else if (message == "@stop"){ + lCommands += "@stop"+ "\n"; + makeMenu(lAtButtons); + } + else if (message == "@run"){ + lCommands += "@run=" + Pos() + "\n"; + llOwnerSay("Recorded position: " + Pos()); + makeMenu(lAtButtons); + } + else if (message == "@fly"){ + lCommands += "@fly=" + Pos() + "\n"; + llOwnerSay("Recorded position: " + Pos()); + makeMenu(lAtButtons); + } + else if (message == "@land"){ + lCommands += "@land=" + Pos() + "\n"; + llOwnerSay("Recorded position: " + Pos()); + makeMenu(lAtButtons); + } + else if (message == "@walk") { + lCommands += "@walk=" + Pos() + "\n"; + llOwnerSay("Recorded position: " + Pos()); + makeMenu(lAtButtons); + } + else if (message == "@stop"){ + lCommands += "@stop"+ "\n"; + makeMenu(lAtButtons); + } + else if (message == "@sound"){ + Text("@sound=","Enter a sound name or UUID to trigger",""); + } + else if (message == "@randsound"){ + lCommands += "@randsound"+ "\n"; + makeMenu(lAtButtons); + } + else if (message == "@say") { + Text("@say=","Enter what the NPC will say",""); + } + else if (message == "@whisper"){ + Text("@whisper=","Enter what the NPC will whisper",""); + } + else if (message == "@shout"){ + Text("@shout=","Enter what the NPC will shout",""); + } + else if (message == "@wander") { + Text("@wander=","Enter radius to wander","Enter number of wanders"); + } + else if (message == "@pause") { + Text("@pause=","Enter time to pause",""); + } + else if (message == "@rotate") { + Text("@rotate=","Enter degrees to rotate",""); + } + else if (message == "@sit"){ + Text("@sit=","Enter name of object to sit on",""); + } + else if (message == "@touch"){ + Text("@touch=","Enter name of object to touch",""); + } + else if (message == "@cmd"){ + Text("@cmd=","Enter cjhannel to speak on","Enter text to speak"); + } + else if (message == "@stand"){ + lCommands += "@stand\n"; + llOwnerSay("Stand Recorded"); + makeMenu(lAtButtons); + } + else if (message == "@animate"){ + Text("@animate=","Enter animation name to play","Enter time to play the animation"); + } + else if (message == "@attach"){ + Text("@animate=","Enter inventory name to attach","Enter number of the attachment point (1-40)"); + } + else if (message == "@speed"){ + Text("@speed=","Enter a speed for the NPC, 1=100% normal speed, 0.5=50% speed",""); + } + + + // Save NPC name + else if (MakeNotecard == STATE) { + sNPCName = message; // in case we need to kill it. + + vector vDest = (vector) Pos(); + + if (relAbs == "Relative") + { + vDest -= llGetPos(); // just an offset for relative + } + sNotecard = "@spawn=" + message + "|" + (string) vDest + "\n"; + integer i; + integer j = llGetListLength(lCommands); + for (; i < j; i++){ + // get the command to save to the notecard + string line = llList2String(lCommands,i); + if (relAbs == "Absolute") { + sNotecard += line; // add the un-modified string to the notecard + } else { + // since we have to record absolute coords since we do not know where the box goes until they press Save, + // we process the absolute to relative conversion for walks here + list parts = llParseString2List(line,["="],[]); //get the @command + + if (llList2String(parts,0) == "@walk") { + vector vec = (vector) llList2String(parts,1) - llGetPos(); + sNotecard += "@walk=" + (string) vec + "\n"; + } + else if (llList2String(parts,0) == "@fly") { + vector vec = (vector) llList2String(parts,1) - llGetPos(); + sNotecard += "@fly=" + (string) vec + "\n"; + } + else if (llList2String(parts,0) == "@run") { + vector vec = (vector) llList2String(parts,1) - llGetPos(); + sNotecard += "@run=" + (string) vec + "\n"; + } + else if (llList2String(parts,0) == "@land") { + vector vec = (vector) llList2String(parts,1) - llGetPos(); + sNotecard += "@land=" + (string) vec + "\n"; + } + else { + sNotecard += line; // add the un-modified string to the notecard + } + } + } + llRemoveInventory(Notecard); // delete the old notecard + osMakeNotecard(Notecard,sNotecard); // Makes the notecard. + llSay(0,sNotecard); + llOwnerSay("Commands notecard has been written"); + STATE = 0; + } // MakeNotecard + + else if (! llStringLength(sParam2)) { + lCommands += sCommand + message + "\n"; + llOwnerSay("Recorded"); + makeMenu(lAtButtons); + } + else if (llStringLength(sParam2)){ + sCommand = sCommand + message + "|"; + llOwnerSay("Recorded"); + makeText(sParam2); + sParam2 = ""; + } + + } + + + + timer(){ + // DEBUG("tick"); + + // if llDialog is up, kill the listener for the dialog box. + if (iHandle) { + llOwnerSay("Menu timed out"); + llListenRemove(iHandle); + iHandle = 0; + return; // ^^^^^^^^^^^^^^^^^^^^^^^ + } + // if NoBodyHome, we are sensing for an avatar + else if (NobodyHome == STATE) { + ProcessSensor(); + return; + } + // if we are spawning, we need time to rez the NPC, then start processing NPC Commands. + else if (Spawning == STATE) { + STATE = 0; + TimerEvent(TIMER); + } + // We end aniamtions with a timer + else if (Animate == STATE){ + NPCAnimate(STAND); + TimerEvent(TIMER); + } + + else if (Walking == STATE) { + if (--iWaitCounter) { + if (llVecDist(osNpcGetPos(NPCKey()), newDest) > MAXDIST) { + return; + } + } + + DEBUG("At Destination: " + (string) newDest); + + // walk, fly, run, land + if (walkstate == 1) { + NPCAnimate(STAND); + NPCWalkOption = OS_NPC_NO_FLY; + } else if (walkstate == 2) { + // nothing + } else if (walkstate == 3) { + NPCAnimate(STAND); + NPCWalkOption = OS_NPC_NO_FLY; + } else if (walkstate == 4) { + llShout(FLIGHT,"landing"); + NPCAnimate(STAND); + NPCWalkOption = OS_NPC_NO_FLY; + } + } + // Wandering timer + else if (Wander == STATE) { + if (--iWaitCounter) { // wait 60 seconds to get to a destination. + if (llVecDist(osNpcGetPos(NPCKey()), vWanderPos) > MAXDIST) + return; + } + + // see if wander counter == 0, if so, stop walking, go to stand and process next line + if(RAMwc == 0) { + NPCAnimate(STAND); + DEBUG("Wander ended, calling DoProcessNPCLine"); + STATE = 0; + DoProcessNPCLine(); + return; + } + // one less time to wander around + RAMwc--; + NPCAnimate(STAND); + TimerEvent(TIMER); + StateWanderhold(); + return; + } + // Wandering requires us to re-wander when we reach a destination + else if (WanderHold == STATE) { + StateWander(); + TimerEvent(TIMER); + return; + } + else if (DoProcess == STATE) { + TimerEvent(QUICK); + } + + + STATE = 0; + + // We always process a NPC line at end of timer. + DEBUG("Tick end, calling DoProcessNPCLine"); + DoProcessNPCLine(); + } + + // sensors are used for sitting on prims + // Neo Cortex: added different SensorFunc states to trigger sit or touch + sensor(integer num) { + if (SensorFunc == 1) { + osNpcSit(NPCKey(), llDetectedKey(0), OS_NPC_SIT_NOW); + DEBUG("Seated, calling DoProcessNPCLine"); + SensorFunc = 0; + } else if (SensorFunc == 2) { + osNpcTouch(NPCKey(), llDetectedKey(0), LINK_THIS); + DEBUG("Touched, calling DoProcessNPCLine"); + SensorFunc = 0; + } + DoProcessNPCLine(); + } + no_sensor(){ + DEBUG ("no target prim located, calling DoProcessNPCLine"); + SensorFunc = 0; + DoProcessNPCLine(); + } + + + link_message(integer sender, integer num, string str, key id){ + DEBUG("Command In:" + str); + if (str=="@go") { + SetStop(FALSE); // Let's run the notecard + DEBUG("@go running"); + DoProcessNPCLine(); + } else { + Stack += [str]; // take anything, the controller will filter away non @ stuff + if (STATE == 0) { + DEBUG("calling DoNPC"); + DoProcessNPCLine(); + } else{ + DEBUG("Queued"); + + } + } + } + +} + + + + + + + +// This is Rev 3.7 08/11/2015 which added @attach + +// Revision History +// Rev 1.1 10-2-2014 @Sit did not work. Minor tweaks to casting for lslEditor +// Rev 1.2 10-14-2014 @ sit had wrong type. +// Rev 1.3 relative movement fixed for @fly +// Rev 1.4 4-3-2014 allow anyone to use this, non owners and non group members can only start and stop. +// Rev 1.5 5-17-2014 set sensor to auto start on reboot of sim +// Rev 1.6 5-24-2014 move menu so you can get it by touching, removed many of the KeyValues to RAM for efficiency +// Rev 1.7 CHANGED_REGION_START, not CHANGED_REGION_START (Opensim difference) +// Rev 1.8 tuned up Kill NPC, added more flexible upgrader +// Rev 1.9 Better script injection by link message// Rev 2.0 Added osSetSpeed so you can speed up or slow down an NPC. +// Rev 2.1 No laggy sensor used exept to sit on stuff +// Rev 2.2 Various sensor fixes +// Rev 2.3 Sets No Sensor in menu, must be started by hand +// Rev 2.4 - reserved for patches to 2.3 if needed +// Rev 3.0 Refactor out into subs, not states to make command injection easier +// New command: @appearance=Notecardname so you can switch to a new notecard on the fly +// New command: @speed=1.0 which slows up ( < 1 ) or speeds up ( > 1) +// Rev 3.1 Commands are not interruptible by Link Message +// Rev 3.2 Sensor patches for consistency in removing the NPC +// Rev 3.3 Added Touch command by Neo.Cortex@hbase42/hopto/org:8002 +// Added Menu 3 for notecard and appearance commands +// Rev 3.4 animation timer cannot be zero or it shuts off timer tweaked +// solves the NPC starting up when no sensor is set. +// Rev 3.5 fixes saving to !Path notecard +// Rev 3.6 08-11-2015 @delete acts like @stop. TYjhe NPC now rezzes after an @go back in where it was deleted +// Rev 3.7 08-11-2015 @attach command added to load an attachment from the inventory to the NPC +// Rev 3.8 08-17-2015 process queued commands one at a time without calling ProcessNPCLine on link message +//*******************************************************************// + +// Instructions on how to use this is at http://www.outworldz.com/opensim/posts/NPC/ +// This is an OpenSim-only script. +// Author: Ferd Frederix aka Fred Beckhusen - fred@mitsi.com + +//////////////////////////////////////////////////////////////////////////////////////////// +// Original code was Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // +/////////////////////////////////////////////////////////////////////////////////////////// +// Please see: http://www.gnu.org/licenses/gpl.html for legal details, // +// rights of fair usage, the disclaimer and warranty conditions. // +/////////////////////////////////////////////////////////////////////////////////////////// +// The original NPC controller was from http://was.fm/opensim:npc +// Extensive additions and bug fixes by Fred Beckhusem, aka Ferd Frederix, fred@mitsi.com +// llSensor had two params swapped +// @Wander would wander where it had rezzed, not where it was. +// There was no 'no_sensor' event in sit, so if a @sit failed, the NPC got stuck +// The animation and walks always stopped old, then started new. It should be start new, then stop old so the default stand would be suppressed. +// New code: +// Merged with new Route recorder and notecard writer +// If the NPC failed to reach a destination it never moved on. Added WAIT global to tune this +// Exposed many tunable variables and ported the code to LSLEditor. +// Added floating point to times in notecard. + +// Added @sound, @randsound, @whisper, @shout, and @cmd controls. +// +// notecards integers are not floats for better control +// +// Link Messages may be used to perform external control by injecting @commands into the stream of actions +// Example: +// To chat something, such as with a chat robot +// llMessageLinked(LINK_SET,0,"@npc_say=Hello",""); + +// This script assumes that NPCs and OSSl scripting is enabled in the OpenSim configuration. +// In order to enable them, the following changes must be made in the OpenSim.ini configuration file: +// +// ; Turn on OSSL +// AllowOSFunctions = true +// OSFunctionThreatLevel = Severe + +//[NPC] +// ;# {Enabled} {} {Enable Non Player Character (NPC) facilities} {true false} +// Enabled = true +// +// and then the server has to be restarted. + + +// Commands: All commands begin with an @ sign. All other lines are ignored +// @commands may have optional parameters. The syntax is always: +// @cmd=parm1|parm2 +// NaN in the table below meand Not a Number. This means there is no parameter + +//Command First Parameter Second Parameter Description +//@spawn name location (vector) Rezzes an NPC with name at a location. +//@appearance NoteCardName NaN switch the NPC appearance to a new notecard +//@walk destination (vector) NaN Makes the NPC walk to destination. +//@fly destination (vector) NaN Makes the NPC fly to destination. +//@land destination (vector) NaN Makes the NPC land at destination. +//@say string NaN Makes the NPC speak a phrase. +//@whisper string NaN Makes the NPC whisper a phrase. +//@shout string NaN Makes the NPC shout a phrase. +//@pause seconds (float) NaN Makes the NPC wait for a multiple of seconds. +//@wander radius (float) cycles (integer) Makes the NPC wander in radius, for cycles seconds. +//@delete NaN NaN Removes the NPC. Requires a link message to continue +//@goto label (string) NaN Jump to the label label in the script. +//@animate animation (string) time (float) Makes the NPC trigger the animation animation for time seconds. +//@sound sound_name NaN plays a sound from inventory +//@randsound NaN NaN Plays a random sound from inventory +//@rotate degrees (float) NaN Rotate the NPC degrees around the Z axis. +//@sit primitive name NaN Sit on a primitive with a given name. +//@touch primitive name NaN Touch on a primitive with a given name. +//@stand NaN NaN If sitting on a primitive, stand up. +//@cmd channel (integer) string Says string on channel, for controlling external gadgets +//@stop NaN NaN Halts the NPC script indefinitely. Can be started with a link message +//@go NaN NaN Continues on next notecard line, for use in link messages +//@speed speed (float) NaN from 0 to N, where 1.0 ius a normal speed of an avatar. 0.2 is a turtle. +//@notecard notename (string) NaN load a new Path notecard +//@attach InventoryName attachmentPoint load an attachment from the inventory to the NPC onto point + +// Constant attachmentPoint Comment +// ATTACH_CHEST 1 chest/sternum +// ATTACH_HEAD 2 head +// ATTACH_LSHOULDER 3 left shoulder +// ATTACH_RSHOULDER 4 right shoulder +// ATTACH_LHAND 5 left hand +// ATTACH_RHAND 6 right hand +// ATTACH_LFOOT 7 left foot +// ATTACH_RFOOT 8 right foot +// ATTACH_BACK 9 back +// ATTACH_PELVIS 10 pelvis +// ATTACH_MOUTH 11 mouth +// ATTACH_CHIN 12 chin +// ATTACH_LEAR 13 left ear +// ATTACH_REAR 14 right ear +// ATTACH_LEYE 15 left eye +// ATTACH_REYE 16 right eye +// ATTACH_NOSE 17 nose +// ATTACH_RUARM 18 right upper arm +// ATTACH_RLARM 19 right lower arm +// ATTACH_LUARM 20 left upper arm +// ATTACH_LLARM 21 left lower arm +// ATTACH_RHIP 22 right hip +// ATTACH_RULEG 23 right upper leg +// ATTACH_RLLEG 24 right lower leg +// ATTACH_LHIP 25 left hip +// ATTACH_LULEG 26 left upper leg +// ATTACH_LLLEG 27 left lower leg +// ATTACH_BELLY 28 belly/stomach/tummy +// ATTACH_LEFT_PEC 29 left pectoral +// ATTACH_RIGHT_PEC 30 right pectoral +// ATTACH_HUD_CENTER_2 31 HUD Center 2 +// ATTACH_HUD_TOP_RIGHT 32 HUD Top Right +// ATTACH_HUD_TOP_CENTER33 HUD Top +// ATTACH_HUD_TOP_LEFT 34 HUD Top Left +// ATTACH_HUD_CENTER_1 35 HUD Center +// ATTACH_HUD_BOTTOM_LEFT 36 HUD Bottom Left +// ATTACH_HUD_BOTTOM 37 HUD Bottom +// ATTACH_HUD_BOTTOM_RIGHT 38 HUD Bottom Right +// ATTACH_NECK 39 neck +// ATTACH_AVATAR_CENTER 40 avatar center/root + + + +////////////////////////////////////////////////////////// +// DEBUG // +////////////////////////////////////////////////////////// +integer debug = FALSE; // set to TRUE or FALSE for debug chat on various actions +integer Editor = FALSE; // set to to TRUE to working in LSLEditor, FALSE for in-world. + // you must also include the NPC commands found in the other script since LSLEditor does not support OpenSim +integer iTitleText = TRUE; // set to TRUE to see debug info in text above the controller + +////////////////////////////////////////////////////////// +// TUNABLE CONFIGURATION // +////////////////////////////////////////////////////////// +float TIMER = 2; // how often the system checks the distance traveled. Fastest you can go is 0.5 seconds +float QUICK = 1; // when we need to move to the next state, we use a QUICK timer +string Appearance = "!Appearance"; // The name of the recorded Appearance notecard +string Notecard = "!Path"; // The name of the recorded routes +integer allowUsers = FALSE; // If true, any user can get a Start NPC and Stop NPC menu. Only groups and owners can get all commands if TRUE, or FALSE +float MAXDIST = 2.0; // how close a NPC has to get to a dest pos to continue to next state. Do not lower this too much, as it may miss the target +integer WANDERRAND = TRUE; // set to TRUE and they will pause during wanders a random number of seconds +float WANDERTIME = 3.0; // how long they stand after each @wander,if WANDERRAND is FALSE. If WANDERRAND is TRUE, this is the max time +integer WAIT = 30; // wait for this number of seconds for the NPC to reach a destination (for safety). If it fails to reach a target, it will move on after this time. +float RANGE = 50; // 1 to N meters - anyone this close to the controller will start NPCS if Sensor button is clicked +float REZTIME = 2.0; // wait this long for NPC to rez in, then start the process +string STAND = "Stand"; // the name of the default Stand animation +string WALK = "Walk"; // the name of the default Walk animation +string FLY = "Fly"; // the name of the default Fly animation +string RUN = "Run"; // the name of the default Run animation +string LAND = "Land"; // the name of the default land animation ( for birds only) +float OffsetZ = 0.5; // appear 0.5 meter above ground, this is added to all destinations to keep them from sinking in. +float SPEEDMULT =0.5; // 1.0 = regular avatar speed. Smaller numbers slow down walks. Large numbers speed them up. +integer FLIGHT = 299; // For controlling wings. A channel that is shouted at when flight starts and ends. "flying" or "landing" + +// DESCRIPTIONS FIELDS HAVE TO SURVIVE A RESET +// These vars are stored by saving them with KeyValueSet +// "pr" is a 0 if it is set for Owner Only, 1 for Group control +// "se" is "on" if Started +// "co" = "R" or "A" for relative or absolute addressing mode +// "key" = NPC key + +// These Globals used to be stored in description. Moved to RAM in V1.6 +float RAMPause; // @pause param +float RAMwd ; // @wander distance +integer RAMwc; // @wander count +float RAMrot; // @rotate +string RAMsit; // @sit primname +string RAMtouch; // @touch primname +string RAManimationName; // @animate animation (string) time (float) +float RAManimationTime; + +// other globals section +integer iChannel; // a listen channel, randomly assigned +integer iHandle; // the handle to it + +// NPC controls +vector newDest ; // tmp storage for the walks +integer iWaitCounter ; // wait for this number of seconds for the NPC to reach a desrtination +string sNPCName; // the name of the NPC that may be in world. So we can remove it. +integer bNPC_STOP = FALSE; // boolean to reuse a listener +integer Stopped = FALSE; // set to TRUE by link messages so we do not remember them +float fTimerVal ; // how long we wait when wandering (calculated) +float NPCEnabled; // true if the NPC is suppodes to be running + +// OS_NPC_CREATOR_OWNED will create an 'owned' NPC that will only respond to osNpc* commands issued from scripts that have the same owner as the one that created the NPC. +// OS_NPC_NOT_OWNED will create an 'unowned' NPC that will respond to any script that has OSSL permissions to call osNpc* commands. +integer NPCOptions = OS_NPC_CREATOR_OWNED; // only yhe owner of this box can control this NPC. + +integer walkstate = 0; // helps us reshare the walk state for run, fly and land - a bit of a hack, but it saves RAM. Has to be done this way because some bits of NPCWalkOption are asserted as 0 + +integer NPCWalkOption; // Some notes for what happens to NPCWalkOption: +// OS_NPC_FLY - Fly the avatar to the given position. The avatar will not land unless the OS_NPC_LAND_AT_TARGET option is also given. +// OS_NPC_NO_FLY - Do not fly to the target. The NPC will attempt to walk to the location. If it's up in the air then the avatar will keep bouncing hopeless until another move target is given or the move is stopped +//OS_NPC_LAND_AT_TARGET - If given and the avatar is flying, then it will land when it reaches the target. If OS_NPC_NO_FLY is given then this option has no effect. +// OS_NPC_RUNNING - if given, NPC avatar moves at running/fast flying speed, otherwise moves at walking/slow flying speed. + +// menus +string mSensor="Sense is Off"; // Sensor or "No Sensor" + +list lAtButtons = ["Menu","-", ">>", "@run", "@walk", "@fly", "@land", "@wander", "@sit", "@stand","@animate","@rotate"]; +list lMenu2 = ["<<", "@comment", ">>>", "@stop", "@say", "@whisper","@shout","@sound","@randsound","@cmd", "@pause", "@delete"]; +list lMenu3 = ["<<<","@notecard","@appearance", "@touch", "@speed", "@attach", "-","-", "-", "-", "-", "-" ]; + +string sCommand; // place to store a command for two-prompted ones +string sParam2; // place to store a prompt for two-prompted ones +string priPub = "Owner Only"; // Private or Group +key kUserKey; // the person who is controlling the avatar, not the Owner +// the command lists +list lCommands; // commands are stored here +list lNPCScript; // Storage for the NPC script. +string npcAction; // Storage for the next action. @cmd=0|hello, this becomes @cmd +string npcParams; // Storage for the param, @cmd=0|hello, this becomes 0|hello + +// misc vars +string sNotecard; // commands are stored here temporarily for dumping +vector vWanderPos; // a place to wander to +string lastANIM ; // last animation run +// Sensor +integer avatarPresent; // Sensor sets this flag when people are within Range. + +// Coordinate control +vector vInitialPos ; // Vector that will be filled by the script with the initial starting position in region coordinates. +vector vDestPos = ZERO_VECTOR; // Storage for destination position. +string relAbs = "Relative"; // absolute vs relative positioning +vector lastKnownPos; // last known NPC position when we deleted it + +// STATES +integer MENU ; // processing a dialog box state, may be concurrent with STATE +integer STATE; // state storage +integer MakeNotecard = 1; // displaying a text box for NPC name +integer RecordPath = 2; // displaying a path notecard menu +integer NobodyHome = 3; // looking for an avatar +integer Spawning = 4; // spawning an avatar +integer Animate = 5; // animation timer needed +integer Walking = 6; // Hey! I am walking here! +integer Wander = 7; // Wandering around neeeds a timer, too +integer WanderHold = 8; // We reached a wander point +integer DoProcess = 9; // Set this to make it process a new command + +key gNpcKey = NULL_KEY; // global key storage for the one NPC, to save CPU cycles +list Stack ; // a command stack from link message input + +integer SensorFunc = 0; // define which function shall be triggered inside the sensor function + // 0 means none, 1 sit, 2 touch +/////////////////////////////////////////////////////////////////////////// +// FUNCTIONS // +/////////////////////////////////////////////////////////////////////////// + +// Do* functions are much like states from the old V2 scripts. + +// Save a Path notecard +DoSave() +{ + STATE = MakeNotecard; + makeText("Stand where you want the NPC to appear, and enter the NPC Name"); +} + +// This function is used to record the path for the NPC +// Each command can take 0, 1, or 2 params +DoMenuForCommands() { + makeMenu(lAtButtons); +} + + +// No one is here when sensors were on, so we kill off the NPC +DoNobodyHome() +{ + DEBUG("Nobody Home"); + STATE = NobodyHome; + if (NPCKey() != NULL_KEY) { + osNpcRemove(NPCKey()); + SaveKey(NULL_KEY); + } + TimerEvent(5); // keep ticking to sense avatars +} + +// Create a NPC +DoSpawn() { + DEBUG("state spawn"); + NPCEnabled = TRUE; // in world + // see if there is already one out there. + if (NPCKey() != NULL_KEY) { + DEBUG("Already living"); + return; + } + + STATE = Spawning; + + list name = llParseString2List(sNPCName, [" "], []); + + if (relAbs == "Relative"){ + vInitialPos += llGetPos(); + } + + DEBUG("Rezzing the NPC:" + (string) vInitialPos); + key aKey = osNpcCreate(llList2String(name, 0), llList2String(name, 1), vInitialPos, Appearance, NPCOptions); + + SaveKey(aKey ); // save in desceription and global, too + + osSetSpeed(aKey,SPEEDMULT); // 1.9 speed multiplier + TimerEvent(REZTIME); + NPCAnimate(STAND); +} + +DoRotate() { + DEBUG("@rotate=" + (string) RAMrot); + osNpcSetRot(NPCKey(), llEuler2Rot(<0,0,RAMrot> * DEG_TO_RAD)); +} + +DoSit() { + DEBUG ("state sit - looking for " + RAMsit); + SensorFunc = 1; //triggers osNpcSit + llSensor(RAMsit, "", PASSIVE|ACTIVE|SCRIPTED, 96, PI); +} + +DoTouch() { + DEBUG ("state touch - looking for " + RAMtouch); + SensorFunc = 2; //triggers osNpcTouch + llSensor(RAMtouch, "", PASSIVE|ACTIVE|SCRIPTED, 96, PI); +} + +DoStand() { + DEBUG("state stand"); + osNpcStand(NPCKey()); +} + + +DoAnimate() { + + DEBUG("state animate"); + STATE = Animate; + NPCAnimate(RAManimationName); + if (RAManimationTime <=0 ) // V 3.4 tweak + RAManimationTime = 1; + TimerEvent(RAManimationTime); +} + +DoWalk() { + + DEBUG("NPCWalkOption = " + (string) NPCWalkOption); + STATE = Walking; + + // walk, fly, run, land + if (walkstate == 1) { + NPCAnimate(WALK); + } else if (walkstate == 2) { + llShout(FLIGHT,"flying"); + NPCAnimate(FLY); + } else if (walkstate == 3) { + NPCAnimate(RUN); + } else if (walkstate == 4) { + NPCAnimate(LAND); + } + newDest = vDestPos ; + newDest.z += OffsetZ; + + // notecard is stored as offsets from this box with relative addressing. Convert to absolute + if (relAbs == "Relative"){ + newDest += llGetPos(); + } + + DEBUG("Moveto:" + (string) newDest); + osNpcMoveToTarget(NPCKey(), newDest, NPCWalkOption); + iWaitCounter = WAIT; // wait 60 seconds to get to a destination. + TimerEvent(TIMER); +} + + +DoWander(){ + DEBUG("state wander"); + STATE = Wander; + + vector point = CirclePoint(RAMwd); + DEBUG("CirclePoint:" + (string) point); + vWanderPos = vDestPos + point; + DEBUG("vWanderPos:" + (string) vWanderPos); + + fTimerVal = WANDERTIME; // default time to pause after each wander + if (WANDERRAND) + fTimerVal = llFrand(WANDERTIME) + 1; // override, they want random times + + NPCAnimate(WALK); + + DEBUG("Wander to:" + (string) vWanderPos); + + osNpcMoveToTarget(NPCKey(), vWanderPos, NPCWalkOption); + iWaitCounter = WAIT; // wait 60 seconds to get to a destination. + TimerEvent(TIMER); +} + +DoWanderhold() { + + DEBUG("Wander Hold"); + STATE = WanderHold; + + // now that we have reached a wander spot, slow the timer down to the desired value + TimerEvent(fTimerVal); +} + +// @pause=10 will do nothing for 10 seconds +DoPause() { + if (RAMPause < 0.1) + RAMPause = 0.1; + DEBUG("@pause=" + (string)RAMPause); + TimerEvent(RAMPause); +} + + +// @stop makes the NPC stop moving in whatever state it is in. You have to linkmessage to get moving again +DoStop() { + DEBUG("NPC is Stopped"); + Stopped = TRUE; // Link controlled - we mnust have a @go to continue with notecards + TimerEvent(0); + Stack = []; // v3.8 +} + +// @delete removes the NPC forever. Next command starts it up again at the beginning +DoDelete() { + DEBUG("state delete"); + + osNpcRemove(NPCKey()); + SaveKey(NULL_KEY); + Stopped = TRUE; // Link controlled - we mnust have a @go to continue with notecards + TimerEvent(0); + Stack = []; // v3.8 +} + +// change the appearance of the NPC +DoAppearance(string notecard) { + DEBUG("state appearance"); + if (llGetInventoryType(notecard) == INVENTORY_NOTECARD){ + DEBUG("Load appearance " + notecard); + osNpcLoadAppearance(NPCKey(),notecard); + } +} + +// Change the avatar speed +DoSpeed(string speed) { + float newspeed = (float) speed; + if (newspeed > 0.1 && newspeed < 5.0) // sanity check + osSetSpeed(NPCKey(),newspeed); +} +DoNewNote (string card) { + DEBUG("Load Notecard " + card); + NPCReadNoteCard(card); + Stopped = FALSE; +} +DoAttach(string params) { + + list Data = llParseString2List(params, ["|"], []); + string itemName = llList2String(Data, 0); + integer attachmentPoint = (integer) llList2String(Data, 1); + if (attachmentPoint > 0 + && attachmentPoint < 40 + && llGetInventoryType(itemName) == INVENTORY_OBJECT + ) + { + osForceAttachToOtherAvatarFromInventory(NPCKey(),itemName,attachmentPoint); + } +} + +// This loops over the notecard, processing each command +DoProcessNPCLine() { + DEBUG("ProcessNPCLine"); + STATE = 0; + + // auto load a notecard + if (! llGetListLength(lNPCScript)) { + DEBUG("Read Notecard"); + NPCReadNoteCard(Notecard); + Stopped = FALSE; + } + + // look for link messages on the stack + string next = llList2String(Stack,0); // lets see if there is anithing from a link message + if (llStringLength(next)) + { + Stack = llDeleteSubList(Stack,0,0); + ProcessCmd(next); //lets do this command instead. + return; + } + + // @stop issued? + if (Stopped) { + TimerEvent(0); + DEBUG("Waiting for input"); + return; + } + + // No, we have an @go for liftoff + next = llList2String(lNPCScript, 0); // get the next command + DEBUG("Execute:" + next); + lNPCScript = llDeleteSubList(lNPCScript, 0, 0); // delete it + + if (llGetListLength(lNPCScript) == 0) { + DEBUG("EOF"); + } + ProcessCmd(next); + +} + + + +ProcessCmd(string cmd) { + + DEBUG("ProcessCmd:" + cmd); + + if (llGetSubString(cmd, 0, 0) != "@") { + DEBUG("ignoring"); + STATE = DoProcess; + TimerEvent(QUICK); // this is so we do not recurse the stack + return; + } + + list data = llParseString2List(cmd, ["="], []); + npcAction = llToLower(llStringTrim(llList2String(data, 0), STRING_TRIM)); + + DEBUG("Action:" + npcAction); + npcParams = llStringTrim(llList2String(data, 1), STRING_TRIM); + + @commands; + + ProcessSensor(); + + + if(npcAction == "@spawn") { + DEBUG("@spawn"); + list spawnData = llParseString2List(npcParams, ["|"], []); + sNPCName =llList2String(spawnData, 0); // V 1.6 name in RAM + + list spawnDest = llParseString2List(llList2String(spawnData, 1), ["<", ",", ">"], []); + vInitialPos.x = llList2Float(spawnDest, 0); + vInitialPos.y = llList2Float(spawnDest, 1); + vInitialPos.z = llList2Float(spawnDest, 2); + + DEBUG("Coords for NPC at " + (string) vInitialPos); + DoSpawn(); + return; + } + + if (! avatarPresent){ + DoNobodyHome(); + DEBUG("No avatar nearby"); + return; + } else { + if ( NPCKey() == NULL_KEY) { + DoSpawn(); + } + } + + if(npcAction == "@stop") { + DoStop(); + return; + } + else if(npcAction == "@goto") { + DEBUG("goto"); + integer lastIdx = llGetListLength(lNPCScript)-1; + lNPCScript = llDeleteSubList(lNPCScript, lastIdx, lastIdx); + // Wind commands till goto label. + @wind; + string next1 = llList2String(lNPCScript, 0); + lNPCScript = llDeleteSubList(lNPCScript, 0, 0); + lNPCScript += next1; + if(next1 != npcParams) jump wind; + // Wind the label too. + next1 = llList2String(lNPCScript, 0); + lNPCScript = llDeleteSubList(lNPCScript, 0, 0); + lNPCScript += next1; + // Get next command. + list data1 = llParseString2List(next1, ["="], []); + npcAction = llToLower(llStringTrim(llList2String(data1, 0), STRING_TRIM)); + npcParams = llStringTrim(llList2String(data1, 1), STRING_TRIM); + // Reschedule. + jump commands; + } + else if(npcAction == "@sound") { + DEBUG("sound"); + llTriggerSound(npcParams,1.0); + } + else if(npcAction == "@randsound") { + DEBUG("@randsound"); + integer N = llGetInventoryNumber(INVENTORY_SOUND); + integer rand = llCeil(llFrand(N)) -1; // pick a random sound + string toPlay = llGetInventoryName(INVENTORY_SOUND,rand); + llTriggerSound(toPlay,1.0); + } + else if(npcAction == "@walk") { + DEBUG("@walk"); + GetDest(npcParams); + walkstate = 1;// walking + NPCWalkOption = OS_NPC_NO_FLY ; + DoWalk(); + return; + } + else if(npcAction == "@fly") { + GetDest(npcParams); + walkstate = 2;// flying + NPCWalkOption = OS_NPC_FLY ; + DoWalk(); + return; + } + else if(npcAction == "@run") { + DEBUG("@run"); + GetDest(npcParams); + walkstate = 3;// running + NPCWalkOption = OS_NPC_NO_FLY | OS_NPC_RUNNING; + DoWalk(); + return; + } + else if(npcAction == "@land") { + DEBUG("@land"); + GetDest(npcParams); + walkstate = 4;// landing + NPCWalkOption= OS_NPC_FLY | OS_NPC_LAND_AT_TARGET ; + DoWalk(); + return; + } + else if(npcAction == "@say") { + DEBUG("@say " + npcParams); + osNpcSay(NPCKey(), 0, npcParams); + } + else if(npcAction == "@shout") { + DEBUG("@shout"); + osNpcShout(NPCKey(),0, npcParams); + } + else if(npcAction == "@whisper") { + DEBUG("@whisper " + npcParams); + osNpcWhisper(NPCKey(),0, npcParams); + } + // speak a command on a channel, so you can open doors and control stuff. + else if(npcAction == "@cmd") { + DEBUG("@cmd"); + list dataToSpeak = llParseString2List(npcParams, ["|"], []); + string channel = llList2String(dataToSpeak,0); + DEBUG("Channel:"+(string) channel); + integer iChannel = (integer) channel; + string stringToSpeak = llList2String(dataToSpeak,1); + llSay(iChannel, stringToSpeak); + } + // stop everything + else if(npcAction == "@pause") { + RAMPause = (float) npcParams; + DoPause(); + return; + } + else if(npcAction == "@wander") { + list wanderData = llParseString2List(npcParams, ["|"], []); + RAMwd = (float) llList2String(wanderData, 0); + RAMwc = (integer) llList2String(wanderData, 1); + vDestPos = osNpcGetPos(NPCKey()); // set the wander start + DEBUG("Starting at " + (string) vDestPos); + DoWander(); + return; + } + else if(npcAction == "@rotate") { + RAMrot = (float) npcParams; + DoRotate(); + } + else if(npcAction == "@sit") { + RAMsit= npcParams; + DoSit(); + return; + } + else if(npcAction == "@touch") { + RAMtouch= npcParams; + DoTouch(); + return; + } + else if(npcAction == "@stand") { + DoStand(); + } + else if(npcAction == "@delete") { + DoDelete(); + return; + } + else if(npcAction == "@animate") { + list animateData = llParseString2List(npcParams, ["|"], []); + RAManimationName = llList2String(animateData, 0); + RAManimationTime = (float) llList2String(animateData, 1); + DoAnimate(); + return; + } + else if(npcAction == "@appearance" ){ + DoAppearance(npcParams); + } + else if (npcAction =="@speed") { + DoSpeed(npcParams); + } + else if (npcAction =="@notecard") { + DoNewNote(npcParams); + Notecard = npcParams; + } + else if (npcAction == "@attach") + { + DoAttach(npcParams); + } + + STATE = DoProcess; + TimerEvent(QUICK); // yeah I know, not possible this fast, we just go as fast as we can go - this is so we do not recurse the stack +} + + + +/////////////////// UTILITY Functions, not state-like ////////////////// + +// DEBUG(string) will chat a string or display it as hovertext if debug == TRUE +DEBUG(string str) { + if (debug) + llOwnerSay( str); // Send the owner debug info so you can chase NPCS + if (iTitleText) { + llSetText(str,<1.0,1.0,1.0>,1.0); // show hovertext + + } +} + +GetDest(string npcParams) { + list dest = llParseString2List(npcParams, ["<", ",", ">"], []); + vDestPos.x = llList2Float(dest, 0); + vDestPos.y = llList2Float(dest, 1); + vDestPos.z = llList2Float(dest, 2); +} + +NPCReadNoteCard(string Note) { + DEBUG("NPCReadNoteCard"); + lNPCScript = llParseString2List(osGetNotecard(Note), ["\n"], []); +} + +integer SenseAvatar() +{ + //Returns a strided list of the UUID, position, and name of each avatar in the region + list avatars = llGetAgentList(AGENT_LIST_REGION ,[]); + integer numOfAvatars = llGetListLength(avatars); + if (numOfAvatars == 0) + { + DEBUG("No people"); + return 0; + } + //DEBUG("Located " + (string)numOfAvatars + " avatars and NPC's"); + + integer nAvatars; + integer i; + for( i = 0;i < numOfAvatars; i++) { + key aviKey = llList2Key(avatars,i); + if (!osIsNpc(aviKey)) { + list detail = llGetObjectDetails(aviKey,[OBJECT_POS]); + vector pos = llList2Vector(detail,0); + float dist = llVecDist(pos, llGetPos()); + if (dist < RANGE) + { + nAvatars++; + DEBUG("In range:" + llKey2Name(aviKey)); + } + } + } + //DEBUG("Located " + (string) nAvatars + " avatars"); + return nAvatars; +} + +// return TRUE if the avatar is owner when private is set, or TRUE if the avatar is in the same group and GROUP is set. +integer checkPerms() { + + integer group = (integer) KeyValueGet("pr"); + if (! group) + priPub = "Owner Only"; + else + priPub = "Group"; + + + if (llDetectedKey(0) == llGetOwner()){ + kUserKey = llDetectedKey(0); + return TRUE; + } + + if ( group && llDetectedGroup(0)) { + kUserKey = llDetectedKey(0); + return TRUE; + } + kUserKey = llDetectedKey(0); + return FALSE; +} + + + +NPCAnimate(string anim) +{ + DEBUG("Start Anim: " + anim); + if (llGetInventoryType(anim) == INVENTORY_ANIMATION ) { + + if (lastANIM != anim) { + if(llStringLength(lastANIM)) { + osNpcStopAnimation(NPCKey(), lastANIM); + } + osNpcPlayAnimation(NPCKey(), anim); + lastANIM = anim; + } + } else { + llSay(DEBUG_CHANNEL, "No animation named " + anim); + } +} + + +TimerEvent(float timesent) +{ + DEBUG("Setting timer: " + (string) timesent); + llSetTimerEvent(timesent); +} + +// Kill a NPC by Name +Kill(string param) +{ + integer count; + list avatars = osGetAvatarList(); // Returns a strided list of the UUID, position, and name of each avatar in the region except the owner.\ + integer i; + integer j = llGetListLength(avatars); + for (i=0 ; i <= j; i+=3){ + + string desired = llList2String(avatars,i+2); + desired = llStringTrim(desired,STRING_TRIM); // should not be needed but is needed + + if (desired == param){ + vector v = llList2Vector(avatars,i+1); + key target = llList2Key(avatars,i); // get the UUID of the avatar + osNpcRemove(target); + SaveKey(NULL_KEY ); + llOwnerSay("Removed " + param+ " at location " + (string) v); + count++; + } + } + + NPCEnabled = FALSE; // not in world + + if (count) + llOwnerSay("Removed " + (string) count + " NPC's"); + else + llOwnerSay("Could not locate " + param); +} + + +// return a String for the position we are at. Strings used as the caller wants strings +string Pos() +{ + vector where = llGetPos(); // find the box position + + where.z += OffsetZ; // use the ground position + an offset + + if (Editor) + where = <128,128,31 + llFrand(1)>; // center of sim for editing + + // if attached the height will be too high by 1/2 the agent size + if (llGetAttached()) { + vector size = llGetAgentSize(llGetOwner()); + float Z = size.z; + where.z -= Z/2; + } + + // DEBUG("Pos= " + (string) where); + return (string) where; +} + +// setup a menu with a timer for timeouts, called by all make*() +menu() +{ + llListenRemove(iHandle); + iChannel = llCeil(llFrand(100000) + 20000); + iHandle = llListen(iChannel,"","",""); + TimerEvent(30.0); + MENU = TRUE; +} + +// make a text box +makeText(string Param) +{ + menu(); + llTextBox(kUserKey, Param, iChannel); +} + +// top level menu +makeMainMenu() +{ + menu(); + list buttons = ["Appearance","Recording","Save","Help","-","Erase RAM", priPub,relAbs,"-","Stop NPC",mSensor,"Start NPC"]; + llDialog(kUserKey,(string) llGetListLength(lCommands) + " Records",buttons,iChannel); +} + + +// Rev 1.4 +// top level menu for non group/ non owners +makeUserMenu() +{ + if (!allowUsers) return; + + menu(); + list buttons = ["Start NPC","Stop NPC"]; + llDialog(kUserKey,"Choose",buttons,iChannel); +} + + + +// programmable menu for @commands +makeMenu(list buttons) +{ + menu(); + llDialog(kUserKey,(string) llGetListLength(lCommands) + " Record",buttons,iChannel); +} + + +// make one or two text boxes with prompts +Text(string cmd, string p1, string p2) +{ + sCommand = cmd; + sParam2 = ""; + if (llStringLength(p2)) + sParam2 = p2; + + makeText(p1); +} + +// Set the Avatar Present flag - if sensors are off and we are forece run, there will be one present. +ProcessSensor() +{ + integer SensorOn; + if ("on" == KeyValueGet("se")) + { + SensorOn = TRUE; // we need to scan for avatars + } else { + SensorOn = FALSE; // we need to scan for avatars + } + DEBUG("Sensor:" + (string) SensorOn); + + integer n = SenseAvatar(); + + DEBUG("Avatars:" + (string) n); + if (SensorOn && n) + avatarPresent = TRUE; // someone is here and we need to tell the system to run + else if (SensorOn && !n) + avatarPresent = FALSE; // someone is not here and we need to tell the system to stop + else { // sensor is off, lete see if there is a NPC. If so, we are ON + DEBUG("NPCEnabled:" + (string) NPCEnabled); + if (NPCEnabled) + avatarPresent = TRUE; + else + avatarPresent = FALSE; + } + + // start up from when when no one is near + if (avatarPresent && STATE == NobodyHome) + STATE = 0; + + //DEBUG("Avatar Present: " + (string) avatarPresent); +} + +vector CirclePoint(float radius) { + float x = llFrand(radius *2) - radius; // +/- radius, randomized + float y = llFrand(radius *2) - radius; // +/- radius, randomized + return ; // so this should always happen +} + +string KeyValueGet(string var) { + list dVars = llParseString2List(llGetObjectDesc(), ["&"], []); + do { + list data = llParseString2List(llList2String(dVars, 0), ["="], []); + string k = llList2String(data, 0); + if(k != var) jump continue; + //DEBUG("got " + var + " = " + llList2String(data, 1)); + return llList2String(data, 1); + @continue; + dVars = llDeleteSubList(dVars, 0, 0); + } while(llGetListLength(dVars)); + return ""; +} + +KeyValueSet(string var, string val) { + + //DEBUG("set " + var + " = " + val); + list dVars = llParseString2List(llGetObjectDesc(), ["&"], []); + if(llGetListLength(dVars) == 0) + { + llSetObjectDesc(var + "=" + val); + return; + } + list result = []; + do { + list data = llParseString2List(llList2String(dVars, 0), ["="], []); + string k = llList2String(data, 0); + if(k == "") jump continue; + if(k == var && val == "") jump continue; + if(k == var) { + result += k + "=" + val; + val = ""; + jump continue; + } + string v = llList2String(data, 1); + if(v == "") jump continue; + result += k + "=" + v; + @continue; + dVars = llDeleteSubList(dVars, 0, 0); + } while(llGetListLength(dVars)); + if(val != "") result += var + "=" + val; + llSetObjectDesc(llDumpList2String(result, "&")); +} + + +// clear RAM +Clr() { + + lCommands = []; + llOwnerSay("RAM Memory cleared. Notecards, if any, are not modified."); + makeMainMenu(); +} + +integer checkNoteCards() +{ + // Check that they have saved an Appeaance and Path notecard + integer num = llGetInventoryNumber(INVENTORY_NOTECARD); // how many notecards overall + + integer i; + integer count; + for (; i < num; i++){ + if (llGetInventoryName(INVENTORY_NOTECARD,i) == Notecard) + count++; + if (llGetInventoryName(INVENTORY_NOTECARD,i) == Appearance) + count++; + } + DEBUG("Checked " + (string) count + " Notecards"); + // if we have both, run the NPC + return count; +} + +Update(string SName) { + + // delete all NPC*scripts except myself + integer i; + integer j = llGetInventoryNumber(INVENTORY_SCRIPT); + for (i = 0; i < j; i++) { + string name = llGetInventoryName(INVENTORY_SCRIPT,i); + string match = llGetSubString(name,0,2); + if (match == SName && llGetScriptName() != name) + { + llRemoveInventory(name); + llOwnerSay("Upgraded"); + } + } + +} + +// Get all default saved params from the Description +GetSwitches() +{ + string rA = KeyValueGet("co"); // Get the remembered menu setting for Abs Vs Relative + if (rA == "A") + relAbs = "Absolute"; + else if (rA == "R") + relAbs = "Relative"; + else + relAbs = "Absolute"; + + + // reenable NPC if sensor is on. + if ("on" == KeyValueGet("se")) + { + NPCEnabled = TRUE; + mSensor = "Sense is On"; + ProcessSensor(); // fake 1 avatar to get it rezzed + } else { + mSensor = "Sense is Off"; + } + } + + +SaveKey(key akey) +{ + DEBUG("Saving Key of " + (string) akey); + KeyValueSet("key", akey); + if (akey != (key) KeyValueGet("key") ) + { + DEBUG("Fatal error, cannot save key"); + } + gNpcKey = akey; +} + + +key NPCKey() +{ + key akey = gNpcKey; // from cached copy + // gNpcKey saves a lot of CPU processing by caching the key, if blank we get it from the description + if (gNpcKey == NULL_KEY) + { + //DEBUG("Get DKey"); + akey = KeyValueGet("key"); // from Description of the prim + } + // DEBUG("NPC KEY:" + (string) akey); + return akey; +} + + +/////////////////// CODE BEGINS ////////////////// + + +default +{ + changed(integer change) { + if(change & CHANGED_REGION_START) { + llResetScript(); + } + } + + on_rez(integer start_param) + { + llResetScript(); + } + + state_entry() { + + llSetText("",<1,1,1>,1.0); // clr all hovertext- we may not be using it. + DoDelete(); // kill any NPC that is out running + Update("NPC"); // If dragged and ropped into a prim with any script named "NPC...", this will replace it. + GetSwitches(); // Get all default saved params from the Description + llSetTimerEvent(TIMER); + } + + + touch_start(integer n) + { // if touched, make a menu + + if (checkPerms()) { + if (RecordPath == STATE) { + makeMenu(lAtButtons); + } else { + makeMainMenu(); + } + } else { + makeUserMenu(); + } + } + + // menu listener + listen(integer iChannel, string name, key id, string message) { + + if (MENU) { + llListenRemove(iHandle); + MENU = 0; // menu is off + iHandle = 0; + } + + if (message == "Stop NPC") + { + lNPCScript = []; // force reload of notecard + NPCEnabled = FALSE; + if (NPCKey() != NULL_KEY){ + Kill(sNPCName); + sNPCName = ""; + } else { + bNPC_STOP = TRUE; + makeText("Enter name of an NPC to stop"); + } + } + else if (message == "Menu" ) { + makeMainMenu(); + } + else if (message == "Erase RAM"){ + Clr(); + } + else if (message == "Relative"){ + relAbs = "Absolute"; + KeyValueSet("co","A"); // remember coordinates = A + Clr(); + } + else if (message == "Absolute"){ + relAbs = "Relative"; + KeyValueSet("co","R"); // remember coordinates = R + Clr(); + } + else if (message == "Recording"){ + DoMenuForCommands(); // show them the recording menu + } + else if (message == "Owner Only") { + priPub = "Group"; + KeyValueSet("pr","1"); + + llOwnerSay("Group members have control"); + makeMainMenu(); + } + else if (message == "Group") { + priPub = "Owner Only"; + KeyValueSet("pr","0"); + llOwnerSay("Only you have control"); + makeMainMenu(); + } + else if (message == "Sense is On") { + mSensor ="Sense is Off"; + KeyValueSet("se", "off"); + llOwnerSay(mSensor); + makeMainMenu(); + } + else if (message == "Sense is Off") { + mSensor ="Sense is On"; + llOwnerSay(mSensor); + KeyValueSet("se", "on"); + + NPCEnabled = FALSE; + + integer count = checkNoteCards(); + if (count >= 2) { + DEBUG("Notecards approved , calling DoProcessNPCLine"); + DoProcessNPCLine(); + return; + } + if (Editor) { + DoProcessNPCLine(); + return; + } + + llOwnerSay("You have not saved a recording and/or appearance, so you cannot start a NPC"); + makeMainMenu(); + } + else if (message == "Appearance") { + llRemoveInventory(Appearance); // delete the notecard + osAgentSaveAppearance(kUserKey,Appearance); // make the ntecard + llOwnerSay("Your outfit has been saved"); + makeMainMenu(); + } + else if (message == "Save") { + if (llGetListLength(lCommands) == 0) { + llOwnerSay("Nothing recorded, you need to make a recording first"); + makeMainMenu(); + return; + } + DoSave(); + } + else if (message == "Help"){ + llLoadURL(kUserKey,"Click to view help","http://www.outworldz.com/opensim/posts/NPC/"); + makeMainMenu(); + } + else if (message == "Start NPC") { + integer count = checkNoteCards(); + Stopped = FALSE; // Let's run the notecard + NPCEnabled = TRUE; + + if (Editor) { + DoProcessNPCLine(); + return; + } + + if (count >= 2) { + DEBUG("Notecards approved , calling DoProcessNPCLine"); + Stopped = FALSE; // Let's run the notecard + DoProcessNPCLine(); + return; + } + + llOwnerSay("You have not saved a recording or maybe an appearance, so we cannot start a NPC"); + + } + else if (bNPC_STOP){ + bNPC_STOP = FALSE; + Kill(message); + } + else if (message == ">>"){ + makeMenu(lMenu2); + } + else if (message == ">>>"){ + makeMenu(lMenu3); + } + else if (message == "<<") { + makeMenu(lAtButtons); + } + else if (message == "<<<") { + makeMenu(lMenu2); + } + else if (message == "@comment"){ + Text("# ","Enter a comment",""); + } + else if (message == "@stop"){ + lCommands += "@stop"+ "\n"; + makeMenu(lAtButtons); + } + else if (message == "@run"){ + lCommands += "@run=" + Pos() + "\n"; + llOwnerSay("Recorded position: " + Pos()); + makeMenu(lAtButtons); + } + else if (message == "@fly"){ + lCommands += "@fly=" + Pos() + "\n"; + llOwnerSay("Recorded position: " + Pos()); + makeMenu(lAtButtons); + } + else if (message == "@land"){ + lCommands += "@land=" + Pos() + "\n"; + llOwnerSay("Recorded position: " + Pos()); + makeMenu(lAtButtons); + } + else if (message == "@walk") { + lCommands += "@walk=" + Pos() + "\n"; + llOwnerSay("Recorded position: " + Pos()); + makeMenu(lAtButtons); + } + else if (message == "@stop"){ + lCommands += "@stop"+ "\n"; + makeMenu(lAtButtons); + } + else if (message == "@sound"){ + Text("@sound=","Enter a sound name or UUID to trigger",""); + } + else if (message == "@randsound"){ + lCommands += "@randsound"+ "\n"; + makeMenu(lAtButtons); + } + else if (message == "@say") { + Text("@say=","Enter what the NPC will say",""); + } + else if (message == "@whisper"){ + Text("@whisper=","Enter what the NPC will whisper",""); + } + else if (message == "@shout"){ + Text("@shout=","Enter what the NPC will shout",""); + } + else if (message == "@wander") { + Text("@wander=","Enter radius to wander","Enter number of wanders"); + } + else if (message == "@pause") { + Text("@pause=","Enter time to pause",""); + } + else if (message == "@rotate") { + Text("@rotate=","Enter degrees to rotate",""); + } + else if (message == "@sit"){ + Text("@sit=","Enter name of object to sit on",""); + } + else if (message == "@touch"){ + Text("@touch=","Enter name of object to touch",""); + } + else if (message == "@cmd"){ + Text("@cmd=","Enter cjhannel to speak on","Enter text to speak"); + } + else if (message == "@stand"){ + lCommands += "@stand\n"; + llOwnerSay("Stand Recorded"); + makeMenu(lAtButtons); + } + else if (message == "@animate"){ + Text("@animate=","Enter animation name to play","Enter time to play the animation"); + } + else if (message == "@attach"){ + Text("@animate=","Enter inventory name to attach","Enter number of the attachment point (1-40)"); + } + else if (message == "@speed"){ + Text("@speed=","Enter a speed for the NPC, 1=100% normal speed, 0.5=50% speed",""); + } + + + // Save NPC name + else if (MakeNotecard == STATE) { + sNPCName = message; // in case we need to kill it. + + vector vDest = (vector) Pos(); + + if (relAbs == "Relative") + { + vDest -= llGetPos(); // just an offset for relative + } + sNotecard = "@spawn=" + message + "|" + (string) vDest + "\n"; + integer i; + integer j = llGetListLength(lCommands); + for (; i < j; i++){ + // get the command to save to the notecard + string line = llList2String(lCommands,i); + if (relAbs == "Absolute") { + sNotecard += line; // add the un-modified string to the notecard + } else { + // since we have to record absolute coords since we do not know where the box goes until they press Save, + // we process the absolute to relative conversion for walks here + list parts = llParseString2List(line,["="],[]); //get the @command + + if (llList2String(parts,0) == "@walk") { + vector vec = (vector) llList2String(parts,1) - llGetPos(); + sNotecard += "@walk=" + (string) vec + "\n"; + } + else if (llList2String(parts,0) == "@fly") { + vector vec = (vector) llList2String(parts,1) - llGetPos(); + sNotecard += "@fly=" + (string) vec + "\n"; + } + else if (llList2String(parts,0) == "@run") { + vector vec = (vector) llList2String(parts,1) - llGetPos(); + sNotecard += "@run=" + (string) vec + "\n"; + } + else if (llList2String(parts,0) == "@land") { + vector vec = (vector) llList2String(parts,1) - llGetPos(); + sNotecard += "@land=" + (string) vec + "\n"; + } + else { + sNotecard += line; // add the un-modified string to the notecard + } + } + } + llRemoveInventory(Notecard); // delete the old notecard + osMakeNotecard(Notecard,sNotecard); // Makes the notecard. + llSay(0,sNotecard); + llOwnerSay("Commands notecard has been written"); + STATE = 0; + } // MakeNotecard + + else if (! llStringLength(sParam2)) { + lCommands += sCommand + message + "\n"; + llOwnerSay("Recorded"); + makeMenu(lAtButtons); + } + else if (llStringLength(sParam2)){ + sCommand = sCommand + message + "|"; + llOwnerSay("Recorded"); + makeText(sParam2); + sParam2 = ""; + } + + } + + + + timer(){ + // DEBUG("tick"); + + // if llDialog is up, kill the listener for the dialog box. + if (iHandle) { + llOwnerSay("Menu timed out"); + llListenRemove(iHandle); + iHandle = 0; + return; // ^^^^^^^^^^^^^^^^^^^^^^^ + } + // if NoBodyHome, we are sensing for an avatar + else if (NobodyHome == STATE) { + ProcessSensor(); + return; + } + // if we are spawning, we need time to rez the NPC, then start processing NPC Commands. + else if (Spawning == STATE) { + STATE = 0; + TimerEvent(TIMER); + } + // We end aniamtions with a timer + else if (Animate == STATE){ + NPCAnimate(STAND); + TimerEvent(TIMER); + } + + else if (Walking == STATE) { + if (--iWaitCounter) { + if (llVecDist(osNpcGetPos(NPCKey()), newDest) > MAXDIST) { + return; + } + } + + DEBUG("At Destination: " + (string) newDest); + + // walk, fly, run, land + if (walkstate == 1) { + NPCAnimate(STAND); + NPCWalkOption = OS_NPC_NO_FLY; + } else if (walkstate == 2) { + // nothing + } else if (walkstate == 3) { + NPCAnimate(STAND); + NPCWalkOption = OS_NPC_NO_FLY; + } else if (walkstate == 4) { + llShout(FLIGHT,"landing"); + NPCAnimate(STAND); + NPCWalkOption = OS_NPC_NO_FLY; + } + } + // Wandering timer + else if (Wander == STATE) { + if (--iWaitCounter) { // wait 60 seconds to get to a destination. + if (llVecDist(osNpcGetPos(NPCKey()), vWanderPos) > MAXDIST) + return; + } + + // see if wander counter == 0, if so, stop walking, go to stand and process next line + if(RAMwc == 0) { + NPCAnimate(STAND); + DEBUG("Wander ended, calling DoProcessNPCLine"); + STATE = 0; + DoProcessNPCLine(); + return; + } + // one less time to wander around + RAMwc--; + NPCAnimate(STAND); + TimerEvent(TIMER); + DoWanderhold(); + return; + } + // Wandering requires us to re-wander when we reach a destination + else if (WanderHold == STATE) { + DoWander(); + TimerEvent(TIMER); + return; + } + else if (DoProcess == STATE) { + TimerEvent(QUICK); + } + + STATE = 0; + + // We always process a NPC line at end of timer. + DEBUG("Tick end, calling DoProcessNPCLine"); + DoProcessNPCLine(); + } + + // sensors are used for sitting on prims + // Neo Cortex: added different SensorFunc states to trigger sit or touch + sensor(integer num) { + if (SensorFunc == 1) { + osNpcSit(NPCKey(), llDetectedKey(0), OS_NPC_SIT_NOW); + DEBUG("Seated, calling DoProcessNPCLine"); + SensorFunc = 0; + } else if (SensorFunc == 2) { + osNpcTouch(NPCKey(), llDetectedKey(0), LINK_THIS); + DEBUG("Touched, calling DoProcessNPCLine"); + SensorFunc = 0; + } + DoProcessNPCLine(); + } + no_sensor(){ + DEBUG ("no target prim located, calling DoProcessNPCLine"); + SensorFunc = 0; + DoProcessNPCLine(); + } + + + link_message(integer sender, integer num, string str, key id){ + DEBUG("Command In:" + str); + if (str=="@go") { + Stopped = FALSE; // Let's run the notecard + DEBUG("@go approved, calling DoProcessNPCLine"); + DoProcessNPCLine(); + } else { + Stack += [str]; // take anything, the controller will filter away non @ stuff + if (llGetListLength(Stack) == 1) { // 3.8 + DEBUG("Stack=1"); + DoProcessNPCLine(); + } + } + } + +} + + + + + + diff --git a/Hypergrid Story Three/Landing Zone/6. Edge Warning/Collision.lsl b/Hypergrid Story Three/Landing Zone/6. Edge Warning/Collision.lsl new file mode 100644 index 00000000..44e4c599 --- /dev/null +++ b/Hypergrid Story Three/Landing Zone/6. Edge Warning/Collision.lsl @@ -0,0 +1,75 @@ +// :SHOW:1 +// :CATEGORY:NPC +// :NAME:Hypergrid Story Three +// :AUTHOR:Ferd Frederix +// :KEYWORDS: +// :CREATED:2015-11-24 20:36:34 +// :EDITED:2015-11-24 19:36:34 +// :ID:1090 +// :NUM:1862 +// :REV:1 +// :WORLD:Opensim +// :DESCRIPTION: +// Sample collision script for NPC animator +// :CODE: + + +integer debug = FALSE; + + +DoIt() +{ + llMessageLinked(2,1, "@animate=avatar_type",""); + llMessageLinked(2,5, "@say=Be wary of the water, for it is deadly",""); +} + +Reset() +{ + llVolumeDetect(FALSE); + llSetStatus(STATUS_PHANTOM, FALSE); + llSleep(0.1); + llVolumeDetect(TRUE); +} + +default +{ + state_entry() + { + llSetText("",<1,1,1>,1.0); + llSetTimerEvent(3600); + Reset(); + } + + collision_start(integer n) { + + if (debug) llOwnerSay("Collided with " + llKey2Name(llDetectedKey(0))); + + if (! osIsNpc(llDetectedKey(0))) + { + if (debug) llOwnerSay("Collided with " + llKey2Name(llDetectedKey(0))); + + DoIt(); + + } + + } + + timer() + { + Reset(); + llSetTimerEvent(3600); + } + + on_rez(integer p) + { + llResetScript(); + } + + changed(integer what) + { + if (what & CHANGED_REGION_START) + { + llResetScript(); + } + } +} diff --git a/Hypergrid Story Three/Landing Zone/7. Racoon at Bottom/!Path b/Hypergrid Story Three/Landing Zone/7. Racoon at Bottom/!Path new file mode 100644 index 00000000..b529c040 --- /dev/null +++ b/Hypergrid Story Three/Landing Zone/7. Racoon at Bottom/!Path @@ -0,0 +1,24 @@ +// :SHOW: +// :CATEGORY:NPC +// :NAME:Hypergrid Story Three +// :AUTHOR:Ferd Frederix +// :KEYWORDS: +// :CREATED:2015-11-24 20:37:06 +// :EDITED:2015-11-24 19:37:06 +// :ID:1090 +// :NUM:1864 +// :REV:1 +// :WORLD:Second Life +// :DESCRIPTION: +// Notecard for sample NPC Sequence +// :CODE: +@spawn="Trusty" Racoon|<82.40501, 60.73487, 21.49010> +@stop +@pause=10 +@walk=<82.40501, 47.29763, 21.49010> +@walk=<82.40501, 49.19700, 21.49010> +@pause=30 +@say=Follow me. I see a shack that could have the magic potions you need for your friends. +@walk=<79.46262, 39.34087, 21.64303> +@stop + diff --git a/Hypergrid Story Three/Landing Zone/7. Racoon at Bottom/Collider.lsl b/Hypergrid Story Three/Landing Zone/7. Racoon at Bottom/Collider.lsl new file mode 100644 index 00000000..7cf36d54 --- /dev/null +++ b/Hypergrid Story Three/Landing Zone/7. Racoon at Bottom/Collider.lsl @@ -0,0 +1,76 @@ +// :SHOW:1 +// :CATEGORY:NPC +// :NAME:Hypergrid Story Three +// :AUTHOR:Ferd Frederix +// :KEYWORDS: +// :CREATED:2015-11-24 20:36:34 +// :EDITED:2015-11-24 19:36:34 +// :ID:1090 +// :NUM:1863 +// :REV:1 +// :WORLD:Opensim +// :DESCRIPTION: +// Sample collision script for NPC animator +// :CODE: + + +integer debug = FALSE; + +DoIt() +{ + if (debug) llOwnerSay("Raccoon at bottom"); + llMessageLinked(LINK_SET,0, "@walk=<79.46262, 39.34087, 21.64303>",""); +} + + +Reset() +{ + llSetStatus(STATUS_PHANTOM, FALSE); + llVolumeDetect(FALSE); + llSleep(0.1); + llVolumeDetect(TRUE); +} + + + +default +{ + state_entry() + { + llSetText("",<1,1,1>,1.0); + Reset(); + llSetTimerEvent(3600); + } + + collision_start(integer n) { + + if (! osIsNpc(llDetectedKey(0))) + { + if (debug) llOwnerSay("Collided with " + llKey2Name(llDetectedKey(0))); + + DoIt(); + } + + } + timer() + { + llSetTimerEvent(3600); + Reset(); + } + on_rez(integer p) + { + llResetScript(); + } + touch_start(integer p) + { + DoIt(); + } + + changed(integer what) + { + if (what & CHANGED_REGION_START) + { + llResetScript(); + } + } +} diff --git a/Hypergrid Story Three/Landing Zone/Landing Zone.prj b/Hypergrid Story Three/Landing Zone/Landing Zone.prj new file mode 100644 index 00000000..6c48bd4b --- /dev/null +++ b/Hypergrid Story Three/Landing Zone/Landing Zone.prj @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Hypergrid Story Three/Landing Zone/NPC Poseball for prims 1,2,3 etc/NPC Rev 4.lsl b/Hypergrid Story Three/Landing Zone/NPC Poseball for prims 1,2,3 etc/NPC Rev 4.lsl new file mode 100644 index 00000000..d921b4f7 --- /dev/null +++ b/Hypergrid Story Three/Landing Zone/NPC Poseball for prims 1,2,3 etc/NPC Rev 4.lsl @@ -0,0 +1,1631 @@ +// :SHOW:1 +// :CATEGORY:NPC +// :NAME:All In One NPC Recorder and Player +// :AUTHOR:Ferd Frederix +// :KEYWORDS:NPC, Puppeteer +// :CREATED:2015-08-28 23:27:03 +// :ID:27 +// :NUM:1824 +// :REV: 4.0 +// :WORLD:OpenSim +// :DESCRIPTION: +// All in one NPC recorder player. +// Supports both absolute and relative paths and many new commands +// Add animations named "Fly, Walk, Stand and Run" +// Click Prim to use. +// Should be worn as a HUD to record. +// Put it on the ground and click Sensor or Start NPC when done. +// :CODE: +// This is Rev 4.0 09/15/2015 + +// Revision History +// Rev 1.1 10-2-2014 @Sit did not work. Minor tweaks to casting for lslEditor +// Rev 1.2 10-14-2014 @ sit had wrong type. +// Rev 1.3 relative movement fixed for @fly +// Rev 1.4 4-3-2014 allow anyone to use this, non owners and non group members can only start and stop. +// Rev 1.5 5-17-2014 set sensor to auto start on reboot of sim +// Rev 1.6 5-24-2014 move menu so you can get it by touching, removed many of the KeyValues to RAM for efficiency +// Rev 1.7 CHANGED_REGION_START, not CHANGED_REGION_START (Opensim difference) +// Rev 1.8 tuned up Kill NPC, added more flexible upgrader +// Rev 1.9 Better script injection by link message// Rev 2.0 Added osSetSpeed so you can speed up or slow down an NPC. +// Rev 2.1 No laggy sensor used exept to sit on stuff +// Rev 2.2 Various sensor fixes +// Rev 2.3 Sets No Sensor in menu, must be started by hand +// Rev 2.4 - reserved for patches to 2.3 if needed +// Rev 3.0 Refactor out into subs, not states to make command injection easier +// New command: @appearance=Notecardname so you can switch to a new notecard on the fly +// New command: @speed=1.0 which slows up ( < 1 ) or speeds up ( > 1) +// Rev 3.1 Commands are not interruptible by Link Message +// Rev 3.2 Sensor patches for consistency in removing the NPC +// Rev 3.3 Added Touch command by Neo.Cortex@hbase42/hopto/org:8002 +// Added Menu 3 for notecard and appearance commands +// Rev 3.4 animation timer cannot be zero or it shuts off timer tweaked +// solves the NPC starting up when no sensor is set. +// Rev 3.5 fixes saving to !Path notecard +// Rev 3.6 08-11-2015 @delete acts like @stop. TYjhe NPC now rezzes after an @go back in where it was deleted +// Rev 3.7 08-11-2015 @attach command added to load an attachment from the inventory to the NPC +// Rev 3.8 08-17-2015 process queued commands one at a time without calling ProcessNPCLine on link message +// Rev 3.9 08-23-2011 Queued command fixes including @delete which were not always working +// Rev 4.0 09-15-2015 - FIxes for Sensor functions which continually rezzed a NPC when no one was around. +//*******************************************************************// + +// Instructions on how to use this are at http://www.outworldz.com/opensim/posts/NPC/ +// This is an OpenSim-only script. +// Author: Ferd Frederix aka Fred Beckhusen - fred@mitsi.com + +//////////////////////////////////////////////////////////////////////////////////////////// +// Original code was Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // +/////////////////////////////////////////////////////////////////////////////////////////// +// Please see: http://www.gnu.org/licenses/gpl.html for legal details, // +// rights of fair usage, the disclaimer and warranty conditions. // +/////////////////////////////////////////////////////////////////////////////////////////// +// The original NPC controller was from http://was.fm/opensim:npc +// Extensive additions and bug fixes by Fred Beckhusen, aka Ferd Frederix +// llSensor had two params swapped +// @Wander would wander where it had rezzed, not where it was. +// There was no 'no_sensor' event in sit, so if a @sit failed, the NPC got stuck +// The animation and walks always stopped old, then started new. It should be start new, then stop old so the default stand would be suppressed. +// New code: +// Merged with new Route recorder and notecard writer +// If the NPC failed to reach a destination it never moved on. +// Added WAIT global to tune this +// Exposed many tunable variables and ported the code +// Added floating point to times in notecard. + +// Added @sound, @randsound, @whisper, @shout, and @cmd controls. +// +// notecards integers are not floats for better control +// +// Link Messages may be used to perform external control by injecting @commands into the stream of actions +// Example: +// To chat something, such as with a chat robot +// llMessageLinked(LINK_SET,0,"@npc_say=Hello",""); + +// This script assumes that NPCs and OSSl scripting is enabled in the OpenSim configuration. +// In order to enable them, the following changes must be made in the OpenSim.ini configuration file: +// +// ; Turn on OSSL +// AllowOSFunctions = true +// OSFunctionThreatLevel = Severe + +//[NPC] +// ;# {Enabled} {} {Enable Non Player Character (NPC) facilities} {true false} +// Enabled = true +// +// and then the server has to be restarted. + + +// Commands: All commands begin with an @ sign. All other lines are ignored +// @commands may have optional parameters. The syntax is always: +// @cmd=parm1|parm2 +// NaN in the table below meand Not a Number. This means there is no parameter + +//Command First Parameter Second Parameter Description +//@spawn name location (vector) Rezzes an NPC with name at a location. +//@appearance NoteCardName NaN switch the NPC appearance to a new notecard +//@walk destination (vector) NaN Makes the NPC walk to destination. +//@fly destination (vector) NaN Makes the NPC fly to destination. +//@land destination (vector) NaN Makes the NPC land at destination. +//@say string NaN Makes the NPC speak a phrase. +//@whisper string NaN Makes the NPC whisper a phrase. +//@shout string NaN Makes the NPC shout a phrase. +//@pause seconds (float) NaN Makes the NPC wait for a multiple of seconds. +//@wander radius (float) cycles (integer) Makes the NPC wander in radius, for cycles seconds. +//@delete NaN NaN Removes the NPC. Requires a link message to continue +//@goto label (string) NaN Jump to the label label in the script. +//@animate animation (string) time (float) Makes the NPC trigger the animation animation for time seconds. +//@sound sound_name NaN plays a sound from inventory +//@randsound NaN NaN Plays a random sound from inventory +//@rotate degrees (float) NaN Rotate the NPC degrees around the Z axis. +//@sit primitive name NaN Sit on a primitive with a given name. +//@touch primitive name NaN Touch on a primitive with a given name. +//@stand NaN NaN If sitting on a primitive, stand up. +//@cmd channel (integer) string Says string on channel, for controlling external gadgets +//@stop NaN NaN Halts the NPC script indefinitely. Can be started with a link message +//@go NaN NaN Continues on next notecard line, for use in link messages +//@speed speed (float) NaN from 0 to N, where 1.0 ius a normal speed of an avatar. 0.2 is a turtle. +//@notecard notename (string) NaN load a new Path notecard +//@attach InventoryName attachmentPoint load an attachment from the inventory to the NPC onto point + +// Constant attachmentPoint Comment +// ATTACH_CHEST 1 chest/sternum +// ATTACH_HEAD 2 head +// ATTACH_LSHOULDER 3 left shoulder +// ATTACH_RSHOULDER 4 right shoulder +// ATTACH_LHAND 5 left hand +// ATTACH_RHAND 6 right hand +// ATTACH_LFOOT 7 left foot +// ATTACH_RFOOT 8 right foot +// ATTACH_BACK 9 back +// ATTACH_PELVIS 10 pelvis +// ATTACH_MOUTH 11 mouth +// ATTACH_CHIN 12 chin +// ATTACH_LEAR 13 left ear +// ATTACH_REAR 14 right ear +// ATTACH_LEYE 15 left eye +// ATTACH_REYE 16 right eye +// ATTACH_NOSE 17 nose +// ATTACH_RUARM 18 right upper arm +// ATTACH_RLARM 19 right lower arm +// ATTACH_LUARM 20 left upper arm +// ATTACH_LLARM 21 left lower arm +// ATTACH_RHIP 22 right hip +// ATTACH_RULEG 23 right upper leg +// ATTACH_RLLEG 24 right lower leg +// ATTACH_LHIP 25 left hip +// ATTACH_LULEG 26 left upper leg +// ATTACH_LLLEG 27 left lower leg +// ATTACH_BELLY 28 belly/stomach/tummy +// ATTACH_LEFT_PEC 29 left pectoral +// ATTACH_RIGHT_PEC 30 right pectoral +// ATTACH_HUD_CENTER_2 31 HUD Center 2 +// ATTACH_HUD_TOP_RIGHT 32 HUD Top Right +// ATTACH_HUD_TOP_CENTER 33 HUD Top +// ATTACH_HUD_TOP_LEFT 34 HUD Top Left +// ATTACH_HUD_CENTER_1 35 HUD Center +// ATTACH_HUD_BOTTOM_LEFT 36 HUD Bottom Left +// ATTACH_HUD_BOTTOM 37 HUD Bottom +// ATTACH_HUD_BOTTOM_RIGHT 38 HUD Bottom Right +// ATTACH_NECK 39 neck +// ATTACH_AVATAR_CENTER 40 avatar center/root + + + +////////////////////////////////////////////////////////// +// DEBUG // +////////////////////////////////////////////////////////// +integer debug = FALSE; // set to TRUE or FALSE for debug chat on various actions +integer LSLEditor = FALSE; // set to to TRUE to working in LSLEditor, FALSE for in-world. + // you must also include the NPC commands found in the other script since LSLEditor does not support OpenSim +integer iTitleText = TRUE; // set to TRUE to see debug info in text above the controller + +////////////////////////////////////////////////////////// +// TUNABLE CONFIGURATION // +////////////////////////////////////////////////////////// +float TIMER = 2; // faster = less jerky stopping. How often the system checks the distance traveled. Fastest you can go is 0.5 seconds +float QUICK = 1; // when we need to move to the next state, we use a QUICK timer +string Appearance = "!Appearance"; // The name of the recorded Appearance notecard +string Notecard = "!Path"; // The name of the recorded routes +integer allowUsers = FALSE; // If true, any user can get a Start NPC and Stop NPC menu. Only groups and owners can get all commands if TRUE, or FALSE +float MAXDIST = 2.0; // how close a NPC has to get to a dest pos to continue to next state. Do not lower this too much, as it may miss the target +integer WANDERRAND = TRUE; // set to TRUE and they will pause during wanders a random number of seconds +float WANDERTIME = 3.0; // how long they stand after each @wander,if WANDERRAND is FALSE. If WANDERRAND is TRUE, this is the max time +integer WAIT = 30; // wait for this number of seconds for the NPC to reach a destination (for safety). If it fails to reach a target, it will move on after this time. +float RANGE = 10; // 1 to N meters - anyone this close to the controller will start NPCS if Sensor button is clicked +float REZTIME = 2.0; // wait this long for NPC to rez in, then start the process +string STAND = "Stand"; // the name of the default Stand animation +string WALK = "Walk"; // the name of the default Walk animation +string FLY = "Fly"; // the name of the default Fly animation +string RUN = "Run"; // the name of the default Run animation +string LAND = "Land"; // the name of the default land animation ( for birds only) +float OffsetZ = 0.5; // appear 0.5 meter above ground, this is added to all destinations to keep them from sinking in. +float SPEEDMULT =0.5; // 1.0 = regular avatar speed. Smaller numbers slow down walks. Large numbers speed them up. +integer FLIGHT = 299; // For controlling wings. A channel that is shouted at when flight starts and ends. "flying" or "landing" + +// DESCRIPTIONS FIELDS HAVE TO SURVIVE A RESET +// These vars are stored by saving them with KeyValueSet +// "pr" is a 0 if it is set for Owner Only, 1 for Group control +// "se" is "on" if Started +// "co" = "R" or "A" for relative or absolute addressing mode +// "key" = NPC key + +// These Globals used to be stored in description. Moved to RAM in V1.6 +float RAMPause; // @pause param +float RAMwd ; // @wander distance +integer RAMwc; // @wander count +float RAMrot; // @rotate +string RAMsit; // @sit primname +string RAMtouch; // @touch primname +string RAManimationName; // @animate animation (string) time (float) +float RAManimationTime; + +// other globals section +integer iChannel; // a listen channel, randomly assigned +integer iHandle; // the handle to it + +// NPC controls +vector newDest ; // tmp storage for the walks +integer iWaitCounter ; // wait for this number of seconds for the NPC to reach a desrtination +string sNPCName; // the name of the NPC that may be in world. So we can remove it. +integer bNPC_STOP = FALSE; // boolean to reuse a listener +integer Stopped = FALSE; // set to TRUE by link messages so we do not remember them +float fTimerVal ; // how long we wait when wandering (calculated) +float NPCEnabled; // true if the NPC is suppodes to be running + +// OS_NPC_CREATOR_OWNED will create an 'owned' NPC that will only respond to osNpc* commands issued from scripts that have the same owner as the one that created the NPC. +// OS_NPC_NOT_OWNED will create an 'unowned' NPC that will respond to any script that has OSSL permissions to call osNpc* commands. +integer NPCOptions = OS_NPC_CREATOR_OWNED; // only yhe owner of this box can control this NPC. + +integer walkstate = 0; // helps us reshare the walk state for run, fly and land - a bit of a hack, but it saves RAM. Has to be done this way because some bits of NPCWalkOption are asserted as 0 + +integer NPCWalkOption; // Some notes for what happens to NPCWalkOption: +// OS_NPC_FLY - Fly the avatar to the given position. The avatar will not land unless the OS_NPC_LAND_AT_TARGET option is also given. +// OS_NPC_NO_FLY - Do not fly to the target. The NPC will attempt to walk to the location. If it's up in the air then the avatar will keep bouncing hopeless until another move target is given or the move is stopped +//OS_NPC_LAND_AT_TARGET - If given and the avatar is flying, then it will land when it reaches the target. If OS_NPC_NO_FLY is given then this option has no effect. +// OS_NPC_RUNNING - if given, NPC avatar moves at running/fast flying speed, otherwise moves at walking/slow flying speed. + +// menus +string mSensor="Sense is Off"; // Sensor or "No Sensor" + +list lAtButtons = ["Menu","-", ">>", "@run", "@walk", "@fly", "@land", "@wander", "@sit", "@stand","@animate","@rotate"]; +list lMenu2 = ["<<", "@comment", ">>>", "@stop", "@say", "@whisper","@shout","@sound","@randsound","@cmd", "@pause", "@delete"]; +list lMenu3 = ["<<<","@notecard","@appearance", "@touch", "@speed", "@attach", "-","-", "-", "-", "-", "-" ]; + +string sCommand; // place to store a command for two-prompted ones +string sParam2; // place to store a prompt for two-prompted ones +string priPub = "Owner Only"; // Private or Group +key kUserKey; // the person who is controlling the avatar, not the Owner +// the command lists +list lCommands; // commands are stored here +list lNpcCommandList; // Storage for the NPC script. +string npcAction; // Storage for the next action. @cmd=0|hello, this becomes @cmd +string npcParams; // Storage for the param, @cmd=0|hello, this becomes 0|hello + +// misc vars +string sNotecard; // commands are stored here temporarily for dumping +vector vWanderPos; // a place to wander to +string lastANIM ; // last animation run +// Sensor +integer avatarPresent; // Sensor sets this flag when people are within Range. + +// Coordinate control +vector vInitialPos ; // Vector that will be filled by the script with the initial starting position in region coordinates. +vector vDestPos = ZERO_VECTOR; // Storage for destination position. +string relAbs = "Relative"; // absolute vs relative positioning + + +// STATES +integer MENU ; // processing a dialog box state, may be concurrent with STATE +integer STATE; // state storage +integer MakeNotecard = 1; // displaying a text box for NPC name +integer RecordPath = 2; // displaying a path notecard menu +integer NobodyHome = 3; // looking for an avatar +integer Spawning = 4; // spawning an avatar +integer Animate = 5; // animation timer needed +integer Walking = 6; // Hey! I am walking here! +integer Wander = 7; // Wandering around neeeds a timer, too +integer WanderHold = 8; // We reached a wander point +integer DoProcess = 9; // Set this to make it process a new command +integer Touch = 10; // Timer is busy sensing something to touch +integer Sit = 11; // Timer is busy sensing something to sit on +integer Paused = 12; // Timer is busy pausing + +key gNpcKey = NULL_KEY; // global key storage for the one NPC, to save CPU cycles +list Stack ; // a command stack from link message input + +integer SensorFunc = 0; // define which function shall be triggered inside the sensor function + // 0 means none, 1 sit, 2 touch +/////////////////////////////////////////////////////////////////////////// +// FUNCTIONS // +/////////////////////////////////////////////////////////////////////////// + + +TimerEvent(float timesent) +{ + if (LSLEditor) + timesent *= 5; // slow thinggs doen when the LSLEDITOR is in use + + DEBUG("Setting timer: " + (string) timesent); + llSetTimerEvent(timesent); +} + + +SetStop(integer what) +{ + DEBUG("Stopped set to " + (string ) what); + Stopped = what; +} +// Do* functions are much like states from the old V2 scripts. + +// Save a Path notecard +DoSave() +{ + STATE = MakeNotecard; + makeText("Stand where you want the NPC to appear, and enter the NPC Name"); +} + +// This function is used to record the path for the NPC +// Each command can take 0, 1, or 2 params +DoMenuForCommands() { + makeMenu(lAtButtons); +} + + +// No one is here when sensors were on, so we kill off the NPC +DoNobodyHome() +{ + DEBUG("Nobody Home"); + STATE = NobodyHome; + if (NPCKey() != NULL_KEY) { + osNpcRemove(NPCKey()); + SaveKey(NULL_KEY); + } + TimerEvent(5); // keep ticking to sense avatars +} + +// Create a NPC +StateSpawn() { + DEBUG("state spawn"); + STATE = Spawning; + + + NPCEnabled = TRUE; // in world + // see if there is already one out there. + if (NPCKey() != NULL_KEY) { + DEBUG("Already living"); + return; + } + + + list name = llParseString2List(sNPCName, [" "], []); + + vector vRezPos = vInitialPos; + if (relAbs == "Relative"){ + vRezPos += llGetPos(); + } + + DEBUG("Rezzing the NPC:" + (string) vRezPos); + key aKey = osNpcCreate(llList2String(name, 0), llList2String(name, 1), vRezPos, Appearance, NPCOptions); + + SaveKey(aKey); // save in desceription and global, too + + osSetSpeed(aKey,SPEEDMULT); // 1.9 speed multiplier + TimerEvent(REZTIME); + NPCAnimate(STAND); +} + +DoRotate() { + DEBUG("@rotate=" + (string) RAMrot); + osNpcSetRot(NPCKey(), llEuler2Rot(<0,0,RAMrot> * DEG_TO_RAD)); +} + +StateSit() { + DEBUG ("state sit - looking for " + RAMsit); + STATE=Sit; + SensorFunc = 1; //triggers osNpcSit + llSensor(RAMsit, "", PASSIVE|ACTIVE|SCRIPTED, 96, PI); +} + +StateTouch() { + DEBUG ("state touch - looking for " + RAMtouch); + STATE = Touch; + SensorFunc = 2; //triggers osNpcTouch + llSensor(RAMtouch, "", PASSIVE|ACTIVE|SCRIPTED, 96, PI); +} + +DoStand() { + DEBUG("state stand"); + osNpcStand(NPCKey()); +} + + +StateAnimate() { + + DEBUG("state animate"); + STATE = Animate; + NPCAnimate(RAManimationName); + if (RAManimationTime <=0 ) // V 3.4 tweak + RAManimationTime = 1; + TimerEvent(RAManimationTime); +} + +StateWalk() { + + DEBUG("NPCWalkOption = " + (string) NPCWalkOption); + STATE = Walking; + + // walk, fly, run, land + if (walkstate == 1) { + NPCAnimate(WALK); + } else if (walkstate == 2) { + llShout(FLIGHT,"flying"); + NPCAnimate(FLY); + } else if (walkstate == 3) { + NPCAnimate(RUN); + } else if (walkstate == 4) { + NPCAnimate(LAND); + } + newDest = vDestPos ; + newDest.z += OffsetZ; + + // notecard is stored as offsets from this box with relative addressing. Convert to absolute + if (relAbs == "Relative"){ + newDest += llGetPos(); + } + + DEBUG("Moveto:" + (string) newDest); + osNpcMoveToTarget(NPCKey(), newDest, NPCWalkOption); + iWaitCounter = WAIT; // wait 60 seconds to get to a destination. + TimerEvent(TIMER); +} + + +StateWander(){ + DEBUG("state wander"); + STATE = Wander; + + vector point = CirclePoint(RAMwd); + DEBUG("CirclePoint:" + (string) point); + vWanderPos = vDestPos + point; + DEBUG("vWanderPos:" + (string) vWanderPos); + + fTimerVal = WANDERTIME; // default time to pause after each wander + if (WANDERRAND) + fTimerVal = llFrand(WANDERTIME) + 1; // override, they want random times + + NPCAnimate(WALK); + + DEBUG("Wander to:" + (string) vWanderPos); + + osNpcMoveToTarget(NPCKey(), vWanderPos, NPCWalkOption); + iWaitCounter = WAIT; // wait 60 seconds to get to a destination. + TimerEvent(TIMER); +} + +StateWanderhold() { + + DEBUG("Wander Hold"); + STATE = WanderHold; + + // now that we have reached a wander spot, slow the timer down to the desired value + TimerEvent(fTimerVal); +} + +// @pause=10 will do nothing for 10 seconds +DoPause() { + STATE =Paused; + if (RAMPause < 0.1) + RAMPause = 0.1; + DEBUG("@pause=" + (string)RAMPause); + TimerEvent(RAMPause); +} + + +// @stop makes the NPC stop moving in whatever state it is in. You have to linkmessage to get moving again +DoStop() { + DEBUG("NPC is Stopped"); + STATE = 0; // accept commands + SetStop(TRUE); // Link controlled - we mnust have a @go to continue with notecards + TimerEvent(0); + Stack = []; // v3.8 +} + +// @delete removes the NPC forever. Next command starts it up again at the beginning +DoDelete() { + DEBUG("state delete"); + STATE = 0; // accept commands + osNpcRemove(NPCKey()); + SaveKey(NULL_KEY); + + TimerEvent(0); + Stack = []; // v3.8 +} + +// change the appearance of the NPC +DoAppearance(string notecard) { + DEBUG("state appearance"); + if (llGetInventoryType(notecard) == INVENTORY_NOTECARD){ + DEBUG("Load appearance " + notecard); + osNpcLoadAppearance(NPCKey(),notecard); + } +} + +// Change the avatar speed +DoSpeed(string speed) { + float newspeed = (float) speed; + if (newspeed > 0.1 && newspeed < 5.0) {// sanity check + osSetSpeed(NPCKey(),newspeed); + } +} +DoNewNote (string card) { + DEBUG("Load Notecard " + card); + NPCReadNoteCard(card); + SetStop(FALSE); +} +DoAttach(string params) { + + list Data = llParseString2List(params, ["|"], []); + string itemName = llList2String(Data, 0); + integer attachmentPoint = (integer) llList2String(Data, 1); + if (attachmentPoint > 0 + && attachmentPoint < 40 + && llGetInventoryType(itemName) == INVENTORY_OBJECT + ) + { + osForceAttachToOtherAvatarFromInventory(NPCKey(),itemName,attachmentPoint); + } +} + +// This loops over the notecard, processing each command +DoProcessNPCLine() { + DEBUG("ProcessNPCLine, stopped = " + (string) Stopped); + STATE = DoProcess; + + // auto load a notecard + if (! llGetListLength(lNpcCommandList)) { + DEBUG("Read Notecard"); + NPCReadNoteCard(Notecard); + SetStop(FALSE); + } + + // look for link messages on the stack + string next = llList2String(Stack,0); // lets see if there is anithing from a link message + if (llStringLength(next)) + { + Stack = llDeleteSubList(Stack,0,0); + ProcessCmd(next); //lets do this command instead. + return; + } + + // @stop issued? + if (Stopped) { + TimerEvent(0); + DEBUG("Stopped, waiting for input"); + STATE = 0; + return; + } + + // No, we have an @go for liftoff + next = llList2String(lNpcCommandList, 0); // get the next command + DEBUG("Execute:" + next); + lNpcCommandList = llDeleteSubList(lNpcCommandList, 0, 0); // delete it + + if (llGetListLength(lNpcCommandList) == 0) { + DEBUG("EOF"); + } + ProcessCmd(next); +} + + + +ProcessCmd(string cmd) { + + DEBUG("ProcessCmd:" + cmd); + + if (llGetSubString(cmd, 0, 0) != "@") { + DEBUG("ignoring"); + STATE = DoProcess; + TimerEvent(QUICK); // this is so we do not recurse the stack + STATE = 0; + return; + } + + list data = llParseString2List(cmd, ["="], []); + npcAction = llToLower(llStringTrim(llList2String(data, 0), STRING_TRIM)); + + DEBUG("Action:" + npcAction); + npcParams = llStringTrim(llList2String(data, 1), STRING_TRIM); + + @commands; + + ProcessSensor(); + + if(npcAction == "@spawn" && avatarPresent) { + DEBUG("@spawn"); + list spawnData = llParseString2List(npcParams, ["|"], []); + sNPCName =llList2String(spawnData, 0); // V 1.6 name in RAM + + list spawnDest = llParseString2List(llList2String(spawnData, 1), ["<", ",", ">"], []); + vInitialPos.x = llList2Float(spawnDest, 0); + vInitialPos.y = llList2Float(spawnDest, 1); + vInitialPos.z = llList2Float(spawnDest, 2); + + DEBUG("Coords for NPC at " + (string) vInitialPos); + StateSpawn(); + return; + } + + if (! avatarPresent){ + DoNobodyHome(); + DEBUG("No avatar nearby"); + STATE = 0; + return; + } else { + if ( NPCKey() == NULL_KEY) { + StateSpawn(); + } + } + + + + + if(npcAction == "@stop") { + DoStop(); + STATE = 0; + return; + } + else if(npcAction == "@goto") { + DEBUG("goto"); + integer lastIdx = llGetListLength(lNpcCommandList)-1; + lNpcCommandList = llDeleteSubList(lNpcCommandList, lastIdx, lastIdx); + // Wind commands till goto label. + @wind; + string next1 = llList2String(lNpcCommandList, 0); + lNpcCommandList = llDeleteSubList(lNpcCommandList, 0, 0); + lNpcCommandList += next1; + if(next1 != npcParams) jump wind; + // Wind the label too. + next1 = llList2String(lNpcCommandList, 0); + lNpcCommandList = llDeleteSubList(lNpcCommandList, 0, 0); + lNpcCommandList += next1; + // Get next command. + list data1 = llParseString2List(next1, ["="], []); + npcAction = llToLower(llStringTrim(llList2String(data1, 0), STRING_TRIM)); + npcParams = llStringTrim(llList2String(data1, 1), STRING_TRIM); + // Reschedule. + jump commands; + } + else if(npcAction == "@sound") { + DEBUG("sound"); + llTriggerSound(npcParams,1.0); + } + else if(npcAction == "@randsound") { + DEBUG("@randsound"); + integer N = llGetInventoryNumber(INVENTORY_SOUND); + integer rand = llCeil(llFrand(N)) -1; // pick a random sound + string toPlay = llGetInventoryName(INVENTORY_SOUND,rand); + llTriggerSound(toPlay,1.0); + } + else if(npcAction == "@walk") { + DEBUG("@walk"); + GetDest(npcParams); + walkstate = 1;// walking + NPCWalkOption = OS_NPC_NO_FLY ; + StateWalk(); + return; + } + else if(npcAction == "@fly") { + GetDest(npcParams); + walkstate = 2;// flying + NPCWalkOption = OS_NPC_FLY ; + StateWalk(); + return; + } + else if(npcAction == "@run") { + DEBUG("@run"); + GetDest(npcParams); + walkstate = 3;// running + NPCWalkOption = OS_NPC_NO_FLY | OS_NPC_RUNNING; + StateWalk(); + return; + } + else if(npcAction == "@land") { + DEBUG("@land"); + GetDest(npcParams); + walkstate = 4;// landing + NPCWalkOption= OS_NPC_FLY | OS_NPC_LAND_AT_TARGET ; + StateWalk(); + return; + } + else if(npcAction == "@say") { + DEBUG("@say " + npcParams); + osNpcSay(NPCKey(), 0, npcParams); + } + else if(npcAction == "@shout") { + DEBUG("@shout"); + osNpcShout(NPCKey(),0, npcParams); + } + else if(npcAction == "@whisper") { + DEBUG("@whisper " + npcParams); + osNpcWhisper(NPCKey(),0, npcParams); + } + // speak a command on a channel, so you can open doors and control stuff. + else if(npcAction == "@cmd") { + DEBUG("@cmd"); + list dataToSpeak = llParseString2List(npcParams, ["|"], []); + string channel = llList2String(dataToSpeak,0); + DEBUG("Channel:"+(string) channel); + integer iChannel = (integer) channel; + string stringToSpeak = llList2String(dataToSpeak,1); + llSay(iChannel, stringToSpeak); + } + // stop everything + else if(npcAction == "@pause") { + RAMPause = (float) npcParams; + DoPause(); + return; + } + else if(npcAction == "@wander") { + list wanderData = llParseString2List(npcParams, ["|"], []); + RAMwd = (float) llList2String(wanderData, 0); + RAMwc = (integer) llList2String(wanderData, 1); + vDestPos = osNpcGetPos(NPCKey()); // set the wander start + DEBUG("Starting at " + (string) vDestPos); + StateWander(); + return; + } + else if(npcAction == "@rotate") { + RAMrot = (float) npcParams; + DoRotate(); + } + else if(npcAction == "@sit") { + RAMsit= npcParams; + StateSit(); + return; + } + else if(npcAction == "@touch") { + RAMtouch= npcParams; + StateTouch(); + return; + } + else if(npcAction == "@stand") { + DoStand(); + } + else if(npcAction == "@delete") { + DoDelete(); + SetStop(TRUE); // Link controlled - we mnust have a @go to continue with notecards + return; + } + else if(npcAction == "@animate") { + list animateData = llParseString2List(npcParams, ["|"], []); + RAManimationName = llList2String(animateData, 0); + RAManimationTime = (float) llList2String(animateData, 1); + StateAnimate(); + return; + } + else if(npcAction == "@appearance" ){ + DoAppearance(npcParams); + } + else if (npcAction =="@speed") { + DoSpeed(npcParams); + } + else if (npcAction =="@notecard") { + DoNewNote(npcParams); + Notecard = npcParams; + } + else if (npcAction == "@attach") + { + DoAttach(npcParams); + } + + STATE = 0; + TimerEvent(QUICK); // yeah I know, not possible this fast, we just go as fast as we can go - this is so we do not recurse the stack +} + + + +/////////////////// UTILITY Functions, not state-like ////////////////// + +// DEBUG(string) will chat a string or display it as hovertext if debug == TRUE +DEBUG(string str) { + if (debug && ! LSLEditor) + llOwnerSay( str); // Send the owner debug info + if (debug && LSLEditor) + llSay(0, str); // Send to the Console in LSLEDitor + if (iTitleText) { + llSetText(str,<1.0,1.0,1.0>,1.0); // show hovertext + + } +} + +GetDest(string npcParams) { + list dest = llParseString2List(npcParams, ["<", ",", ">"], []); + vDestPos.x = llList2Float(dest, 0); + vDestPos.y = llList2Float(dest, 1); + vDestPos.z = llList2Float(dest, 2); +} + +NPCReadNoteCard(string Note) { + DEBUG("NPCReadNoteCard"); + lNpcCommandList = llParseString2List(osGetNotecard(Note), ["\n"], []); +} + +integer SenseAvatar() +{ + //Returns a strided list of the UUID, position, and name of each avatar in the region + list avatars = llGetAgentList(AGENT_LIST_REGION ,[]); + integer numOfAvatars = llGetListLength(avatars); + if (numOfAvatars == 0) + { + DEBUG("No people"); + return 0; + } + //DEBUG("Located " + (string)numOfAvatars + " avatars and NPC's"); + + integer nAvatars; + integer i; + for( i = 0;i < numOfAvatars; i++) { + key aviKey = llList2Key(avatars,i); + if (!osIsNpc(aviKey)) { + list detail = llGetObjectDetails(aviKey,[OBJECT_POS]); + vector pos = llList2Vector(detail,0); + float dist = llVecDist(pos, llGetPos()); + if (dist < RANGE) + { + nAvatars++; + DEBUG("In range:" + llKey2Name(aviKey)); + } + } + } + //DEBUG("Located " + (string) nAvatars + " avatars"); + return nAvatars; +} + +// return TRUE if the avatar is owner when private is set, or TRUE if the avatar is in the same group and GROUP is set. +integer checkPerms() { + + integer group = (integer) KeyValueGet("pr"); + if (! group) + priPub = "Owner Only"; + else + priPub = "Group"; + + + if (llDetectedKey(0) == llGetOwner()){ + kUserKey = llDetectedKey(0); + return TRUE; + } + + if ( group && llDetectedGroup(0)) { + kUserKey = llDetectedKey(0); + return TRUE; + } + kUserKey = llDetectedKey(0); + return FALSE; +} + + + +NPCAnimate(string anim) +{ + DEBUG("Start Anim: " + anim); + if (llGetInventoryType(anim) == INVENTORY_ANIMATION ) { + + if (lastANIM != anim) { + if(llStringLength(lastANIM)) { + osNpcStopAnimation(NPCKey(), lastANIM); + } + osNpcPlayAnimation(NPCKey(), anim); + lastANIM = anim; + } + } else { + llSay(DEBUG_CHANNEL, "No animation named " + anim); + } +} + +// Kill a NPC by Name +Kill(string param) +{ + integer count; + list avatars = osGetAvatarList(); // Returns a strided list of the UUID, position, and name of each avatar in the region except the owner.\ + integer i; + integer j = llGetListLength(avatars); + for (i=0 ; i <= j; i+=3){ + + string desired = llList2String(avatars,i+2); + desired = llStringTrim(desired,STRING_TRIM); // should not be needed but is needed + + if (desired == param){ + vector v = llList2Vector(avatars,i+1); + key target = llList2Key(avatars,i); // get the UUID of the avatar + osNpcRemove(target); + SaveKey(NULL_KEY ); + llOwnerSay("Removed " + param+ " at location " + (string) v); + count++; + } + } + + NPCEnabled = FALSE; // not in world + + if (count) + llOwnerSay("Removed " + (string) count + " NPC's"); + else + llOwnerSay("Could not locate " + param); +} + + +// return a String for the position we are at. Strings used as the caller wants strings +string Pos() +{ + vector where = llGetPos(); // find the box position + + where.z += OffsetZ; // use the ground position + an offset + + if (LSLEditor) + where = <128,128,31 + llFrand(1)>; // force center of sim when editing + + // if attached the height will be too high by 1/2 the agent size + if (llGetAttached()) { + vector size = llGetAgentSize(llGetOwner()); + float Z = size.z; + where.z -= Z/2; + } + + // DEBUG("Pos= " + (string) where); + return (string) where; +} + +// setup a menu with a timer for timeouts, called by all make*() +menu() +{ + llListenRemove(iHandle); + iChannel = llCeil(llFrand(100000) + 20000); + iHandle = llListen(iChannel,"","",""); + TimerEvent(30.0); + MENU = TRUE; +} + +// make a text box +makeText(string Param) +{ + menu(); + llTextBox(kUserKey, Param, iChannel); +} + +// top level menu +makeMainMenu() +{ + menu(); + list buttons = ["Appearance","Recording","Save","Help","-","Erase RAM", priPub,relAbs,"-","Stop NPC",mSensor,"Start NPC"]; + llDialog(kUserKey,(string) llGetListLength(lCommands) + " Records",buttons,iChannel); +} + + +// Rev 1.4 +// top level menu for non group/ non owners +makeUserMenu() +{ + if (!allowUsers) return; + + menu(); + list buttons = ["Start NPC","Stop NPC"]; + llDialog(kUserKey,"Choose",buttons,iChannel); +} + + + +// programmable menu for @commands +makeMenu(list buttons) +{ + menu(); + llDialog(kUserKey,(string) llGetListLength(lCommands) + " Record",buttons,iChannel); +} + + +// make one or two text boxes with prompts +Text(string cmd, string p1, string p2) +{ + sCommand = cmd; + sParam2 = ""; + if (llStringLength(p2)) + sParam2 = p2; + + makeText(p1); +} + +// Set the Avatar Present flag - if sensors are off and we are forece run, there will be one present. +ProcessSensor() +{ + integer SensorOn; + if ("on" == KeyValueGet("se")) + { + SensorOn = TRUE; // we need to scan for avatars + } else { + SensorOn = FALSE; // we need to scan for avatars + } + DEBUG("Sensor:" + (string) SensorOn); + + integer n = SenseAvatar(); + + DEBUG("Avatars:" + (string) n); + if (SensorOn && n) + avatarPresent = TRUE; // someone is here and we need to tell the system to run + else if (SensorOn && !n) + avatarPresent = FALSE; // someone is not here and we need to tell the system to stop + else { // sensor is off, lete see if there is a NPC. If so, we are ON + DEBUG("NPCEnabled:" + (string) NPCEnabled); + if (NPCEnabled) + avatarPresent = TRUE; + else + avatarPresent = FALSE; + } + + // start up from when when no one is near + if (avatarPresent && STATE == NobodyHome) + STATE = 0; + + DEBUG("Avatar Present: " + (string) avatarPresent); +} + +vector CirclePoint(float radius) { + float x = llFrand(radius *2) - radius; // +/- radius, randomized + float y = llFrand(radius *2) - radius; // +/- radius, randomized + return ; // so this should always happen +} + +string KeyValueGet(string var) { + list dVars = llParseString2List(llGetObjectDesc(), ["&"], []); + do { + list data = llParseString2List(llList2String(dVars, 0), ["="], []); + string k = llList2String(data, 0); + if(k != var) jump continue; + //DEBUG("got " + var + " = " + llList2String(data, 1)); + return llList2String(data, 1); + @continue; + dVars = llDeleteSubList(dVars, 0, 0); + } while(llGetListLength(dVars)); + return ""; +} + +KeyValueSet(string var, string val) { + + //DEBUG("set " + var + " = " + val); + list dVars = llParseString2List(llGetObjectDesc(), ["&"], []); + if(llGetListLength(dVars) == 0) + { + llSetObjectDesc(var + "=" + val); + return; + } + list result = []; + do { + list data = llParseString2List(llList2String(dVars, 0), ["="], []); + string k = llList2String(data, 0); + if(k == "") jump continue; + if(k == var && val == "") jump continue; + if(k == var) { + result += k + "=" + val; + val = ""; + jump continue; + } + string v = llList2String(data, 1); + if(v == "") jump continue; + result += k + "=" + v; + @continue; + dVars = llDeleteSubList(dVars, 0, 0); + } while(llGetListLength(dVars)); + if(val != "") result += var + "=" + val; + llSetObjectDesc(llDumpList2String(result, "&")); +} + + +// clear RAM +Clr() { + + lCommands = []; + llOwnerSay("RAM Memory cleared. Notecards, if any, are not modified."); + makeMainMenu(); +} + +integer checkNoteCards() +{ + // Check that they have saved an Appeaance and Path notecard + integer num = llGetInventoryNumber(INVENTORY_NOTECARD); // how many notecards overall + + integer i; + integer count; + for (; i < num; i++){ + if (llGetInventoryName(INVENTORY_NOTECARD,i) == Notecard) + count++; + if (llGetInventoryName(INVENTORY_NOTECARD,i) == Appearance) + count++; + } + DEBUG("Checked " + (string) count + " Notecards"); + // if we have both, run the NPC + return count; +} + +Update(string SName) { + + // delete all NPC* scripts except myself + integer i; + integer j = llGetInventoryNumber(INVENTORY_SCRIPT); + for (i = 0; i < j; i++) { + string targetName = llGetInventoryName(INVENTORY_SCRIPT,i); + string match = llGetSubString(targetName,0,2); + + if (match == SName && llGetScriptName() != targetName){ + llOwnerSay("Upgrading " + targetName); + if (! LSLEditor){ // lets not kill the editor + llRemoveInventory(targetName); + } + } + } +} + +// Get all default saved params from the Description +GetSwitches() +{ + string rA = KeyValueGet("co"); // Get the remembered menu setting for Abs Vs Relative + if (rA == "A") + relAbs = "Absolute"; + else if (rA == "R") + relAbs = "Relative"; + else + relAbs = "Absolute"; + + + // reenable NPC if sensor is on. + if ("on" == KeyValueGet("se")) + { + NPCEnabled = TRUE; + mSensor = "Sense is On"; + ProcessSensor(); // fake 1 avatar to get it rezzed + } else { + mSensor = "Sense is Off"; + } + } + + +SaveKey(key akey) +{ + DEBUG("Saving Key of " + (string) akey); + KeyValueSet("key", akey); + if (akey != (key) KeyValueGet("key") ) + { + DEBUG("Fatal error, cannot save key"); + } + gNpcKey = akey; +} + + +key NPCKey() +{ + key akey = gNpcKey; // from cached copy + // gNpcKey saves a lot of CPU processing by caching the key, if blank we get it from the description + if (gNpcKey == NULL_KEY) + { + //DEBUG("Get DKey"); + akey = KeyValueGet("key"); // from Description of the prim + } + // DEBUG("NPC KEY:" + (string) akey); + return akey; +} + + +/////////////////// CODE BEGINS ////////////////// + + +default +{ + changed(integer change) { + if(change & CHANGED_REGION_START) { + llResetScript(); + } + } + + on_rez(integer start_param) + { + llResetScript(); + } + + state_entry() { + + llSetText("",<1,1,1>,1.0); // clr all hovertext- we may not be using it. + DoDelete(); // kill any NPC that is out running + Update("NPC"); // If dragged and ropped into a prim with any script named "NPC...", this will replace it. + GetSwitches(); // Get all default saved params from the Description + TimerEvent(TIMER); + } + + + touch_start(integer n) + { // if touched, make a menu + + if (checkPerms()) { + if (RecordPath == STATE) { + makeMenu(lAtButtons); + } else { + makeMainMenu(); + } + } else { + makeUserMenu(); + } + } + + // menu listener + listen(integer iChannel, string name, key id, string message) { + + if (MENU) { + llListenRemove(iHandle); + MENU = 0; // menu is off + iHandle = 0; + } + + if (message == "Stop NPC") + { + lNpcCommandList = []; // force reload of notecard + NPCEnabled = FALSE; + if (NPCKey() != NULL_KEY){ + Kill(sNPCName); + sNPCName = ""; + } else { + bNPC_STOP = TRUE; + makeText("Enter name of an NPC to stop"); + } + } + else if (message == "Menu" ) { + makeMainMenu(); + } + else if (message == "Erase RAM"){ + Clr(); + } + else if (message == "Relative"){ + relAbs = "Absolute"; + KeyValueSet("co","A"); // remember coordinates = A + Clr(); + } + else if (message == "Absolute"){ + relAbs = "Relative"; + KeyValueSet("co","R"); // remember coordinates = R + Clr(); + } + else if (message == "Recording"){ + DoMenuForCommands(); // show them the recording menu + } + else if (message == "Owner Only") { + priPub = "Group"; + KeyValueSet("pr","1"); + + llOwnerSay("Group members have control"); + makeMainMenu(); + } + else if (message == "Group") { + priPub = "Owner Only"; + KeyValueSet("pr","0"); + llOwnerSay("Only you have control"); + makeMainMenu(); + } + else if (message == "Sense is On") { + mSensor ="Sense is Off"; + KeyValueSet("se", "off"); + llOwnerSay(mSensor); + makeMainMenu(); + } + else if (message == "Sense is Off") { + mSensor ="Sense is On"; + llOwnerSay(mSensor); + KeyValueSet("se", "on"); + + NPCEnabled = FALSE; + + integer count = checkNoteCards(); + if (count >= 2) { + DEBUG("Notecards ok, DoProcessNPCLine"); + DoProcessNPCLine(); + return; + } + if (LSLEditor) { + DoProcessNPCLine(); + return; + } + + llOwnerSay("You have not saved a recording and/or appearance, so you cannot start a NPC"); + makeMainMenu(); + } + else if (message == "Appearance") { + llRemoveInventory(Appearance); // delete the notecard + osAgentSaveAppearance(kUserKey,Appearance); // make the ntecard + llOwnerSay("Your outfit has been saved"); + makeMainMenu(); + } + else if (message == "Save") { + if (llGetListLength(lCommands) == 0) { + llOwnerSay("Nothing recorded, you need to make a recording first"); + makeMainMenu(); + return; + } + DoSave(); + } + else if (message == "Help"){ + llLoadURL(kUserKey,"Click to view help","http://www.outworldz.com/opensim/posts/NPC/"); + makeMainMenu(); + } + else if (message == "Start NPC") { + integer count = checkNoteCards(); + + NPCEnabled = TRUE; + + if (LSLEditor) { + DoProcessNPCLine(); + return; + } + + if (count >= 2) { + DEBUG("Notecards approved , calling DoProcessNPCLine"); + SetStop(FALSE); // Let's run the notecard + DoProcessNPCLine(); + return; + } + + llOwnerSay("You have not saved a recording or maybe an appearance, so we cannot start a NPC"); + + } + else if (bNPC_STOP){ + bNPC_STOP = FALSE; + Kill(message); + } + else if (message == ">>"){ + makeMenu(lMenu2); + } + else if (message == ">>>"){ + makeMenu(lMenu3); + } + else if (message == "<<") { + makeMenu(lAtButtons); + } + else if (message == "<<<") { + makeMenu(lMenu2); + } + else if (message == "@comment"){ + Text("# ","Enter a comment",""); + } + else if (message == "@stop"){ + lCommands += "@stop"+ "\n"; + makeMenu(lAtButtons); + } + else if (message == "@run"){ + lCommands += "@run=" + Pos() + "\n"; + llOwnerSay("Recorded position: " + Pos()); + makeMenu(lAtButtons); + } + else if (message == "@fly"){ + lCommands += "@fly=" + Pos() + "\n"; + llOwnerSay("Recorded position: " + Pos()); + makeMenu(lAtButtons); + } + else if (message == "@land"){ + lCommands += "@land=" + Pos() + "\n"; + llOwnerSay("Recorded position: " + Pos()); + makeMenu(lAtButtons); + } + else if (message == "@walk") { + lCommands += "@walk=" + Pos() + "\n"; + llOwnerSay("Recorded position: " + Pos()); + makeMenu(lAtButtons); + } + else if (message == "@stop"){ + lCommands += "@stop"+ "\n"; + makeMenu(lAtButtons); + } + else if (message == "@sound"){ + Text("@sound=","Enter a sound name or UUID to trigger",""); + } + else if (message == "@randsound"){ + lCommands += "@randsound"+ "\n"; + makeMenu(lAtButtons); + } + else if (message == "@say") { + Text("@say=","Enter what the NPC will say",""); + } + else if (message == "@whisper"){ + Text("@whisper=","Enter what the NPC will whisper",""); + } + else if (message == "@shout"){ + Text("@shout=","Enter what the NPC will shout",""); + } + else if (message == "@wander") { + Text("@wander=","Enter radius to wander","Enter number of wanders"); + } + else if (message == "@pause") { + Text("@pause=","Enter time to pause",""); + } + else if (message == "@rotate") { + Text("@rotate=","Enter degrees to rotate",""); + } + else if (message == "@sit"){ + Text("@sit=","Enter name of object to sit on",""); + } + else if (message == "@touch"){ + Text("@touch=","Enter name of object to touch",""); + } + else if (message == "@cmd"){ + Text("@cmd=","Enter cjhannel to speak on","Enter text to speak"); + } + else if (message == "@stand"){ + lCommands += "@stand\n"; + llOwnerSay("Stand Recorded"); + makeMenu(lAtButtons); + } + else if (message == "@animate"){ + Text("@animate=","Enter animation name to play","Enter time to play the animation"); + } + else if (message == "@attach"){ + Text("@animate=","Enter inventory name to attach","Enter number of the attachment point (1-40)"); + } + else if (message == "@speed"){ + Text("@speed=","Enter a speed for the NPC, 1=100% normal speed, 0.5=50% speed",""); + } + + + // Save NPC name + else if (MakeNotecard == STATE) { + sNPCName = message; // in case we need to kill it. + + vector vDest = (vector) Pos(); + + if (relAbs == "Relative") + { + vDest -= llGetPos(); // just an offset for relative + } + sNotecard = "@spawn=" + message + "|" + (string) vDest + "\n"; + integer i; + integer j = llGetListLength(lCommands); + for (; i < j; i++){ + // get the command to save to the notecard + string line = llList2String(lCommands,i); + if (relAbs == "Absolute") { + sNotecard += line; // add the un-modified string to the notecard + } else { + // since we have to record absolute coords since we do not know where the box goes until they press Save, + // we process the absolute to relative conversion for walks here + list parts = llParseString2List(line,["="],[]); //get the @command + + if (llList2String(parts,0) == "@walk") { + vector vec = (vector) llList2String(parts,1) - llGetPos(); + sNotecard += "@walk=" + (string) vec + "\n"; + } + else if (llList2String(parts,0) == "@fly") { + vector vec = (vector) llList2String(parts,1) - llGetPos(); + sNotecard += "@fly=" + (string) vec + "\n"; + } + else if (llList2String(parts,0) == "@run") { + vector vec = (vector) llList2String(parts,1) - llGetPos(); + sNotecard += "@run=" + (string) vec + "\n"; + } + else if (llList2String(parts,0) == "@land") { + vector vec = (vector) llList2String(parts,1) - llGetPos(); + sNotecard += "@land=" + (string) vec + "\n"; + } + else { + sNotecard += line; // add the un-modified string to the notecard + } + } + } + llRemoveInventory(Notecard); // delete the old notecard + osMakeNotecard(Notecard,sNotecard); // Makes the notecard. + llSay(0,sNotecard); + llOwnerSay("Commands notecard has been written"); + STATE = 0; + } // MakeNotecard + + else if (! llStringLength(sParam2)) { + lCommands += sCommand + message + "\n"; + llOwnerSay("Recorded"); + makeMenu(lAtButtons); + } + else if (llStringLength(sParam2)){ + sCommand = sCommand + message + "|"; + llOwnerSay("Recorded"); + makeText(sParam2); + sParam2 = ""; + } + + } + + + + timer(){ + // DEBUG("tick"); + + // if llDialog is up, kill the listener for the dialog box. + if (iHandle) { + llOwnerSay("Menu timed out"); + llListenRemove(iHandle); + iHandle = 0; + return; // ^^^^^^^^^^^^^^^^^^^^^^^ + } + // if NoBodyHome, we are sensing for an avatar + else if (NobodyHome == STATE) { + ProcessSensor(); + return; + } + // if we are spawning, we need time to rez the NPC, then start processing NPC Commands. + else if (Spawning == STATE) { + STATE = 0; + TimerEvent(TIMER); + } + // We end aniamtions with a timer + else if (Animate == STATE){ + NPCAnimate(STAND); + TimerEvent(TIMER); + } + + else if (Walking == STATE) { + if (--iWaitCounter) { + if (llVecDist(osNpcGetPos(NPCKey()), newDest) > MAXDIST) { + return; + } + } + + DEBUG("At Destination: " + (string) newDest); + + // walk, fly, run, land + if (walkstate == 1) { + NPCAnimate(STAND); + NPCWalkOption = OS_NPC_NO_FLY; + } else if (walkstate == 2) { + // nothing + } else if (walkstate == 3) { + NPCAnimate(STAND); + NPCWalkOption = OS_NPC_NO_FLY; + } else if (walkstate == 4) { + llShout(FLIGHT,"landing"); + NPCAnimate(STAND); + NPCWalkOption = OS_NPC_NO_FLY; + } + } + // Wandering timer + else if (Wander == STATE) { + if (--iWaitCounter) { // wait 60 seconds to get to a destination. + if (llVecDist(osNpcGetPos(NPCKey()), vWanderPos) > MAXDIST) + return; + } + + // see if wander counter == 0, if so, stop walking, go to stand and process next line + if(RAMwc == 0) { + NPCAnimate(STAND); + DEBUG("Wander ended, calling DoProcessNPCLine"); + STATE = 0; + DoProcessNPCLine(); + return; + } + // one less time to wander around + RAMwc--; + NPCAnimate(STAND); + TimerEvent(TIMER); + StateWanderhold(); + return; + } + // Wandering requires us to re-wander when we reach a destination + else if (WanderHold == STATE) { + StateWander(); + TimerEvent(TIMER); + return; + } + else if (DoProcess == STATE) { + TimerEvent(QUICK); + } + + + STATE = 0; + + // We always process a NPC line at end of timer. + DEBUG("Tick end, calling DoProcessNPCLine"); + DoProcessNPCLine(); + } + + // sensors are used for sitting on prims + // Neo Cortex: added different SensorFunc states to trigger sit or touch + sensor(integer num) { + if (SensorFunc == 1) { + osNpcSit(NPCKey(), llDetectedKey(0), OS_NPC_SIT_NOW); + DEBUG("Seated, calling DoProcessNPCLine"); + SensorFunc = 0; + } else if (SensorFunc == 2) { + osNpcTouch(NPCKey(), llDetectedKey(0), LINK_THIS); + DEBUG("Touched, calling DoProcessNPCLine"); + SensorFunc = 0; + } + DoProcessNPCLine(); + } + no_sensor(){ + DEBUG ("no target prim located, calling DoProcessNPCLine"); + SensorFunc = 0; + DoProcessNPCLine(); + } + + + link_message(integer sender, integer num, string str, key id){ + DEBUG("Command In:" + str); + if (str=="@go") { + SetStop(FALSE); // Let's run the notecard + DEBUG("@go running"); + DoProcessNPCLine(); + } else { + Stack += [str]; // take anything, the controller will filter away non @ stuff + if (STATE == 0) { + DEBUG("calling DoNPC"); + DoProcessNPCLine(); + } else{ + DEBUG("Queued"); + } + } + } + +} + + + + + + diff --git a/Hypergrid Story Three/License and readme.txt b/Hypergrid Story Three/License and readme.txt new file mode 100644 index 00000000..ec773168 --- /dev/null +++ b/Hypergrid Story Three/License and readme.txt @@ -0,0 +1,18 @@ +Come visit us on the hypergrid at www.outworldz.com:9000 + +By Ferd Frederix -from the Free Script Library at http://www.outworldz.com + +LICENSE: + +This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. + +You are free to: + +Share — copy and redistribute the material in any medium or format +Adapt — remix, transform, and build upon the material +The licensor cannot revoke these freedoms as long as you follow the following terms: + +Attribution — You must give appropriate credit, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use. +NonCommercial — You may not use the material for commercial purposes. +ShareAlike — If you remix, transform, or build upon the material, you must distribute your contributions under the same license as the original. +No additional restrictions — You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits. \ No newline at end of file diff --git a/Hypergrid Story Three/Project/Project.prj b/Hypergrid Story Three/Project/Project.prj new file mode 100644 index 00000000..b17945e4 --- /dev/null +++ b/Hypergrid Story Three/Project/Project.prj @@ -0,0 +1,2 @@ + + diff --git a/Hypergrid Story Three/RockFall/Rock Prim/Notecard.txt b/Hypergrid Story Three/RockFall/Rock Prim/Notecard.txt new file mode 100644 index 00000000..7a831a35 --- /dev/null +++ b/Hypergrid Story Three/RockFall/Rock Prim/Notecard.txt @@ -0,0 +1,32 @@ +// :SHOW:1 +// :CATEGORY:Gaming +// :NAME:Noatcard for Rock Fall NPCS +// :AUTHOR:Ferd Frederix +// :KEYWORDS:Game, Sensor +// :REV:1.0 +// :WORLD:OpenSim +// :DESCRIPTION: +// Controls the path of the rockfall NPC. Requires a All in One NPC controller. +// :CODE: + +@spawn=Rhyolite|<75.82667, 53.47481, 31.85062> +@walk=<78.04362, 53.50777, 21.30894> +@stop +@pause=1 +@walk=<83.01588, 57.14717, 21.30894> + +@animate=avatar_blowkiss|2 + + +//////////// SECOND EXAMPLER NOTECARD + +Notecards for each rock differ slightly +@spawn=Vitric Tuff|<85.04330, 49.56506, 31.85062> +@walk=<84.35471, 52.02113, 21.30894> +@stop +@pause=1 +@walk=<84.35471, 52.02113, 21.30894> +@animate=avatar_jumpforjoy|2 + + + diff --git a/Hypergrid Story Three/RockFall/RockFall Image.png b/Hypergrid Story Three/RockFall/RockFall Image.png new file mode 100644 index 00000000..32d543be Binary files /dev/null and b/Hypergrid Story Three/RockFall/RockFall Image.png differ diff --git a/Hypergrid Story Three/RockFall/RockFall.prj b/Hypergrid Story Three/RockFall/RockFall.prj new file mode 100644 index 00000000..294660ad --- /dev/null +++ b/Hypergrid Story Three/RockFall/RockFall.prj @@ -0,0 +1,10 @@ + + + + + + + + + diff --git a/Hypergrid Story Three/RockFall/Rockfall Controller/rock controller.lsl b/Hypergrid Story Three/RockFall/Rockfall Controller/rock controller.lsl new file mode 100644 index 00000000..745ba553 --- /dev/null +++ b/Hypergrid Story Three/RockFall/Rockfall Controller/rock controller.lsl @@ -0,0 +1,119 @@ +// :SHOW: +// :CATEGORY:NPC +// :NAME:All-In one example Sequencer +// :AUTHOR:Ferd Frederix +/// :REV:1 +// :WORLD:Opensim +// :DESCRIPTION: +// Sample collision script for NPC sequence for the rock fall game + +// :CODE: +list things ; + +DoIt() +{ + things = [ -1,1,"@pause=1"]; + things = [ -1,8,"@sound=tremor"]; + + things += [ 2,4,"@say=Oooooh a Human fell down and went boom!"]; + things += [ 2,0.1,"@rotate=180"]; + things += [ 4,4,"@say=No one said rocks would be safe hahahaha!"]; + things += [ 2,5,"@say=Anyone want to bet this silly human will never figure out how to get to the end of this?"]; + + things += [ 3,2,"@say=Not me!"]; + things += [ 3,0.1,"@rotate=90"]; + things += [ 2,1,"@say=Not me!"]; + things += [ 2,0.1,"@rotate=90"]; + things += [ 3,2,"@fly=<81.33295, 53.93847, 34.28074>"]; + things += [ 3,2,"@fly=<83.37263, 66.61314, 33.87567>"]; + + things += [ 2,2,"@fly=<81.33295, 53.93847, 34.28074>"]; + things += [ 2,2,"@fly=<90.31698, 65.18839, 32.46637>"]; + + things += [ 4,1,"@say=No way ! Goodbye!"]; + things += [ 4,1,"@rotate=270"]; + things += [ 4,1,"@rotate=90"]; + things += [ 4,2,"@fly=<81.33295, 53.93847, 34.28074>"]; + things += [ 4,2,"@fly=<90.31698, 65.18839, 32.46637>"]; + + things += [ 5,1,"@rotate=270"]; + things += [ 5,2,"@rotate=90"]; + things += [ 5,2,"@say=I bet this human thinks everything here is worth their trust."]; + things += [ 5,2,"@fly=<81.33295, 53.93847, 34.28074>"]; + things += [ 5,0.1,"@fly=<90.49598, 68.05671, 35.85766>"]; + + things += [ 5,4,"@say=Good luck!"]; + things += [ -1,0.1,"@delete"]; + + Speak(); +} + +Speak() { + + integer prim = llList2Integer(things,0); + float time = llList2Float(things,1); + string msg = llList2String(things,2); + //llOwnerSay("Prim:" + (string) prim + " time:" + (string) time + " Msg:" + msg); + if (prim) { + things = llDeleteSubList(things,0,2); + llMessageLinked(prim,0, msg,""); + if (time > 0) { + llSetTimerEvent(time); + } else { + llOwnerSay("Whooops, time = 0!"); + llOwnerSay("Prim:" + (string) prim + " time:" + (string) time + " Msg:" + msg); + llSetTimerEvent(0); + } + } else { + llOwnerSay("Done"); + Reset(); + llSetTimerEvent(0); + } +} + +Reset() +{ + llSetStatus(STATUS_PHANTOM, FALSE); // Rev 2. + llVolumeDetect(FALSE); + llSleep(0.1); + llVolumeDetect(TRUE); +} + +default +{ + state_entry() + { + llSetText("",<1,1,1>,1.0); + Reset(); + } + + timer() + { + Speak(); + } + + collision_start(integer n) { + //llOwnerSay("Collided with " + llKey2Name(llDetectedKey(0))); + + if (! osIsNpc(llDetectedKey(0))) + { + llOwnerSay("Rock fall!"); + llTriggerSound("tremor",1.0); + DoIt(); + } + + } + + touch_start(integer p) + { + DoIt(); + } + + changed(integer what) + { + if (what & CHANGED_REGION_START) + { + llResetScript(); + } + } +} \ No newline at end of file diff --git a/Hypergrid Story Three/Tree Greeter/Collider/Tree Collider.lsl b/Hypergrid Story Three/Tree Greeter/Collider/Tree Collider.lsl new file mode 100644 index 00000000..60493daf --- /dev/null +++ b/Hypergrid Story Three/Tree Greeter/Collider/Tree Collider.lsl @@ -0,0 +1,51 @@ +// :SHOW: +// :CATEGORY:NPC +// :NAME:Collider +// :AUTHOR:Ferd Frederix +// :KEYWORDS: + +// :REV:1 +// :WORLD:Open Sim +// :DESCRIPTION: +// Sample collision script for NPC animator +// :CODE: + +Reset() +{ + llSetStatus(STATUS_PHANTOM, FALSE); + llSleep(0.1); + llVolumeDetect(FALSE); + llVolumeDetect(TRUE); +} + + + +default +{ + state_entry() + { + llSetText("",<1,1,1>,1.0); + Reset(); + llSetTimerEvent(3600); + } + + collision_start(integer n) { + + if (!osIsNpc(llDetectedKey(0))) { + llMessageLinked(LINK_SET,0, "@notecard=Hello",""); + } + } + timer() + { + llSetTimerEvent(3600); + Reset(); + } + + changed(integer what) + { + if (what & CHANGED_REGION_START) + { + llResetScript(); + } + } +} \ No newline at end of file diff --git a/Hypergrid Story Three/Tree Greeter/Root/!Appearance b/Hypergrid Story Three/Tree Greeter/Root/!Appearance new file mode 100644 index 00000000..baad4cc5 --- /dev/null +++ b/Hypergrid Story Three/Tree Greeter/Root/!Appearance @@ -0,0 +1,10 @@ +// :SHOW:1 +// :CATEGORY:Gaming +// :NAME:Placeholder +// :AUTHOR:Ferd Frederix +// :KEYWORDS:Game, conteoller +// :REV:1.0 +// :WORLD:OpenSim +// :DESCRIPTION: +// A Placeholder for a notecard +// :CODE: \ No newline at end of file diff --git a/Hypergrid Story Three/Tree Greeter/Root/!Path b/Hypergrid Story Three/Tree Greeter/Root/!Path new file mode 100644 index 00000000..2f5e0a42 --- /dev/null +++ b/Hypergrid Story Three/Tree Greeter/Root/!Path @@ -0,0 +1,17 @@ +// :SHOW:1 +// :CATEGORY:Gaming +// :NAME:Sample Path notecard for the Rockfall prims +// :AUTHOR:Ferd Frederix +// :KEYWORDS:Game +// :REV:1.0 +// :WORLD:OpenSim +// :DESCRIPTION: +// :CODE: + +@spawn=Wise Tree|<100.15351, 112.22149, 39.33461> + +@walk=<100.15351, 112.22149, 39.33461> +@wander=5|1 +@pause=3 +@wander=5|1 +@pause=3 diff --git a/Hypergrid Story Three/Tree Greeter/Root/Hello b/Hypergrid Story Three/Tree Greeter/Root/Hello new file mode 100644 index 00000000..89797c24 --- /dev/null +++ b/Hypergrid Story Three/Tree Greeter/Root/Hello @@ -0,0 +1,24 @@ +// :SHOW:1 +// :CATEGORY:Gaming +// :NAME:Path notecard +// :AUTHOR:Ferd Frederix +// :KEYWORDS:Game, conteoller +// :REV:1.0 +// :WORLD:OpenSim +// :DESCRIPTION: +// Sample path notecard. The controller switches to this when bumped. +// :CODE: + + +@say=Hello +@walk=<104.10423, 107.79723, 39.33461> +@walk=<105.77486, 109.27833, 39.33461> + +@animate=avatar_typing|3 +@say=You are aware this is a swamp? That animals cannot be trusted? +@pause=3 +@animate=avatar_typing|2 +@say=Your friends will be safe. This is a magical land, and the trees and fairies will protect them and you. Try not to fall in the water. +@animate=avatar_jumpforjoy|1 +@notecard=!Path + diff --git a/Hypergrid Story Three/Tree Greeter/Root/NPC Controller - All In One Rev 4.6.lsl b/Hypergrid Story Three/Tree Greeter/Root/NPC Controller - All In One Rev 4.6.lsl new file mode 100644 index 00000000..8ef35458 --- /dev/null +++ b/Hypergrid Story Three/Tree Greeter/Root/NPC Controller - All In One Rev 4.6.lsl @@ -0,0 +1,1618 @@ +// :SHOW:1 +// :CATEGORY:NPC +// :NAME:All In One NPC Recorder and Player +// :AUTHOR:Ferd Frederix +// :KEYWORDS:NPC, Puppeteer +// :ID:27 +// :REV:3.9 +// :WORLD:OpenSim +// :DESCRIPTION: +// All in one NPC recorder player. +// Supports both absolute and relative paths and many new commands +// Add animations named "Fly, Walk, Stand and Run" +// Click Prim to use. +// Should be worn as a HUD to record. +// Put it on the ground and click Sensor or Start NPC when done. +// :CODE: +// This is Rev 3.9 08/23/2015 + +// Revision History +// Rev 1.1 10-2-2014 @Sit did not work. Minor tweaks to casting for lslEditor +// Rev 1.2 10-14-2014 @ sit had wrong type. +// Rev 1.3 relative movement fixed for @fly +// Rev 1.4 4-3-2014 allow anyone to use this, non owners and non group members can only start and stop. +// Rev 1.5 5-17-2014 set sensor to auto start on reboot of sim +// Rev 1.6 5-24-2014 move menu so you can get it by touching, removed many of the KeyValues to RAM for efficiency +// Rev 1.7 CHANGED_REGION_START, not CHANGED_REGION_START (Opensim difference) +// Rev 1.8 tuned up Kill NPC, added more flexible upgrader +// Rev 1.9 Better script injection by link message// Rev 2.0 Added osSetSpeed so you can speed up or slow down an NPC. +// Rev 2.1 No laggy sensor used exept to sit on stuff +// Rev 2.2 Various sensor fixes +// Rev 2.3 Sets No Sensor in menu, must be started by hand +// Rev 2.4 - reserved for patches to 2.3 if needed +// Rev 3.0 Refactor out into subs, not states to make command injection easier +// New command: @appearance=Notecardname so you can switch to a new notecard on the fly +// New command: @speed=1.0 which slows up ( < 1 ) or speeds up ( > 1) +// Rev 3.1 Commands are not interruptible by Link Message +// Rev 3.2 Sensor patches for consistency in removing the NPC +// Rev 3.3 Added Touch command by Neo.Cortex@hbase42/hopto/org:8002 +// Added Menu 3 for notecard and appearance commands +// Rev 3.4 animation timer cannot be zero or it shuts off timer tweaked +// solves the NPC starting up when no sensor is set. +// Rev 3.5 fixes saving to !Path notecard +// Rev 3.6 08-11-2015 @delete acts like @stop. TYjhe NPC now rezzes after an @go back in where it was deleted +// Rev 3.7 08-11-2015 @attach command added to load an attachment from the inventory to the NPC +// Rev 3.8 08-17-2015 process queued commands one at a time without calling ProcessNPCLine on link message +// Rev 3.9 08-23-2011 Queued command fixes including @delete which were not always working +//*******************************************************************// + +// Instructions on how to use this is at http://www.outworldz.com/opensim/posts/NPC/ +// This is an OpenSim-only script. +// Author: Ferd Frederix aka Fred Beckhusen - fred@mitsi.com + +//////////////////////////////////////////////////////////////////////////////////////////// +// Original code was Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 // +/////////////////////////////////////////////////////////////////////////////////////////// +// Please see: http://www.gnu.org/licenses/gpl.html for legal details, // +// rights of fair usage, the disclaimer and warranty conditions. // +/////////////////////////////////////////////////////////////////////////////////////////// +// The original NPC controller was from http://was.fm/opensim:npc +// Extensive additions and bug fixes by Fred Beckhusem, aka Ferd Frederix, fred@mitsi.com +// llSensor had two params swapped +// @Wander would wander where it had rezzed, not where it was. +// There was no 'no_sensor' event in sit, so if a @sit failed, the NPC got stuck +// The animation and walks always stopped old, then started new. It should be start new, then stop old so the default stand would be suppressed. +// New code: +// Merged with new Route recorder and notecard writer +// If the NPC failed to reach a destination it never moved on. Added WAIT global to tune this +// Exposed many tunable variables and ported the code to LSLEditor. +// Added floating point to times in notecard. + +// Added @sound, @randsound, @whisper, @shout, and @cmd controls. +// +// notecards integers are not floats for better control +// +// Link Messages may be used to perform external control by injecting @commands into the stream of actions +// Example: +// To chat something, such as with a chat robot +// llMessageLinked(LINK_SET,0,"@npc_say=Hello",""); + +// This script assumes that NPCs and OSSl scripting is enabled in the OpenSim configuration. +// In order to enable them, the following changes must be made in the OpenSim.ini configuration file: +// +// ; Turn on OSSL +// AllowOSFunctions = true +// OSFunctionThreatLevel = Severe + +//[NPC] +// ;# {Enabled} {} {Enable Non Player Character (NPC) facilities} {true false} +// Enabled = true +// +// and then the server has to be restarted. + + +// Commands: All commands begin with an @ sign. All other lines are ignored +// @commands may have optional parameters. The syntax is always: +// @cmd=parm1|parm2 +// NaN in the table below meand Not a Number. This means there is no parameter + +//Command First Parameter Second Parameter Description +//@spawn name location (vector) Rezzes an NPC with name at a location. +//@appearance NoteCardName NaN switch the NPC appearance to a new notecard +//@walk destination (vector) NaN Makes the NPC walk to destination. +//@fly destination (vector) NaN Makes the NPC fly to destination. +//@land destination (vector) NaN Makes the NPC land at destination. +//@say string NaN Makes the NPC speak a phrase. +//@whisper string NaN Makes the NPC whisper a phrase. +//@shout string NaN Makes the NPC shout a phrase. +//@pause seconds (float) NaN Makes the NPC wait for a multiple of seconds. +//@wander radius (float) cycles (integer) Makes the NPC wander in radius, for cycles seconds. +//@delete NaN NaN Removes the NPC. Requires a link message to continue +//@goto label (string) NaN Jump to the label label in the script. +//@animate animation (string) time (float) Makes the NPC trigger the animation animation for time seconds. +//@sound sound_name NaN plays a sound from inventory +//@randsound NaN NaN Plays a random sound from inventory +//@rotate degrees (float) NaN Rotate the NPC degrees around the Z axis. +//@sit primitive name NaN Sit on a primitive with a given name. +//@touch primitive name NaN Touch on a primitive with a given name. +//@stand NaN NaN If sitting on a primitive, stand up. +//@cmd channel (integer) string Says string on channel, for controlling external gadgets +//@stop NaN NaN Halts the NPC script indefinitely. Can be started with a link message +//@go NaN NaN Continues on next notecard line, for use in link messages +//@speed speed (float) NaN from 0 to N, where 1.0 ius a normal speed of an avatar. 0.2 is a turtle. +//@notecard notename (string) NaN load a new Path notecard +//@attach InventoryName attachmentPoint load an attachment from the inventory to the NPC onto point + +// Constant attachmentPoint Comment +// ATTACH_CHEST 1 chest/sternum +// ATTACH_HEAD 2 head +// ATTACH_LSHOULDER 3 left shoulder +// ATTACH_RSHOULDER 4 right shoulder +// ATTACH_LHAND 5 left hand +// ATTACH_RHAND 6 right hand +// ATTACH_LFOOT 7 left foot +// ATTACH_RFOOT 8 right foot +// ATTACH_BACK 9 back +// ATTACH_PELVIS 10 pelvis +// ATTACH_MOUTH 11 mouth +// ATTACH_CHIN 12 chin +// ATTACH_LEAR 13 left ear +// ATTACH_REAR 14 right ear +// ATTACH_LEYE 15 left eye +// ATTACH_REYE 16 right eye +// ATTACH_NOSE 17 nose +// ATTACH_RUARM 18 right upper arm +// ATTACH_RLARM 19 right lower arm +// ATTACH_LUARM 20 left upper arm +// ATTACH_LLARM 21 left lower arm +// ATTACH_RHIP 22 right hip +// ATTACH_RULEG 23 right upper leg +// ATTACH_RLLEG 24 right lower leg +// ATTACH_LHIP 25 left hip +// ATTACH_LULEG 26 left upper leg +// ATTACH_LLLEG 27 left lower leg +// ATTACH_BELLY 28 belly/stomach/tummy +// ATTACH_LEFT_PEC 29 left pectoral +// ATTACH_RIGHT_PEC 30 right pectoral +// ATTACH_HUD_CENTER_2 31 HUD Center 2 +// ATTACH_HUD_TOP_RIGHT 32 HUD Top Right +// ATTACH_HUD_TOP_CENTER33 HUD Top +// ATTACH_HUD_TOP_LEFT 34 HUD Top Left +// ATTACH_HUD_CENTER_1 35 HUD Center +// ATTACH_HUD_BOTTOM_LEFT 36 HUD Bottom Left +// ATTACH_HUD_BOTTOM 37 HUD Bottom +// ATTACH_HUD_BOTTOM_RIGHT 38 HUD Bottom Right +// ATTACH_NECK 39 neck +// ATTACH_AVATAR_CENTER 40 avatar center/root + + + +////////////////////////////////////////////////////////// +// DEBUG // +////////////////////////////////////////////////////////// +integer debug = FALSE; // set to TRUE or FALSE for debug chat on various actions +integer Editor = FALSE; // set to to TRUE to working in LSLEditor, FALSE for in-world. + // you must also include the NPC commands found in the other script since LSLEditor does not support OpenSim +integer iTitleText = FALSE; // set to TRUE to see debug info in text above the controller + +////////////////////////////////////////////////////////// +// TUNABLE CONFIGURATION // +////////////////////////////////////////////////////////// +float TIMER = 2; // how often the system checks the distance traveled. Fastest you can go is 0.5 seconds +float QUICK = 1; // when we need to move to the next state, we use a QUICK timer +string Appearance = "!Appearance"; // The name of the recorded Appearance notecard +string Notecard = "!Path"; // The name of the recorded routes +integer allowUsers = FALSE; // If true, any user can get a Start NPC and Stop NPC menu. Only groups and owners can get all commands if TRUE, or FALSE +float MAXDIST = 2.0; // how close a NPC has to get to a dest pos to continue to next state. Do not lower this too much, as it may miss the target +integer WANDERRAND = TRUE; // set to TRUE and they will pause during wanders a random number of seconds +float WANDERTIME = 3.0; // how long they stand after each @wander,if WANDERRAND is FALSE. If WANDERRAND is TRUE, this is the max time +integer WAIT = 30; // wait for this number of seconds for the NPC to reach a destination (for safety). If it fails to reach a target, it will move on after this time. +float RANGE = 50; // 1 to N meters - anyone this close to the controller will start NPCS if Sensor button is clicked +float REZTIME = 2.0; // wait this long for NPC to rez in, then start the process +string STAND = "Stand"; // the name of the default Stand animation +string WALK = "Walk"; // the name of the default Walk animation +string FLY = "Fly"; // the name of the default Fly animation +string RUN = "Run"; // the name of the default Run animation +string LAND = "Land"; // the name of the default land animation ( for birds only) +float OffsetZ = 0.5; // appear 0.5 meter above ground, this is added to all destinations to keep them from sinking in. +float SPEEDMULT =0.5; // 1.0 = regular avatar speed. Smaller numbers slow down walks. Large numbers speed them up. +integer FLIGHT = 299; // For controlling wings. A channel that is shouted at when flight starts and ends. "flying" or "landing" + +// DESCRIPTIONS FIELDS HAVE TO SURVIVE A RESET +// These vars are stored by saving them with KeyValueSet +// "pr" is a 0 if it is set for Owner Only, 1 for Group control +// "se" is "on" if Started +// "co" = "R" or "A" for relative or absolute addressing mode +// "key" = NPC key + +// These Globals used to be stored in description. Moved to RAM in V1.6 +float RAMPause; // @pause param +float RAMwd ; // @wander distance +integer RAMwc; // @wander count +float RAMrot; // @rotate +string RAMsit; // @sit primname +string RAMtouch; // @touch primname +string RAManimationName; // @animate animation (string) time (float) +float RAManimationTime; + +// other globals section +integer iChannel; // a listen channel, randomly assigned +integer iHandle; // the handle to it + +// NPC controls +vector newDest ; // tmp storage for the walks +integer iWaitCounter ; // wait for this number of seconds for the NPC to reach a desrtination +string sNPCName; // the name of the NPC that may be in world. So we can remove it. +integer bNPC_STOP = FALSE; // boolean to reuse a listener +integer Stopped = FALSE; // set to TRUE by link messages so we do not remember them +float fTimerVal ; // how long we wait when wandering (calculated) +float NPCEnabled; // true if the NPC is suppodes to be running + +// OS_NPC_CREATOR_OWNED will create an 'owned' NPC that will only respond to osNpc* commands issued from scripts that have the same owner as the one that created the NPC. +// OS_NPC_NOT_OWNED will create an 'unowned' NPC that will respond to any script that has OSSL permissions to call osNpc* commands. +integer NPCOptions = OS_NPC_CREATOR_OWNED; // only yhe owner of this box can control this NPC. + +integer walkstate = 0; // helps us reshare the walk state for run, fly and land - a bit of a hack, but it saves RAM. Has to be done this way because some bits of NPCWalkOption are asserted as 0 + +integer NPCWalkOption; // Some notes for what happens to NPCWalkOption: +// OS_NPC_FLY - Fly the avatar to the given position. The avatar will not land unless the OS_NPC_LAND_AT_TARGET option is also given. +// OS_NPC_NO_FLY - Do not fly to the target. The NPC will attempt to walk to the location. If it's up in the air then the avatar will keep bouncing hopeless until another move target is given or the move is stopped +//OS_NPC_LAND_AT_TARGET - If given and the avatar is flying, then it will land when it reaches the target. If OS_NPC_NO_FLY is given then this option has no effect. +// OS_NPC_RUNNING - if given, NPC avatar moves at running/fast flying speed, otherwise moves at walking/slow flying speed. + +// menus +string mSensor="Sense is Off"; // Sensor or "No Sensor" + +list lAtButtons = ["Menu","-", ">>", "@run", "@walk", "@fly", "@land", "@wander", "@sit", "@stand","@animate","@rotate"]; +list lMenu2 = ["<<", "@comment", ">>>", "@stop", "@say", "@whisper","@shout","@sound","@randsound","@cmd", "@pause", "@delete"]; +list lMenu3 = ["<<<","@notecard","@appearance", "@touch", "@speed", "@attach", "-","-", "-", "-", "-", "-" ]; + +string sCommand; // place to store a command for two-prompted ones +string sParam2; // place to store a prompt for two-prompted ones +string priPub = "Owner Only"; // Private or Group +key kUserKey; // the person who is controlling the avatar, not the Owner +// the command lists +list lCommands; // commands are stored here +list lNPCScript; // Storage for the NPC script. +string npcAction; // Storage for the next action. @cmd=0|hello, this becomes @cmd +string npcParams; // Storage for the param, @cmd=0|hello, this becomes 0|hello + +// misc vars +string sNotecard; // commands are stored here temporarily for dumping +vector vWanderPos; // a place to wander to +string lastANIM ; // last animation run +// Sensor +integer avatarPresent; // Sensor sets this flag when people are within Range. + +// Coordinate control +vector vInitialPos ; // Vector that will be filled by the script with the initial starting position in region coordinates. +vector vDestPos = ZERO_VECTOR; // Storage for destination position. +string relAbs = "Relative"; // absolute vs relative positioning +vector lastKnownPos; // last known NPC position when we deleted it + +// STATES +integer MENU ; // processing a dialog box state, may be concurrent with STATE +integer STATE; // state storage +integer MakeNotecard = 1; // displaying a text box for NPC name +integer RecordPath = 2; // displaying a path notecard menu +integer NobodyHome = 3; // looking for an avatar +integer Spawning = 4; // spawning an avatar +integer Animate = 5; // animation timer needed +integer Walking = 6; // Hey! I am walking here! +integer Wander = 7; // Wandering around neeeds a timer, too +integer WanderHold = 8; // We reached a wander point +integer DoProcess = 9; // Set this to make it process a new command +integer Touch = 10; // Timer is busy sensing something to touch +integer Sit = 11; // Timer is busy sensing something to sit on +integer Paused = 12; // Timer is busy pausing + +key gNpcKey = NULL_KEY; // global key storage for the one NPC, to save CPU cycles +list Stack ; // a command stack from link message input + +integer SensorFunc = 0; // define which function shall be triggered inside the sensor function + // 0 means none, 1 sit, 2 touch +/////////////////////////////////////////////////////////////////////////// +// FUNCTIONS // +/////////////////////////////////////////////////////////////////////////// + + +SetStop(integer what) +{ + DEBUG("Stopped set to " + (string ) what); + Stopped = what; +} +// Do* functions are much like states from the old V2 scripts. + +// Save a Path notecard +DoSave() +{ + STATE = MakeNotecard; + makeText("Stand where you want the NPC to appear, and enter the NPC Name"); +} + +// This function is used to record the path for the NPC +// Each command can take 0, 1, or 2 params +DoMenuForCommands() { + makeMenu(lAtButtons); +} + + +// No one is here when sensors were on, so we kill off the NPC +DoNobodyHome() +{ + DEBUG("Nobody Home"); + STATE = NobodyHome; + if (NPCKey() != NULL_KEY) { + osNpcRemove(NPCKey()); + SaveKey(NULL_KEY); + } + TimerEvent(5); // keep ticking to sense avatars +} + +// Create a NPC +StateSpawn() { + DEBUG("state spawn"); + STATE = Spawning; + + + NPCEnabled = TRUE; // in world + // see if there is already one out there. + if (NPCKey() != NULL_KEY) { + DEBUG("Already living"); + return; + } + + + list name = llParseString2List(sNPCName, [" "], []); + + if (relAbs == "Relative"){ + vInitialPos += llGetPos(); + } + + DEBUG("Rezzing the NPC:" + (string) vInitialPos); + key aKey = osNpcCreate(llList2String(name, 0), llList2String(name, 1), vInitialPos, Appearance, NPCOptions); + + SaveKey(aKey ); // save in desceription and global, too + + osSetSpeed(aKey,SPEEDMULT); // 1.9 speed multiplier + TimerEvent(REZTIME); + NPCAnimate(STAND); +} + +DoRotate() { + DEBUG("@rotate=" + (string) RAMrot); + osNpcSetRot(NPCKey(), llEuler2Rot(<0,0,RAMrot> * DEG_TO_RAD)); +} + +StateSit() { + DEBUG ("state sit - looking for " + RAMsit); + STATE=Sit; + SensorFunc = 1; //triggers osNpcSit + llSensor(RAMsit, "", PASSIVE|ACTIVE|SCRIPTED, 96, PI); +} + +StateTouch() { + DEBUG ("state touch - looking for " + RAMtouch); + STATE = Touch; + SensorFunc = 2; //triggers osNpcTouch + llSensor(RAMtouch, "", PASSIVE|ACTIVE|SCRIPTED, 96, PI); +} + +DoStand() { + DEBUG("state stand"); + osNpcStand(NPCKey()); +} + + +StateAnimate() { + + DEBUG("state animate"); + STATE = Animate; + NPCAnimate(RAManimationName); + if (RAManimationTime <=0 ) // V 3.4 tweak + RAManimationTime = 1; + TimerEvent(RAManimationTime); +} + +StateWalk() { + + DEBUG("NPCWalkOption = " + (string) NPCWalkOption); + STATE = Walking; + + // walk, fly, run, land + if (walkstate == 1) { + NPCAnimate(WALK); + } else if (walkstate == 2) { + llShout(FLIGHT,"flying"); + NPCAnimate(FLY); + } else if (walkstate == 3) { + NPCAnimate(RUN); + } else if (walkstate == 4) { + NPCAnimate(LAND); + } + newDest = vDestPos ; + newDest.z += OffsetZ; + + // notecard is stored as offsets from this box with relative addressing. Convert to absolute + if (relAbs == "Relative"){ + newDest += llGetPos(); + } + + DEBUG("Moveto:" + (string) newDest); + osNpcMoveToTarget(NPCKey(), newDest, NPCWalkOption); + iWaitCounter = WAIT; // wait 60 seconds to get to a destination. + TimerEvent(TIMER); +} + + +StateWander(){ + DEBUG("state wander"); + STATE = Wander; + + vector point = CirclePoint(RAMwd); + DEBUG("CirclePoint:" + (string) point); + vWanderPos = vDestPos + point; + DEBUG("vWanderPos:" + (string) vWanderPos); + + fTimerVal = WANDERTIME; // default time to pause after each wander + if (WANDERRAND) + fTimerVal = llFrand(WANDERTIME) + 1; // override, they want random times + + NPCAnimate(WALK); + + DEBUG("Wander to:" + (string) vWanderPos); + + osNpcMoveToTarget(NPCKey(), vWanderPos, NPCWalkOption); + iWaitCounter = WAIT; // wait 60 seconds to get to a destination. + TimerEvent(TIMER); +} + +StateWanderhold() { + + DEBUG("Wander Hold"); + STATE = WanderHold; + + // now that we have reached a wander spot, slow the timer down to the desired value + TimerEvent(fTimerVal); +} + +// @pause=10 will do nothing for 10 seconds +DoPause() { + STATE =Paused; + if (RAMPause < 0.1) + RAMPause = 0.1; + DEBUG("@pause=" + (string)RAMPause); + TimerEvent(RAMPause); +} + + +// @stop makes the NPC stop moving in whatever state it is in. You have to linkmessage to get moving again +DoStop() { + DEBUG("NPC is Stopped"); + STATE = 0; // accept commands + SetStop(TRUE); // Link controlled - we mnust have a @go to continue with notecards + TimerEvent(0); + Stack = []; // v3.8 +} + +// @delete removes the NPC forever. Next command starts it up again at the beginning +DoDelete() { + DEBUG("state delete"); + STATE = 0; // accept commands + osNpcRemove(NPCKey()); + SaveKey(NULL_KEY); + + TimerEvent(0); + Stack = []; // v3.8 +} + +// change the appearance of the NPC +DoAppearance(string notecard) { + DEBUG("state appearance"); + if (llGetInventoryType(notecard) == INVENTORY_NOTECARD){ + DEBUG("Load appearance " + notecard); + osNpcLoadAppearance(NPCKey(),notecard); + } +} + +// Change the avatar speed +DoSpeed(string speed) { + float newspeed = (float) speed; + if (newspeed > 0.1 && newspeed < 5.0) {// sanity check + osSetSpeed(NPCKey(),newspeed); + } +} +DoNewNote (string card) { + DEBUG("Load Notecard " + card); + NPCReadNoteCard(card); + SetStop(FALSE); +} +DoAttach(string params) { + + list Data = llParseString2List(params, ["|"], []); + string itemName = llList2String(Data, 0); + integer attachmentPoint = (integer) llList2String(Data, 1); + if (attachmentPoint > 0 + && attachmentPoint < 40 + && llGetInventoryType(itemName) == INVENTORY_OBJECT + ) + { + osForceAttachToOtherAvatarFromInventory(NPCKey(),itemName,attachmentPoint); + } +} + +// This loops over the notecard, processing each command +DoProcessNPCLine() { + DEBUG("ProcessNPCLine, stopped = " + (string) Stopped); + STATE = DoProcess; + + // auto load a notecard + if (! llGetListLength(lNPCScript)) { + DEBUG("Read Notecard"); + NPCReadNoteCard(Notecard); + } + + // look for link messages on the stack + string next = llList2String(Stack,0); // lets see if there is anithing from a link message + if (llStringLength(next)) + { + Stack = llDeleteSubList(Stack,0,0); + ProcessCmd(next); //lets do this command instead. + return; + } + + // @stop issued? + if (Stopped) { + TimerEvent(0); + DEBUG("Stopped, waiting for input"); + STATE = 0; + return; + } + + // No, we have an @go for liftoff + next = llList2String(lNPCScript, 0); // get the next command + DEBUG("Execute:" + next); + lNPCScript = llDeleteSubList(lNPCScript, 0, 0); // delete it + + if (llGetListLength(lNPCScript) == 0) { + DEBUG("EOF"); + } + ProcessCmd(next); +} + + + +ProcessCmd(string cmd) { + + DEBUG("ProcessCmd:" + cmd); + + if (llGetSubString(cmd, 0, 0) != "@") { + DEBUG("ignoring"); + STATE = DoProcess; + TimerEvent(QUICK); // this is so we do not recurse the stack + STATE = 0; + return; + } + + list data = llParseString2List(cmd, ["="], []); + npcAction = llToLower(llStringTrim(llList2String(data, 0), STRING_TRIM)); + + DEBUG("Action:" + npcAction); + npcParams = llStringTrim(llList2String(data, 1), STRING_TRIM); + + @commands; + + ProcessSensor(); + + + if(npcAction == "@spawn") { + DEBUG("@spawn"); + list spawnData = llParseString2List(npcParams, ["|"], []); + sNPCName =llList2String(spawnData, 0); // V 1.6 name in RAM + + list spawnDest = llParseString2List(llList2String(spawnData, 1), ["<", ",", ">"], []); + vInitialPos.x = llList2Float(spawnDest, 0); + vInitialPos.y = llList2Float(spawnDest, 1); + vInitialPos.z = llList2Float(spawnDest, 2); + + DEBUG("Coords for NPC at " + (string) vInitialPos); + StateSpawn(); + return; + } + + if (! avatarPresent){ + DoNobodyHome(); + DEBUG("No avatar nearby"); + STATE = 0; + return; + } else { + if ( NPCKey() == NULL_KEY) { + StateSpawn(); + } + } + + if(npcAction == "@stop") { + DoStop(); + STATE = 0; + return; + } + else if(npcAction == "@goto") { + DEBUG("goto"); + integer lastIdx = llGetListLength(lNPCScript)-1; + lNPCScript = llDeleteSubList(lNPCScript, lastIdx, lastIdx); + // Wind commands till goto label. + @wind; + string next1 = llList2String(lNPCScript, 0); + lNPCScript = llDeleteSubList(lNPCScript, 0, 0); + lNPCScript += next1; + if(next1 != npcParams) jump wind; + // Wind the label too. + next1 = llList2String(lNPCScript, 0); + lNPCScript = llDeleteSubList(lNPCScript, 0, 0); + lNPCScript += next1; + // Get next command. + list data1 = llParseString2List(next1, ["="], []); + npcAction = llToLower(llStringTrim(llList2String(data1, 0), STRING_TRIM)); + npcParams = llStringTrim(llList2String(data1, 1), STRING_TRIM); + // Reschedule. + jump commands; + } + else if(npcAction == "@sound") { + DEBUG("sound"); + llTriggerSound(npcParams,1.0); + } + else if(npcAction == "@randsound") { + DEBUG("@randsound"); + integer N = llGetInventoryNumber(INVENTORY_SOUND); + integer rand = llCeil(llFrand(N)) -1; // pick a random sound + string toPlay = llGetInventoryName(INVENTORY_SOUND,rand); + llTriggerSound(toPlay,1.0); + } + else if(npcAction == "@walk") { + DEBUG("@walk"); + GetDest(npcParams); + walkstate = 1;// walking + NPCWalkOption = OS_NPC_NO_FLY ; + StateWalk(); + return; + } + else if(npcAction == "@fly") { + GetDest(npcParams); + walkstate = 2;// flying + NPCWalkOption = OS_NPC_FLY ; + StateWalk(); + return; + } + else if(npcAction == "@run") { + DEBUG("@run"); + GetDest(npcParams); + walkstate = 3;// running + NPCWalkOption = OS_NPC_NO_FLY | OS_NPC_RUNNING; + StateWalk(); + return; + } + else if(npcAction == "@land") { + DEBUG("@land"); + GetDest(npcParams); + walkstate = 4;// landing + NPCWalkOption= OS_NPC_FLY | OS_NPC_LAND_AT_TARGET ; + StateWalk(); + return; + } + else if(npcAction == "@say") { + DEBUG("@say " + npcParams); + osNpcSay(NPCKey(), 0, npcParams); + } + else if(npcAction == "@shout") { + DEBUG("@shout"); + osNpcShout(NPCKey(),0, npcParams); + } + else if(npcAction == "@whisper") { + DEBUG("@whisper " + npcParams); + osNpcWhisper(NPCKey(),0, npcParams); + } + // speak a command on a channel, so you can open doors and control stuff. + else if(npcAction == "@cmd") { + DEBUG("@cmd"); + list dataToSpeak = llParseString2List(npcParams, ["|"], []); + string channel = llList2String(dataToSpeak,0); + DEBUG("Channel:"+(string) channel); + integer iChannel = (integer) channel; + string stringToSpeak = llList2String(dataToSpeak,1); + llSay(iChannel, stringToSpeak); + } + // stop everything + else if(npcAction == "@pause") { + RAMPause = (float) npcParams; + DoPause(); + return; + } + else if(npcAction == "@wander") { + list wanderData = llParseString2List(npcParams, ["|"], []); + RAMwd = (float) llList2String(wanderData, 0); + RAMwc = (integer) llList2String(wanderData, 1); + vDestPos = osNpcGetPos(NPCKey()); // set the wander start + DEBUG("Starting at " + (string) vDestPos); + StateWander(); + return; + } + else if(npcAction == "@rotate") { + RAMrot = (float) npcParams; + DoRotate(); + } + else if(npcAction == "@sit") { + RAMsit= npcParams; + StateSit(); + return; + } + else if(npcAction == "@touch") { + RAMtouch= npcParams; + StateTouch(); + return; + } + else if(npcAction == "@stand") { + DoStand(); + } + else if(npcAction == "@delete") { + DoDelete(); + SetStop(TRUE); // Link controlled - we mnust have a @go to continue with notecards + return; + } + else if(npcAction == "@animate") { + list animateData = llParseString2List(npcParams, ["|"], []); + RAManimationName = llList2String(animateData, 0); + RAManimationTime = (float) llList2String(animateData, 1); + StateAnimate(); + return; + } + else if(npcAction == "@appearance" ){ + DoAppearance(npcParams); + } + else if (npcAction =="@speed") { + DoSpeed(npcParams); + } + else if (npcAction =="@notecard") { + DoNewNote(npcParams); + Notecard = npcParams; + } + else if (npcAction == "@attach") + { + DoAttach(npcParams); + } + + STATE = 0; + TimerEvent(QUICK); // yeah I know, not possible this fast, we just go as fast as we can go - this is so we do not recurse the stack +} + + + +/////////////////// UTILITY Functions, not state-like ////////////////// + +// DEBUG(string) will chat a string or display it as hovertext if debug == TRUE +DEBUG(string str) { + if (debug) + llOwnerSay( str); // Send the owner debug info so you can chase NPCS + if (iTitleText) { + llSetText(str,<1.0,1.0,1.0>,1.0); // show hovertext + + } +} + +GetDest(string npcParams) { + list dest = llParseString2List(npcParams, ["<", ",", ">"], []); + vDestPos.x = llList2Float(dest, 0); + vDestPos.y = llList2Float(dest, 1); + vDestPos.z = llList2Float(dest, 2); +} + +NPCReadNoteCard(string Note) { + DEBUG("NPCReadNoteCard"); + lNPCScript = llParseString2List(osGetNotecard(Note), ["\n"], []); +} + +integer SenseAvatar() +{ + //Returns a strided list of the UUID, position, and name of each avatar in the region + list avatars = llGetAgentList(AGENT_LIST_REGION ,[]); + integer numOfAvatars = llGetListLength(avatars); + if (numOfAvatars == 0) + { + DEBUG("No people"); + return 0; + } + //DEBUG("Located " + (string)numOfAvatars + " avatars and NPC's"); + + integer nAvatars; + integer i; + for( i = 0;i < numOfAvatars; i++) { + key aviKey = llList2Key(avatars,i); + if (!osIsNpc(aviKey)) { + list detail = llGetObjectDetails(aviKey,[OBJECT_POS]); + vector pos = llList2Vector(detail,0); + float dist = llVecDist(pos, llGetPos()); + if (dist < RANGE) + { + nAvatars++; + DEBUG("In range:" + llKey2Name(aviKey)); + } + } + } + //DEBUG("Located " + (string) nAvatars + " avatars"); + return nAvatars; +} + +// return TRUE if the avatar is owner when private is set, or TRUE if the avatar is in the same group and GROUP is set. +integer checkPerms() { + + integer group = (integer) KeyValueGet("pr"); + if (! group) + priPub = "Owner Only"; + else + priPub = "Group"; + + + if (llDetectedKey(0) == llGetOwner()){ + kUserKey = llDetectedKey(0); + return TRUE; + } + + if ( group && llDetectedGroup(0)) { + kUserKey = llDetectedKey(0); + return TRUE; + } + kUserKey = llDetectedKey(0); + return FALSE; +} + + + +NPCAnimate(string anim) +{ + DEBUG("Start Anim: " + anim); + if (llGetInventoryType(anim) == INVENTORY_ANIMATION ) { + + if (lastANIM != anim) { + if(llStringLength(lastANIM)) { + osNpcStopAnimation(NPCKey(), lastANIM); + } + osNpcPlayAnimation(NPCKey(), anim); + lastANIM = anim; + } + } else { + llSay(DEBUG_CHANNEL, "No animation named " + anim); + } +} + + +TimerEvent(float timesent) +{ + DEBUG("Setting timer: " + (string) timesent); + llSetTimerEvent(timesent); +} + +// Kill a NPC by Name +Kill(string param) +{ + integer count; + list avatars = osGetAvatarList(); // Returns a strided list of the UUID, position, and name of each avatar in the region except the owner.\ + integer i; + integer j = llGetListLength(avatars); + for (i=0 ; i <= j; i+=3){ + + string desired = llList2String(avatars,i+2); + desired = llStringTrim(desired,STRING_TRIM); // should not be needed but is needed + + if (desired == param){ + vector v = llList2Vector(avatars,i+1); + key target = llList2Key(avatars,i); // get the UUID of the avatar + osNpcRemove(target); + SaveKey(NULL_KEY ); + llOwnerSay("Removed " + param+ " at location " + (string) v); + count++; + } + } + + NPCEnabled = FALSE; // not in world + + if (count) + llOwnerSay("Removed " + (string) count + " NPC's"); + else + llOwnerSay("Could not locate " + param); +} + + +// return a String for the position we are at. Strings used as the caller wants strings +string Pos() +{ + vector where = llGetPos(); // find the box position + + where.z += OffsetZ; // use the ground position + an offset + + if (Editor) + where = <128,128,31 + llFrand(1)>; // center of sim for editing + + // if attached the height will be too high by 1/2 the agent size + if (llGetAttached()) { + vector size = llGetAgentSize(llGetOwner()); + float Z = size.z; + where.z -= Z/2; + } + + // DEBUG("Pos= " + (string) where); + return (string) where; +} + +// setup a menu with a timer for timeouts, called by all make*() +menu() +{ + llListenRemove(iHandle); + iChannel = llCeil(llFrand(100000) + 20000); + iHandle = llListen(iChannel,"","",""); + TimerEvent(30.0); + MENU = TRUE; +} + +// make a text box +makeText(string Param) +{ + menu(); + llTextBox(kUserKey, Param, iChannel); +} + +// top level menu +makeMainMenu() +{ + menu(); + list buttons = ["Appearance","Recording","Save","Help","-","Erase RAM", priPub,relAbs,"-","Stop NPC",mSensor,"Start NPC"]; + llDialog(kUserKey,(string) llGetListLength(lCommands) + " Records",buttons,iChannel); +} + + +// Rev 1.4 +// top level menu for non group/ non owners +makeUserMenu() +{ + if (!allowUsers) return; + + menu(); + list buttons = ["Start NPC","Stop NPC"]; + llDialog(kUserKey,"Choose",buttons,iChannel); +} + + + +// programmable menu for @commands +makeMenu(list buttons) +{ + menu(); + llDialog(kUserKey,(string) llGetListLength(lCommands) + " Record",buttons,iChannel); +} + + +// make one or two text boxes with prompts +Text(string cmd, string p1, string p2) +{ + sCommand = cmd; + sParam2 = ""; + if (llStringLength(p2)) + sParam2 = p2; + + makeText(p1); +} + +// Set the Avatar Present flag - if sensors are off and we are forece run, there will be one present. +ProcessSensor() +{ + integer SensorOn; + if ("on" == KeyValueGet("se")) + { + SensorOn = TRUE; // we need to scan for avatars + } else { + SensorOn = FALSE; // we need to scan for avatars + } + DEBUG("Sensor:" + (string) SensorOn); + + integer n = SenseAvatar(); + + DEBUG("Avatars:" + (string) n); + if (SensorOn && n) + avatarPresent = TRUE; // someone is here and we need to tell the system to run + else if (SensorOn && !n) + avatarPresent = FALSE; // someone is not here and we need to tell the system to stop + else { // sensor is off, lete see if there is a NPC. If so, we are ON + DEBUG("NPCEnabled:" + (string) NPCEnabled); + if (NPCEnabled) + avatarPresent = TRUE; + else + avatarPresent = FALSE; + } + + // start up from when when no one is near + if (avatarPresent && STATE == NobodyHome) + STATE = 0; + + //DEBUG("Avatar Present: " + (string) avatarPresent); +} + +vector CirclePoint(float radius) { + float x = llFrand(radius *2) - radius; // +/- radius, randomized + float y = llFrand(radius *2) - radius; // +/- radius, randomized + return ; // so this should always happen +} + +string KeyValueGet(string var) { + list dVars = llParseString2List(llGetObjectDesc(), ["&"], []); + do { + list data = llParseString2List(llList2String(dVars, 0), ["="], []); + string k = llList2String(data, 0); + if(k != var) jump continue; + //DEBUG("got " + var + " = " + llList2String(data, 1)); + return llList2String(data, 1); + @continue; + dVars = llDeleteSubList(dVars, 0, 0); + } while(llGetListLength(dVars)); + return ""; +} + +KeyValueSet(string var, string val) { + + //DEBUG("set " + var + " = " + val); + list dVars = llParseString2List(llGetObjectDesc(), ["&"], []); + if(llGetListLength(dVars) == 0) + { + llSetObjectDesc(var + "=" + val); + return; + } + list result = []; + do { + list data = llParseString2List(llList2String(dVars, 0), ["="], []); + string k = llList2String(data, 0); + if(k == "") jump continue; + if(k == var && val == "") jump continue; + if(k == var) { + result += k + "=" + val; + val = ""; + jump continue; + } + string v = llList2String(data, 1); + if(v == "") jump continue; + result += k + "=" + v; + @continue; + dVars = llDeleteSubList(dVars, 0, 0); + } while(llGetListLength(dVars)); + if(val != "") result += var + "=" + val; + llSetObjectDesc(llDumpList2String(result, "&")); +} + + +// clear RAM +Clr() { + + lCommands = []; + llOwnerSay("RAM Memory cleared. Notecards, if any, are not modified."); + makeMainMenu(); +} + +integer checkNoteCards() +{ + // Check that they have saved an Appeaance and Path notecard + integer num = llGetInventoryNumber(INVENTORY_NOTECARD); // how many notecards overall + + integer i; + integer count; + for (; i < num; i++){ + if (llGetInventoryName(INVENTORY_NOTECARD,i) == Notecard) + count++; + if (llGetInventoryName(INVENTORY_NOTECARD,i) == Appearance) + count++; + } + DEBUG("Checked " + (string) count + " Notecards"); + // if we have both, run the NPC + return count; +} + +Update(string SName) { + + // delete all NPC*scripts except myself + integer i; + integer j = llGetInventoryNumber(INVENTORY_SCRIPT); + for (i = 0; i < j; i++) { + string name = llGetInventoryName(INVENTORY_SCRIPT,i); + string match = llGetSubString(name,0,2); + if (match == SName && llGetScriptName() != name) + { + llRemoveInventory(name); + llOwnerSay("Upgraded"); + } + } + +} + +// Get all default saved params from the Description +GetSwitches() +{ + string rA = KeyValueGet("co"); // Get the remembered menu setting for Abs Vs Relative + if (rA == "A") + relAbs = "Absolute"; + else if (rA == "R") + relAbs = "Relative"; + else + relAbs = "Absolute"; + + + // reenable NPC if sensor is on. + if ("on" == KeyValueGet("se")) + { + NPCEnabled = TRUE; + mSensor = "Sense is On"; + ProcessSensor(); // fake 1 avatar to get it rezzed + } else { + mSensor = "Sense is Off"; + } + } + + +SaveKey(key akey) +{ + DEBUG("Saving Key of " + (string) akey); + KeyValueSet("key", akey); + if (akey != (key) KeyValueGet("key") ) + { + DEBUG("Fatal error, cannot save key"); + } + gNpcKey = akey; +} + + +key NPCKey() +{ + key akey = gNpcKey; // from cached copy + // gNpcKey saves a lot of CPU processing by caching the key, if blank we get it from the description + if (gNpcKey == NULL_KEY) + { + //DEBUG("Get DKey"); + akey = KeyValueGet("key"); // from Description of the prim + } + // DEBUG("NPC KEY:" + (string) akey); + return akey; +} + + +/////////////////// CODE BEGINS ////////////////// + + +default +{ + changed(integer change) { + if(change & CHANGED_REGION_START) { + llResetScript(); + } + } + + on_rez(integer start_param) + { + llResetScript(); + } + + state_entry() { + + llSetText("",<1,1,1>,1.0); // clr all hovertext- we may not be using it. + DoDelete(); // kill any NPC that is out running + Update("NPC"); // If dragged and ropped into a prim with any script named "NPC...", this will replace it. + GetSwitches(); // Get all default saved params from the Description + llSetTimerEvent(TIMER); + } + + + touch_start(integer n) + { // if touched, make a menu + + if (checkPerms()) { + if (RecordPath == STATE) { + makeMenu(lAtButtons); + } else { + makeMainMenu(); + } + } else { + makeUserMenu(); + } + } + + // menu listener + listen(integer iChannel, string name, key id, string message) { + + if (MENU) { + llListenRemove(iHandle); + MENU = 0; // menu is off + iHandle = 0; + } + + if (message == "Stop NPC") + { + lNPCScript = []; // force reload of notecard + NPCEnabled = FALSE; + if (NPCKey() != NULL_KEY){ + Kill(sNPCName); + sNPCName = ""; + } else { + bNPC_STOP = TRUE; + makeText("Enter name of an NPC to stop"); + } + } + else if (message == "Menu" ) { + makeMainMenu(); + } + else if (message == "Erase RAM"){ + Clr(); + } + else if (message == "Relative"){ + relAbs = "Absolute"; + KeyValueSet("co","A"); // remember coordinates = A + Clr(); + } + else if (message == "Absolute"){ + relAbs = "Relative"; + KeyValueSet("co","R"); // remember coordinates = R + Clr(); + } + else if (message == "Recording"){ + DoMenuForCommands(); // show them the recording menu + } + else if (message == "Owner Only") { + priPub = "Group"; + KeyValueSet("pr","1"); + + llOwnerSay("Group members have control"); + makeMainMenu(); + } + else if (message == "Group") { + priPub = "Owner Only"; + KeyValueSet("pr","0"); + llOwnerSay("Only you have control"); + makeMainMenu(); + } + else if (message == "Sense is On") { + mSensor ="Sense is Off"; + KeyValueSet("se", "off"); + llOwnerSay(mSensor); + makeMainMenu(); + } + else if (message == "Sense is Off") { + mSensor ="Sense is On"; + llOwnerSay(mSensor); + KeyValueSet("se", "on"); + + NPCEnabled = FALSE; + + integer count = checkNoteCards(); + if (count >= 2) { + DEBUG("Notecards approved , calling DoProcessNPCLine"); + DoProcessNPCLine(); + return; + } + if (Editor) { + DoProcessNPCLine(); + return; + } + + llOwnerSay("You have not saved a recording and/or appearance, so you cannot start a NPC"); + makeMainMenu(); + } + else if (message == "Appearance") { + llRemoveInventory(Appearance); // delete the notecard + osAgentSaveAppearance(kUserKey,Appearance); // make the ntecard + llOwnerSay("Your outfit has been saved"); + makeMainMenu(); + } + else if (message == "Save") { + if (llGetListLength(lCommands) == 0) { + llOwnerSay("Nothing recorded, you need to make a recording first"); + makeMainMenu(); + return; + } + DoSave(); + } + else if (message == "Help"){ + llLoadURL(kUserKey,"Click to view help","http://www.outworldz.com/opensim/posts/NPC/"); + makeMainMenu(); + } + else if (message == "Start NPC") { + integer count = checkNoteCards(); + + NPCEnabled = TRUE; + + if (Editor) { + DoProcessNPCLine(); + return; + } + + if (count >= 2) { + DEBUG("Notecards approved , calling DoProcessNPCLine"); + SetStop(FALSE); // Let's run the notecard + DoProcessNPCLine(); + return; + } + + llOwnerSay("You have not saved a recording or maybe an appearance, so we cannot start a NPC"); + + } + else if (bNPC_STOP){ + bNPC_STOP = FALSE; + Kill(message); + } + else if (message == ">>"){ + makeMenu(lMenu2); + } + else if (message == ">>>"){ + makeMenu(lMenu3); + } + else if (message == "<<") { + makeMenu(lAtButtons); + } + else if (message == "<<<") { + makeMenu(lMenu2); + } + else if (message == "@comment"){ + Text("# ","Enter a comment",""); + } + else if (message == "@stop"){ + lCommands += "@stop"+ "\n"; + makeMenu(lAtButtons); + } + else if (message == "@run"){ + lCommands += "@run=" + Pos() + "\n"; + llOwnerSay("Recorded position: " + Pos()); + makeMenu(lAtButtons); + } + else if (message == "@fly"){ + lCommands += "@fly=" + Pos() + "\n"; + llOwnerSay("Recorded position: " + Pos()); + makeMenu(lAtButtons); + } + else if (message == "@land"){ + lCommands += "@land=" + Pos() + "\n"; + llOwnerSay("Recorded position: " + Pos()); + makeMenu(lAtButtons); + } + else if (message == "@walk") { + lCommands += "@walk=" + Pos() + "\n"; + llOwnerSay("Recorded position: " + Pos()); + makeMenu(lAtButtons); + } + else if (message == "@stop"){ + lCommands += "@stop"+ "\n"; + makeMenu(lAtButtons); + } + else if (message == "@sound"){ + Text("@sound=","Enter a sound name or UUID to trigger",""); + } + else if (message == "@randsound"){ + lCommands += "@randsound"+ "\n"; + makeMenu(lAtButtons); + } + else if (message == "@say") { + Text("@say=","Enter what the NPC will say",""); + } + else if (message == "@whisper"){ + Text("@whisper=","Enter what the NPC will whisper",""); + } + else if (message == "@shout"){ + Text("@shout=","Enter what the NPC will shout",""); + } + else if (message == "@wander") { + Text("@wander=","Enter radius to wander","Enter number of wanders"); + } + else if (message == "@pause") { + Text("@pause=","Enter time to pause",""); + } + else if (message == "@rotate") { + Text("@rotate=","Enter degrees to rotate",""); + } + else if (message == "@sit"){ + Text("@sit=","Enter name of object to sit on",""); + } + else if (message == "@touch"){ + Text("@touch=","Enter name of object to touch",""); + } + else if (message == "@cmd"){ + Text("@cmd=","Enter cjhannel to speak on","Enter text to speak"); + } + else if (message == "@stand"){ + lCommands += "@stand\n"; + llOwnerSay("Stand Recorded"); + makeMenu(lAtButtons); + } + else if (message == "@animate"){ + Text("@animate=","Enter animation name to play","Enter time to play the animation"); + } + else if (message == "@attach"){ + Text("@animate=","Enter inventory name to attach","Enter number of the attachment point (1-40)"); + } + else if (message == "@speed"){ + Text("@speed=","Enter a speed for the NPC, 1=100% normal speed, 0.5=50% speed",""); + } + + + // Save NPC name + else if (MakeNotecard == STATE) { + sNPCName = message; // in case we need to kill it. + + vector vDest = (vector) Pos(); + + if (relAbs == "Relative") + { + vDest -= llGetPos(); // just an offset for relative + } + sNotecard = "@spawn=" + message + "|" + (string) vDest + "\n"; + integer i; + integer j = llGetListLength(lCommands); + for (; i < j; i++){ + // get the command to save to the notecard + string line = llList2String(lCommands,i); + if (relAbs == "Absolute") { + sNotecard += line; // add the un-modified string to the notecard + } else { + // since we have to record absolute coords since we do not know where the box goes until they press Save, + // we process the absolute to relative conversion for walks here + list parts = llParseString2List(line,["="],[]); //get the @command + + if (llList2String(parts,0) == "@walk") { + vector vec = (vector) llList2String(parts,1) - llGetPos(); + sNotecard += "@walk=" + (string) vec + "\n"; + } + else if (llList2String(parts,0) == "@fly") { + vector vec = (vector) llList2String(parts,1) - llGetPos(); + sNotecard += "@fly=" + (string) vec + "\n"; + } + else if (llList2String(parts,0) == "@run") { + vector vec = (vector) llList2String(parts,1) - llGetPos(); + sNotecard += "@run=" + (string) vec + "\n"; + } + else if (llList2String(parts,0) == "@land") { + vector vec = (vector) llList2String(parts,1) - llGetPos(); + sNotecard += "@land=" + (string) vec + "\n"; + } + else { + sNotecard += line; // add the un-modified string to the notecard + } + } + } + llRemoveInventory(Notecard); // delete the old notecard + osMakeNotecard(Notecard,sNotecard); // Makes the notecard. + llSay(0,sNotecard); + llOwnerSay("Commands notecard has been written"); + STATE = 0; + } // MakeNotecard + + else if (! llStringLength(sParam2)) { + lCommands += sCommand + message + "\n"; + llOwnerSay("Recorded"); + makeMenu(lAtButtons); + } + else if (llStringLength(sParam2)){ + sCommand = sCommand + message + "|"; + llOwnerSay("Recorded"); + makeText(sParam2); + sParam2 = ""; + } + + } + + + + timer(){ + // DEBUG("tick"); + + // if llDialog is up, kill the listener for the dialog box. + if (iHandle) { + llOwnerSay("Menu timed out"); + llListenRemove(iHandle); + iHandle = 0; + return; // ^^^^^^^^^^^^^^^^^^^^^^^ + } + // if NoBodyHome, we are sensing for an avatar + else if (NobodyHome == STATE) { + ProcessSensor(); + return; + } + // if we are spawning, we need time to rez the NPC, then start processing NPC Commands. + else if (Spawning == STATE) { + STATE = 0; + TimerEvent(TIMER); + } + // We end aniamtions with a timer + else if (Animate == STATE){ + NPCAnimate(STAND); + TimerEvent(TIMER); + } + + else if (Walking == STATE) { + if (--iWaitCounter) { + if (llVecDist(osNpcGetPos(NPCKey()), newDest) > MAXDIST) { + return; + } + } + + DEBUG("At Destination: " + (string) newDest); + + // walk, fly, run, land + if (walkstate == 1) { + NPCAnimate(STAND); + NPCWalkOption = OS_NPC_NO_FLY; + } else if (walkstate == 2) { + // nothing + } else if (walkstate == 3) { + NPCAnimate(STAND); + NPCWalkOption = OS_NPC_NO_FLY; + } else if (walkstate == 4) { + llShout(FLIGHT,"landing"); + NPCAnimate(STAND); + NPCWalkOption = OS_NPC_NO_FLY; + } + } + // Wandering timer + else if (Wander == STATE) { + if (--iWaitCounter) { // wait 60 seconds to get to a destination. + if (llVecDist(osNpcGetPos(NPCKey()), vWanderPos) > MAXDIST) + return; + } + + // see if wander counter == 0, if so, stop walking, go to stand and process next line + if(RAMwc == 0) { + NPCAnimate(STAND); + DEBUG("Wander ended, calling DoProcessNPCLine"); + STATE = 0; + DoProcessNPCLine(); + return; + } + // one less time to wander around + RAMwc--; + NPCAnimate(STAND); + TimerEvent(TIMER); + StateWanderhold(); + return; + } + // Wandering requires us to re-wander when we reach a destination + else if (WanderHold == STATE) { + StateWander(); + TimerEvent(TIMER); + return; + } + else if (DoProcess == STATE) { + TimerEvent(QUICK); + } + + + STATE = 0; + + // We always process a NPC line at end of timer. + DEBUG("Tick end, calling DoProcessNPCLine"); + DoProcessNPCLine(); + } + + // sensors are used for sitting on prims + // Neo Cortex: added different SensorFunc states to trigger sit or touch + sensor(integer num) { + if (SensorFunc == 1) { + osNpcSit(NPCKey(), llDetectedKey(0), OS_NPC_SIT_NOW); + DEBUG("Seated, calling DoProcessNPCLine"); + SensorFunc = 0; + } else if (SensorFunc == 2) { + osNpcTouch(NPCKey(), llDetectedKey(0), LINK_THIS); + DEBUG("Touched, calling DoProcessNPCLine"); + SensorFunc = 0; + } + DoProcessNPCLine(); + } + no_sensor(){ + DEBUG ("no target prim located, calling DoProcessNPCLine"); + SensorFunc = 0; + DoProcessNPCLine(); + } + + + link_message(integer sender, integer num, string str, key id){ + DEBUG("Command In:" + str); + if (str=="@go") { + SetStop(FALSE); // Let's run the notecard + DEBUG("@go running"); + DoProcessNPCLine(); + } else { + Stack += [str]; // take anything, the controller will filter away non @ stuff + if (STATE == 0) { + DEBUG("calling DoNPC"); + DoProcessNPCLine(); + } else{ + DEBUG("Queued"); + + } + } + } + +} + + + + + + diff --git a/Hypergrid Story Three/Tree Greeter/Tree Greeter.prj b/Hypergrid Story Three/Tree Greeter/Tree Greeter.prj new file mode 100644 index 00000000..51cb9c93 --- /dev/null +++ b/Hypergrid Story Three/Tree Greeter/Tree Greeter.prj @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + diff --git a/MOAP Top 2000 Radio Stations Player/Top_Radio_Stations_Player/Object/Top_Radio_Stations_Player_1.lsl b/MOAP Top 2000 Radio Stations Player/Top_Radio_Stations_Player/Object/Top_Radio_Stations_Player_1.lsl index 90344b90..fd75e4ec 100644 --- a/MOAP Top 2000 Radio Stations Player/Top_Radio_Stations_Player/Object/Top_Radio_Stations_Player_1.lsl +++ b/MOAP Top 2000 Radio Stations Player/Top_Radio_Stations_Player/Object/Top_Radio_Stations_Player_1.lsl @@ -4,7 +4,7 @@ // :AUTHOR:Ferd Frederix // :KEYWORDS: // :CREATED:2012-09-04 15:30:52.010 -// :EDITED:2015-06-12 16:41:11 +// :EDITED:2015-12-05 19:25:59 // :ID:902 // :NUM:1278 // :REV:1.1 diff --git a/NPC Manager/NPC Manager.sol b/NPC Manager/NPC Manager.sol new file mode 100644 index 00000000..37e91b93 --- /dev/null +++ b/NPC Manager/NPC Manager.sol @@ -0,0 +1,3 @@ + + + diff --git a/NPC Manager/NPC Manager/NPC Manager.prj b/NPC Manager/NPC Manager/NPC Manager.prj new file mode 100644 index 00000000..f4b22f39 --- /dev/null +++ b/NPC Manager/NPC Manager/NPC Manager.prj @@ -0,0 +1,6 @@ + + + + + diff --git a/NPC Manager/NPC Manager/Paramour NPC Manager/Paramour NPC Manager.lsl b/NPC Manager/NPC Manager/Paramour NPC Manager/Paramour NPC Manager.lsl new file mode 100644 index 00000000..0e60d001 --- /dev/null +++ b/NPC Manager/NPC Manager/Paramour NPC Manager/Paramour NPC Manager.lsl @@ -0,0 +1,210 @@ +// :SHOW: +// :CATEGORY:NPC +// :NAME:NPC Manager +// :AUTHOR:Aine Caoimhe +// :KEYWORDS: +// :CREATED:2015-11-24 20:37:08 +// :EDITED:2015-11-24 19:37:08 +// :ID:1091 +// :NUM:1865 +// :REV:1 +// :WORLD:OpenSim +// :DESCRIPTION: +// Wear or rez to ground, then touch to be able to globally or selectively remove NPCs from a region +// :CODE: +// Paramour NPC Manager +// by Aine Caoimhe (Mata Hari)(c. LACM) April 2015 +// Provided under Creative Commons Attribution-Non-Commercial-ShareAlike 4.0 International license. +// Please be sure you read and adhere to the terms of this license: https://creativecommons.org/licenses/by-nc-sa/4.0/ +// +// Wear or rez to ground, then touch to be able to globally or selectively remove NPCs from a region +// OSSL functions required: +// osGetAvatarList() +// osNpcRemove() +// osIsNpc() +// +// USER SETTINGS +float dialogTimeout=60.0; // how long to wait before timing out the dialog if no response received +integer ownerOnlyTouch=TRUE; // TRUE = only owner to can touch to activate, FALSE = anyone can use it +string floatyText="Paramour NPC Manager\n(right-click and take a copy)"; // text to have floating above the object or supply an empty string ( "") for none +vector floatyTextColour=<1.000, 0.906, 0.502>; // LSL vector colour to use for the text (<0.0, 0.0, 0.0> = black, <1.0, 1.0, 1.0> = white) +// +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// DON'T CHANGE ANYTHING BELOW HERE UNLESS YOU KNOW WHAT YOU'RE DOING +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +list npcList; +integer indNpc; +list globalButtons=["DONE","-","KILL ALL","< PREV","RESCAN","NEXT >"]; +integer myChannel; +integer handle; +key user; + +showMenu() +{ + string txtDia="Select a NPC to remove\nor KILL ALL to remove all NPCs\nor RESCAN to refresh the list\n\nNPCs currently in the region: "+(string)(llGetListLength(npcList)/4-1)+"\n"; + list butDia; + integer i=indNpc; + while (llGetListLength(butDia)<6) + { + if ((i*4)>=llGetListLength(npcList)) butDia=butDia+["-"]; + else + { + butDia=[]+butDia+[(string)i]; + txtDia+="\n"+(string)i+". "+llList2String(npcList,i*4+2)+" at "+llList2String(npcList,i*4+3); + } + i++; + } + butDia=[]+globalButtons+llList2List(butDia,3,5)+llList2List(butDia,0,2); + handle=llListen(myChannel,"",user,""); + llSetTimerEvent(dialogTimeout); + llDialog(user,txtDia,butDia,myChannel); +} +doRemoveNpc(integer ind) +{ + if (llGetAgentSize(llList2Key(npcList,ind+1))!=ZERO_VECTOR) osNpcRemove(llList2Key(npcList,ind+1)); + llRegionSayTo(user,0,"Removed NPC "+llList2String(npcList,ind+2)+" from location "+llList2String(npcList,ind+3)); + npcList=[]+llDeleteSubList(npcList,ind,ind+3); +} +doKillAll() +{ + integer i; + integer l=llGetListLength(npcList); + for (i=4;i=llGetListLength(npcList)) indNpc=1; + if (indNpc==-5) indNpc=(integer)(llGetListLength(npcList)/4.0 -6.0); + if (indNpc<1) indNpc=1; +} +string vec2Int0String(vector vConvert) +{ + // returns a vector as a string with values rounded to nearest whole number + string r="<"+(string)(llRound(vConvert.x))+", "+(string)(llRound(vConvert.y))+", "+(string)(llRound(vConvert.z))+">"; + return r; +} +default +{ + state_entry() + { + myChannel=0x80000000|(integer)("0x"+(string)llGetKey()); + finishedUsing(); + llSetText(floatyText,floatyTextColour,1.0); + } + timer() + { + llRegionSayTo(user,0,"Dialog has timed out. Please touch me again to resume"); + stopListening(); + finishedUsing(); + } + on_rez(integer food) + { + llResetScript(); + } + changed (integer change) + { + if (change & CHANGED_REGION_START) llResetScript(); + else if (change & CHANGED_OWNER) llResetScript(); + else if (change & CHANGED_REGION) llResetScript(); + } + touch_start(integer num) + { + key toucher=llDetectedKey(0); + if (toucher!=user) + { + if (user==NULL_KEY) + { + if (ownerOnlyTouch && (toucher!=llGetOwner())) llRegionSayTo(toucher,0,"Sorry, only the owner can use this"); + else + { + doRescan(); + if (!indNpc) llRegionSayTo(toucher,0,"There are currently no NPCs in this region"); + else + { + user=toucher; + showMenu(); + } + } + } + else llRegionSayTo(toucher,0,"Sorry, this is already being used by someone else"); + } + else showMenu(); + } + listen(integer channel, string name, key who, string message) + { + stopListening(); + if (message=="DONE") finishedUsing(); + else if (message=="KILL ALL") doKillAll(); + else if (message=="-") showMenu(); + else if (message=="RESCAN") + { + doRescan(); + showMenu(); + } + else if ((message=="< PREV")||(message=="NEXT >")) + { + if (message=="NEXT >") indNpc+=6; + else indNpc-=6; + checkIndex(); + showMenu(); + } + else + { + // message is number of the NPC to remove + doRemoveNpc(4*((integer)message)); + if (llGetListLength(npcList)<=4) + { + llRegionSayTo(user,0,"There are no more NPCs remaining in the region"); + finishedUsing(); + } + else + { + checkIndex(); + showMenu(); + } + } + } +} diff --git a/PMAC/PARAMOUR MULTI-ANIMATION CONTROLLER (PMAC)/Object/!!!READ ME - 1. Overview, Feature List, Change Log, License.txt b/PMAC/PARAMOUR MULTI-ANIMATION CONTROLLER (PMAC)/Object/!!!READ ME - 1. Overview, Feature List, Change Log, License.txt new file mode 100644 index 00000000..79ed0358 --- /dev/null +++ b/PMAC/PARAMOUR MULTI-ANIMATION CONTROLLER (PMAC)/Object/!!!READ ME - 1. Overview, Feature List, Change Log, License.txt @@ -0,0 +1,251 @@ +// :SHOW: +// :CATEGORY:NPC +// :NAME:PMAC +// :AUTHOR:Aine Caoimhe +// :KEYWORDS: +// :CREATED:2015-11-24 20:38:40 +// :EDITED:2015-11-24 19:38:40 +// :ID:1095 +// :NUM:1871 +// :REV:1 +// :WORLD:Second Life +// :DESCRIPTION: +// PARAMOUR MULTI-ANIMATION CONTROLLER (PMAC) v1.02(OSSL) +// :CODE: + +// Provided under Creative Commons Attribution-Non-Commercial-ShareAlike 4.0 International license. +// Please be sure you read and adhere to the terms of this license: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +PARAMOUR MULTI-ANIMATION CONTROLLER (PMAC) v1.02 +by Aine Caoimhe (c. LACM) January 2015 + +**** OVERVIEW ***** + +The Paramour Multi-Animation Controller (PMAC) is a no-poseball script system +intended for use in +almost any piece of furniture that needs to be able to position and animate +multiple avatars at once. +PMAC is designed to offer a high performance alternative to existing systems +such as MLP, nPose, and +xPose. PMAC uses a core engine that leverages the powerful OSSL functions +available only in Opensim + to drastically increase the script's speed and reliability, while at the same +time using only a + fraction of the sim resources of the existing systems. + + +***** KEY FEATURES ***** + +* WORKS IN ANYTHING - The PMAC system can be put into anything, from a +single-prim scultpy blanket or +one-piece mesh sofa to an elaborate multi-piece bedroom linkset. You can put it +in a simple pine cube +prim if you feel like it. There's nothing extra to attach and hide or position +nearby. All it takes +is a single prim. The only "requirement" is that PMAC must be in the root prim +of an object. + +* NO POSEBALLS - There are no poseballs used at all for PMAC. None to disguise +or hide as part of the + furniture, none that have to be rezzed and sat on, none to get lost inside a +sofa or buried underground + or scattered around a sim...all of the users simply sit on the furniture item +that contains the main +script and PMAC takes care of the rest using a "virtual poseball" approach that +exists within the logic of the script. + +* A TRUE MULTI-AVATAR CONTROLLER - PMAC systems can be set up to handle +animations designed for 1 to as + many as 9 avatars with the flexibility to load any preset configuration on the +fly. You could set it up + in a sofa to act like a sit AO when you're lounging around alone, and quickly +turn it into a couples +controller when that special someone comes to visit, and then switch it into a +multi-person handler + when you have even more company...and it's all available virtually instantly +from the menu without +the need to stand up or reset...all adjustments are done on the fly during use. +Now your VL furniture + items can be just as flexible as your RL ones are. + +* REGION-FRIENDLY - Most multi-avatar controllers are pretty harsh on a +simulator, eating up valuable +cycles, and memory and other script resources. PMAC's extensive use of OSSL +functions allows it to +drastically reduce this overhead. The core PMAC system is a single script. Yep, +a fully functional +controller for up to 9 avi all done with one low-footprint script. When not in +use, it is completely +dormant and uses virtually no simulator resources at all (just the tiny amount +of memory required for +the script itself). Even when in use, the system's footprint barely changes -- +unlike SL systems with +poseball scripts, animation handler scripts, multiple timers, multiple +listeners, high message traffic, +large menu memory consumption, and all the other complications that LSL-based +scripts are subject to. +Even with 9 avatars seated all it uses is a single script and single listener. +The only time it ever uses a timer is on those rare occasions when you're +editing positions or setting up a new system. + +* SPEED - The total time to initialize a typical PMAC system is approximately +one second. That's right, +just 1 second! If for some reason you need to reset it you'll be ready to use it +again almost instantly. +Due to its unique OSSL-based menu method, PMAC dialogs pop up right away even +for very extensive set-ups +with hundreds of different animations. + +* SYNCH - PMAC uses the tried and true Paramour synch method, keeping up to 9 +avatars moving in perfect +unison once their animations are in your viewer's cache. + +* FULL NPC INTEGRATION - Want some company but none of your friends are online? +PMAC can rez a NPC +(or two, or three, or up to eight!) to join you instead. When you're done, PMAC +tucks them away again +for next time. You can pick who, where, and when at the touch of a few simple +dialog buttons. + + +* AUTO-MODE - Don't want to have to change your own animations? Simply engage +PMAC's auto mode and + let the system do it for you, more or less like engaging a sit AO for up to 9 +people. Timing can be chosen from a variety of options and changed on the fly; +and you can configure the system to engage it automatically so you can have your +furniture fully useable without ever even needing to see a dialog (but of course +you can simply touch the object to bring up the menu and disengage this at any +time). + +* SWAPPING - You can quickly and easily swap positions with anyone else -- even +if they aren't there + and it's just an unoccupied "virtual position". + +* AUTO HEIGHT ADJUSTMENTS - PMAC automatically adjusts positions based on each +avatar's height (although admittedly this is a very rudimentary estimate and +depends a lot on how the original animation was created). In many cases this +will be sufficient unless you have a very tall or short avatar. + +* ON-THE-FLY POSITION ADJUSTMENTS - The owner can make more detailed adjustments +to animation positioning at any time, rapidly and on the fly, and it remains +temporarily stored in the script's memory. + +* NO NOTECARD EDITING FOR USERS - If the owner wishes to persist any position +adjustments to the system's notecards, PMAC facilitates this by handling it all +done with the touch of a single dialog button. No more copy-pasting reams of +text from chat into a notecard and hoping you don't mess something up! Typically +the only time you'll manually open and edit a notecard is when making major +changes such as adding new animations or deleting existing ones. + +* EASY CONFIGURATION - PMAC can be fully configured in the script or via an +optional configuration notecard which some users might find a little less +intimidating to do. + +* USER ACCESS RESTRICTIONS - The PMAC system has a variety of settings allowing +you to control who can use it, who can access which menus or animations, who can +rez which NPCs, and other similar access-control restrictions. + +* POWERFULL ADD-ON COMMAND SYSTEM INTEGRATION - As a design goal, I wanted the +PMAC system to be easy and intuitive enough to just "rez and play" without what +can be a rather nasty learning curve of many other systems. I also felt it was +imperative to optimize PMAC's performance, simulator footprint, and XEngine +demands. As a result, only the most important and commonly-demanded features are +part of the core "out of the box" PMAC system. But I also wanted to offer the +flexibility for more advanced builders and users to enhance its functions, +capabilities and offerings, so PMAC incorporates an integrated and extremely +powerful command system that opens up almost endless possibilities via the use +of scripted add-ons. If PMAC doesn't do something you want it to do, it's very +likely that an add-on can be scripted to do it in conjunction with PMAC's +command system -- at the expense of the additional script(s) overhead. I will +likely create add-ons for the most often-requested extra features; but anyone +with reasonable scripting skills can write their own custom applications for +specialized requirements. The only limits are likely to be your imagination (and +ability to script it). + +* FREE - The PMAC system is the culmination of many months of work and almost 5 +years of development and testing of various approaches to making the "next +generation" multi-avatar controller, and I'm releasing it to the Opensim +community for free (under Creative Commons Attribution-Non-Commercial-ShareAlike +4.0 International license). Any add-ons I create for it in future will be free, +too, and I sincerely hope that other add-on creators will follow suit and donate +their enhancements to the community as well. + +* COMPANION MLP CONVERTER - Thanks to Seth Nygard, existing MLP 2.x systems can +be easily converted to PMAC in a matter of minutes using a supplied conversion +script. Look for the "PMAC Builder's Kit" which contains everything you need to +take an existing MLP system and turn it into a PMAC system. + +***** CHANGE LOG ***** + +v. 1.0 January 2015 (Initial beta release) + +v. 1.01 March 2015 (General release) +- NEW: added ability to supply a configuration notecard that will be read on +initialization and override scripted variable values (written by Seth Nygard - +thanks Seth!) +- TWEAK: tweaked Seth's configuration addition to support both his nameset as +well as the actual variable names being supplied +- TWEAK: slight further optimizations of memory use +- TWEAK: system is now made completely inactive if worn +- TWEAK: only owner is now notified when initialization completes +- FIX: minor text tweaks and typo corrections + +v 1.02 May 2015 (Update Release) +- BUGFIX/OVERSIGHT: add flag so if an addon rezzes an object core is prevented +from interpreting this as an edit handle being rezzed and entering edit mode +- BUGFIX: fixed a bug in edit mode list wrapping where advancing from the last +animation in a group using "NEXT" would pull the incorrect data for the next +(first) animation. +- TWEAK: set a minimal sit target on root to allow sitting on it from distances +greater than 10m +- TWEAK: When displaying animation number range in dialog menu, display the +actual range instead of possible range +- NEW: added support for the Object-Rezzing prop add-on "NC_PROP" by Neo Cortex +(code from Neo) +- NEW/TWEAK: Altered script logic to optionally allow NPCs to occupy a PMAC +object with no "real" avatars being present and added a user configuration +variable to enable or disable this feature. By default it will be set to FALSE. +If the NPC is to be rezzed by another object/script that script will also need +to handle seating it, later unseating it, and removing it from the scene. +Initially rezzing a PMAC NPC still requires an avatar user but if that user +stands when NPC flag is TRUE, the NPC will not be removed (I'll need to sit down +again and remove it). Be careful NOT to reset the core script when a PMAC NPC is +still in the scene since this will strand it. +- NEW/TWEAK: Added new user option to show the groups menu instead of the +current group's animation menu when you first initiate the dialog. Subsequently +if you close the dialog and re-show it, you should received whatever your most +recent dialog was unless someone else was in control in the interim. + + +**** UPDATING FROM PMAC 1.01 TO PMAC 1.02 ***** + +- if you use in-script user settings instead of the configuration notecard, +transfer your settings to the PMAC core 1.02 script. +- delete the Core 1.01 script from your object's inventory +- place a copy of the Core 1.02 script into the object's inventory - it should +compile and enable itself automatically +- replace the old READ ME 1 and READ ME 2 notecards with the updated versions. +The other three core read me notecards remain unchanged. +- no further changes are require...your system is ready for use +- OPTIONAL: if you want to use the new object-rezzer add-on by Neo Cortex, place +a copy of it in the object's inventory and follow the instructions he supplied +for setting up your animations to use it. The MLP parser by Seth Nygard does not +currently support conversion of MLP rezzed objects so you will need to do this +manually. + + +***** LICENSE ***** + +All PMAC scripts and documentation are my own creation and released under +Creative Commons Attribution-Non-Commercial-ShareAlike 4.0 International +license. Please be sure you read and adhere to the terms: +https://creativecommons.org/licenses/by-nc-sa/4.0/ + +Unless otherwise specified, animations contained in any Paramour product are +*not* my creation but were obtained under equivalent CC terms. If you are the +creator of one of the animations and do not wish it distributed please notify me +and I will immediately remove it from all current and future offerings. + +***** CONTACT ***** + +You can most easily contact me in world at refugegrid.com:8002 or on G+ diff --git a/PMAC/PARAMOUR MULTI-ANIMATION CONTROLLER (PMAC)/Object/!!!READ ME - 2. Owner Initial Setup Instructions.txt b/PMAC/PARAMOUR MULTI-ANIMATION CONTROLLER (PMAC)/Object/!!!READ ME - 2. Owner Initial Setup Instructions.txt new file mode 100644 index 00000000..3846fcf7 --- /dev/null +++ b/PMAC/PARAMOUR MULTI-ANIMATION CONTROLLER (PMAC)/Object/!!!READ ME - 2. Owner Initial Setup Instructions.txt @@ -0,0 +1,420 @@ +// :SHOW: +// :CATEGORY:NPC +// :NAME:PMAC +// :AUTHOR:Aine Caoimhe +// :KEYWORDS: +// :CREATED:2015-11-24 20:38:40 +// :EDITED:2015-11-24 19:38:40 +// :ID:1095 +// :NUM:1872 +// :REV:1 +// :WORLD:OpenSim +// :DESCRIPTION: +// PARAMOUR MULTI-ANIMATION CONTROLLER (PMAC) v1.02 (OSSL) +// :CODE: +PARAMOUR MULTI-ANIMATION CONTROLLER (PMAC) v1.0 (OSSL) +by Aine Caoimhe January 2015 + +OWNER INITIAL SET-UP AND INSTALLATION + + +**** OVERVIEW ***** + +A new PMAC system is usually supplied inside an object that is already set up and configured for general use, +containing all of the necessary animations, notecards, etc. In most cases you can simply "rez and play" right +away provided your region is already set up to allow the necessary script functions. + +This notecard contains details configuring regions correctly to allow PMAC to work, details about some of the +user settings you may wish to change, and trouble-shooting tips. + + +The step-by-step overview for a new user is to set (or confirm) the following: + +1. Ensure the server supports the script +2. Ensure the region is configured to allow the necessary functions +3. Rez the object +4. If necessary, reset or even recompile the scripts +5. Adjust any user settings according to preference + + +*************************************** +1. Ensure Server Support of the Script +**************************************** + +PMAC uses both LSL and OSSL functions that make extensive use of vectors and rotations for positioning and text-based + notecards for data storage. The server must use the period (decimal point)(.) as the decimal separator for numbers that +require this precision (called "floats"). + +In almost all cases your server will already have this configuration (otherwise other scripts would also have issues) +but if you experience problems with odd behaviour this is the first thing to check. Some computer localizations (particularly +linux systems in Europe) may be configured to use the comma (,) as the decimal separator which will *not* +work with this system (and many other LSL scripts). Consult your operating system guide as to how to + configure the computer to use the decimal point separator. + + +******************************************************************** +2. Ensure the Region is Configured to Allow the Necessary Functions +******************************************************************** + +The PMAC script makes extensive use of a special set of functions that are only available in Opensim: OSSL functions. +Depending on your region's configuration, these may or may not be available so it is essential to check and ensure that +the correct settings have been made. As a general rule of thumb: + +- By default, stock Opensim builds will need changes to the region configuration +- By default, the Diva distribution is pre-configured correctly and will require no changes +- Most open grid configurations will require at least some changes +- Many region hosting services will require some changes, although this is slowly changing, and for many +such providers any changes to the ini files must be made by their staff. If that's the case for you, please +provide them with a copy of this information. + +****** NOTE: there have been recent changes to OSSL function set-up and enabling ***** +The method for setting OSSL functions changed as of Opensim Git #2e1f5bb (r/25931 2015-04-14) so the instructions +below may not be fully applicable to your region's set-up. Please see: +http://ainetutorials.blogspot.ca/2015/04/notice-ossl-implementation-changes-for.html for further information +You will still need to enable NPC in the [NPC section] in either case. + +Unless you are using a special custom configuration (such as the Diva distribution) the configuration changes that +need to be made are located in the /bin/opensim.ini file that is in the same directory as the opensim.exe for the +instance that runs the region. + +Scroll down through the opensim.ini file and look for the [NPC] section and ensure that NPCs are enabled to allow + PMAC to rez them in the region + +[NPC] + Enabled = true + +Now scroll through the opensim.ini file and find the [XEngine] section -- usually it is before the [NPC] section. +There are a number of changes that need to be made here. First, we need to generally allow OSSL functions, then +we need to ensure that some of the ones used by PMAC are both available and suitably "protected" against abuse +by other visitors to your region. + +In the [XEngine] section find the line where the AllowOSFunctions variable is set and ensure it is set to true. + +[XEngine] + AllowOSFunctions = true + +A little further down, look for OSFunctionThreatLevel which sets a global "threat level" for OSSL functions. +If an OSSL has this threat level or less, it will be allowed for any script that wants to use it. By default +Opensim has this set to VeryLow but in my opinion a global Low setting is perfectly secure. + +[XEngine] + OSFunctionThreatLevel = Low + +PMAC uses a number of functions that are "higher threat" than this, though. You could just set a far higher +global threat level but then you would expose yourself and your region guests to malicious visitors so I strongly +advise against doing so. Instead, you can set specific functions to be available only to a limited range of users. +The options are to specify one or more of the following to have permission to use each function (the one(s) you use +will depend on your specific set-up): + +- ESTATE_OWNER allows scripts owned by the estate owner of the region to use those functions +- ESTATE_MANAGER does the same for scripts owned by one of your estate managers +- PARCEL_OWNER does the same for scripts owned by a parcel owner and located in that parcel +- PARCEL_GROUP_MEMBER does the same for scripts owned by a member of the same group that the parcel +- by specifying the UUID of a user (or multiple users) to allow scripts they own to use the functions + +Note that in all cases it is the script owner (usually whoever rezzed the object) that is checked against +the permission, not the person using it. + +It is beyond the scope of these instructions to go any further into this so if you wish to get absolutely all +of the details see: http://opensimulator.org/wiki/Threat_level + +For someone who is a region owner (usually this means ESTATE_OWNER) I would suggest setting all OSSL functions +to be available at least for any scripts that you own, and in many cases probably for those belonging to any E +STATE_MANAGER you assign (since to give them those powers you probably trust them sufficiently not to abuse them). + +My own regions' XEngine section includes the following lines to enable OSSL and allow scripts owned by myself +or my estate managers to use functions that are either used by PMAC or are likely to be used by other common +OSSL scripts you might wish to write/use (usually relating to NPC or animation): + +[XEngine] + AllowOSFunctions = true + OSFunctionThreatLevel = Low + Allow_osMessageAttachments = true + Allow_osGetGridName = ESTATE_OWNER, ESTATE_MANAGER + Allow_osGetGridNick = ESTATE_OWNER, ESTATE_MANAGER + Allow_osOwnerSaveAppearance = ESTATE_OWNER, ESTATE_MANAGER + Allow_osGetLinkPrimitiveParams = ESTATE_OWNER, ESTATE_MANAGER + Allow_osGetPrimitiveParams = ESTATE_OWNER, ESTATE_MANAGER + Allow_osMakeNotecard = ESTATE_OWNER, ESTATE_MANAGER + Allow_osNpcCreate = ESTATE_OWNER, ESTATE_MANAGER + Allow_osNpcGetPos = ESTATE_OWNER, ESTATE_MANAGER + Allow_osNpcGetRot = ESTATE_OWNER, ESTATE_MANAGER + Allow_osNpcLoadAppearance = ESTATE_OWNER, ESTATE_MANAGER + Allow_osNpcMoveTo = ESTATE_OWNER, ESTATE_MANAGER + Allow_osNpcMoveToTarget = ESTATE_OWNER, ESTATE_MANAGER + Allow_osNpcPlayAnimation = ESTATE_OWNER, ESTATE_MANAGER + Allow_osNpcRemove = ESTATE_OWNER, ESTATE_MANAGER + Allow_osNpcSaveAppearance = ESTATE_OWNER, ESTATE_MANAGER + Allow_osNpcSay = ESTATE_OWNER, ESTATE_MANAGER + Allow_osNpcSetRot = ESTATE_OWNER, ESTATE_MANAGER + Allow_osNpcShout = ESTATE_OWNER, ESTATE_MANAGER + Allow_osNpcSit = ESTATE_OWNER, ESTATE_MANAGER + Allow_osNpcStand = ESTATE_OWNER, ESTATE_MANAGER + Allow_osNpcStopAnimation = ESTATE_OWNER, ESTATE_MANAGER + Allow_osNpcTouch = ESTATE_OWNER, ESTATE_MANAGER + Allow_osNpcWhisper = ESTATE_OWNER, ESTATE_MANAGER + Allow_osSetPrimitiveParams = ESTATE_OWNER, ESTATE_MANAGER + Allow_osSetProjectionParams = ESTATE_OWNER, ESTATE_MANAGER + Allow_osSetRegionWaterHeight = ESTATE_OWNER, ESTATE_MANAGER + Allow_osSetTerrainHeight = ESTATE_OWNER, ESTATE_MANAGER + Allow_osAvatarPlayAnimation = ESTATE_OWNER, ESTATE_MANAGER + Allow_osAvatarStopAnimation = ESTATE_OWNER, ESTATE_MANAGER + Allow_osForceOtherSit = ESTATE_OWNER, ESTATE_MANAGER + Allow_osGetNotecard = ESTATE_OWNER, ESTATE_MANAGER + Allow_osGetNotecardLine = ESTATE_OWNER, ESTATE_MANAGER + Allow_osGetNumberOfNotecardLines = ESTATE_OWNER, ESTATE_MANAGER + Allow_osRegionNotice = ESTATE_OWNER, ESTATE_MANAGER + Allow_osAgentSaveAppearance = ESTATE_OWNER, ESTATE_MANAGER + +There are many other OSSL functions but the above will suffice for most people and includes the ones +necessary to allow PMAC to work correctly. After making your changes you will need to save the +opensim.ini file, then restart the simulator (restarting just the region will not suffice). + + +****************** +3. Rez the Object +****************** + +Not much to say here...just rez it to ground and position it wherever you want it to be. + + +******************************************* +4. Reset or Recompile Scripts if Necessary +******************************************* + +Exactly what will be required here depends on your main server configuration, your region + configuration, and where you obtained the PMAC object. In many cases you won't need to do +anything...simply rezzing the object will automatically cause the script to reset and initialize. +After a second or two, you'll see a message in general chat telling you that initialization is +complete and the object is ready to use. + +If this doesn't happen, you may simply be able to right-click on the object and use the radial + menu to reset the script. + +In some regions and/or grids, it is necessary to "recompile" a script -- particularly when the +object was obtained from a different grid while hypergridding -- before it will work. Although +a little tedious, if you don't know how to recompile a script here is a set of steps to take to + be absolutely sure that everything is running properly: + +1. Select the object while in edit mode in your viewer +2. Take a copy of the PMAC Core script into your own inventory +3. If the object contains any add-on scripts, take copies of those too +4. Look for an object in inventory called "~~~positioner" and take a copy of it as well +5. Wear the ~~~positioner object (on your left hand), then edit it and open the script inside it +6. Make a tiny change to this script by adding a space at the end of one of the red comment lines +(or add an empty blank line at the very end) +7. Now "Save" this. You should simply see a "Save complete" notice and no errors...this has forced +that script to recompile. +8. Now unwear the ~~~positioner +9. Delete the original ~~~positioner from the main object's inventory +10. Now place your newly recompiled ~~~positioner from your inventory into the original object +11. Delete each of the scripts that you took copies of from the original object +12. If you have any add-on scripts, put the copies of these back into the original object first. +They will automatically recompile when you do this. +13. And finally, place the copy of the PMAC Core script back into the original object and it will + recompile as well. + +You should now see the message telling you that initialization is complete and the object is ready to use. + + +*********************************************** +5. Adjust User Settings According to Preference +*********************************************** + +In most cases this is entirely optional because the object will come pre-configured to be suitable + for the average user. If you want to look at the settings or change any, open the PMAC Core script +and you will see a section at the top where you can do this -- it's divided into a "general user +section" and an "advanced/builder" section. + +I'll give detailed information for the basic ones, and assume that anyone using the advanced/builder + ones will have enough scripting knowledge to need a little less detail. + +***** General User Settings: ***** + +Name: defaultGroup +Type : string +Use: Identify the animation group to load when the PMAC object initializes. +Details: +The name you supply here is just the simple group name, not the full notecard name, and must be +enclosed in quotation marks (with a semi-colon after the last one). If your animation group's +notecard name is ".menu123A Groupname" then the value you'd enter just "Groupname" for this variable. +It will always be loaded initially, even if its permission settings wouldn't normally allow the user to do so. + +******** + +Name: resetOnQuit +Type : integer (boolean) TRUE or FALSE +Use: Indicate whether the script should reset and re-initialize the object when you stop using it. +Details: +If you set this to TRUE (no quotation marks!) then after you finish using the object and everyone +stands up, the object will completely reset itself and reload all of its default values. This is useful + for furniture items where you might want them to have a specific set of animations active whenever +someone new sits down and you don't want to have to remember to reset it back to these when you finish using it. + +If you set this to FALSE it will simply leave everything the way it's currently set and when someone + sits down again it will resume with whatever animation was most recently loaded. If it was in auto mode + when you stopped using it, it will resume auto mode when the next person sits. + + +********** + +Name: ownerUseReq +Type : integer (boolean) TRUE or FALSE +Use: Indicate whether the owner must be sitting on the object before anyone else is allowed to sit +Details: +If set to TRUE, the object will refuse to allow anyone other than the owner to sit on it. Once the owner + is seated, other people will then be allowed to sit as well. If the owner then stands, the existing users + will be allowed to remain there but no new user (other than the owner) will be allowed to sit. Normally +you'd only set this to TRUE for an object that you wish to reserve for your own private use. + +If set to FALSE, anyone can sit down and start using the object even if the owner is offline or in another region. + +********** + +Name: ownerOnlyMenus +Type : integer (boolean) TRUE or FALSE +Use: Indicate whether only the owner is allowed to access the dialog menus +Details: +If set to TRUE, only the owner can ever touch the object to access the dialogs. Anyone else who sits will +simply play whatever the current animation is and continue to do so until the owner uses the menu to change it. +If auto mode is enabled then it will change animations based on its timer setting so you could use this as a +sort of "multi-person AO" for a furniture item. + +If set to FALSE, anyone can touch the object and ask to be the controller in charge of the dialogs. The menu +options they see will depend on the permission settings of the individual group notecards and NPC notecards. +Only the owner ever sees the top line of the OPTIONS menu. + +*********** + +Name: ownerUseUnlocksPerms +Type : integer (boolean) TRUE or FALSE +Use: Indicate whether other users temporarily gain "owner" permissions for groups and NPCs if the owner seated +Details: +If set to TRUE, when the owner is one of the users currently seated on the object, all users can then access +animation groups and NPCs that are normally restricted to the owner. This is a setting you might use for a bed +where you want a different (larger) set of animations available when you're there, but want to restrict them otherwise. + +If set to FALSE, only the owner will ever be able to see or load notecards where the permission is set to +owner-only; and only group members with the group currently active will see or be able to access notecards where +the permission is set to group. + +************* + +Name: autoOn +Type : integer (boolean) TRUE or FALSE +Use: Indicate whether the PMAC Auto mode should be turned on by default +Details: +Auto mode automatically cycles through the animations in the currently-loaded animation group, advancing based + on the time set for autoTimer (see below). If you set this to TRUE, this will automatically be enabled any time + the script is reset so it would be particularly useful if you're using the ownerOnlyMenus=TRUE setting above. + +If set to FALSE, the object will be in manual mode when the script resets. + +Regardless of this setting, when you stop using the object it will remember and whatever its last setting was +and use it when someone sits down to start using it again. If you don't want that to happen you would need to +set the resetOnScript=TRUE (see above) which will cause the script to reset after you stop using it and then +pick up and use the default value again. + + +************* + +Name: autoTimer +Type : float +Use: Set a default time to use for the auto mode's timer +Details: +The value set here is a number larger than 0.0 and determines the default number of seconds to wait before + advancing to the next animation when PMAC is in auto mode. You can use the OPTIONS menu to change this +during use, too. When you stop using the object it will remember whatever value was most recently used and won't +pick up and use this default value again until the script is reset. + +************* + +Name: showGroupsMenuFirst +Type : integer (boolean) TRUE or FALSE +Use: determines which menu level to show first when initiating dialog +Details: +When FALSE, initiating the dialog will display the current group's animation selection menu (PMAC 1.01 and MLP +normal behavour). When TRUE, the groups menu will be shown instead. Default is FALSE. + +************* + +Name: allowSoloNPC +Type : integer (boolean) TRUE or FALSE +Use: Determines whether an NPC can occupy a PMAC object when no avatars are currently using it +Details: +When FALSE, PMAC will not allow a NPC to sit on a PMAC object unless an avatar user is already seated. When +there are no remaining "real" users, any remaining NPC are removed. When TRUE, NPCs are allowed to occupy the + object but it becomes your responsibility to later unseat them and remove them from the scene. DO NOT manually +reset the core script until you've removed any NPCs it is controlling or they will become stranded. NOTE: using +the dialog option "QUIT" will still remove all NPCs regardless of this setting. + + +***** Advanced/Builder Settings: ***** + +There are five variables in this section, three of which are simply convenience/preference tweaks +that change the appearance of the "positioner" handles when you are in edit mode. + +handleName is the object name in inventory for the positioning handle. If you want to use a different + handle than the one I supply by default, simply put it in inventory and enter its name for this +variable to have it be rezzed instead. + +handleColours is a list of nine LSL vector colours to use for the positioning handles in edit mode. +You must supply exactly 9 vectors and they are rezzed in the order supplied in the list for each position. + +handleSize is the vector dimension to set for each of the positioning handles. All handles will use this same size. + +handleAlpha is the (float) alpha value that will be used for all of the positioning handles. + +And finally, baseAn is the name of a priority 1 animation in inventory that should be used for synch. +I supply one in almost all animation products I use that is just a generic standing pose but if you +want to use a different one then drop it into inventory and change this variable to that animation's +name. This is a work-around method for the "fix" that was made to Opensim in 2012 that broke the old +behaviour of stopping and starting animations. While there are various ways to make synch work, in my +view this is the most reliable one for this sort of application. In normal use, you will never see it + or even be aware that it's running underneath the other animations. When a user stands it is +automatically released along with the currently playing animation. + + +After making any changes to any of the above, save and your changes will be applied. + +********************************* +6. Optional Configuration Notecard +********************************* + +Instead of manually setting configurations in the script directly, you can include a configuration + notecard in PMAC's inventory instead. Bu default it must have the name ".PMAC-CONFIG" although +you can change this in the script's user settings. Each line of the notecard can contain a parameter + name, followed by an equals sign followed by the desired value. Any that are included will override + the values set in the script directly, and any that omitted will use the script's current default + values. Commands recognized: +- defaultGroup or DefaultGroup +- resetOnQuit or ResetOnQuit +- ownerUseReq or OwnerUseReq +- ownerOnlyMenus or OwnerOnlyMenus +- ownerUseUnlocksPerms or OwnerUseUnlockPerms +- autoTimer or AutoTimerValue +- baseAn or BaseAnimation +-showGroupsMenuFirst or ShowGroupsMenuFirst +- allowSoloNPC or AllowSoloNPC + +Example: this would change the default menu to load on startup to "Cuddles", changes the behaviour to +reset the script on quit, and changes the system to allow solo NPCs. + +defaultGroup = Cuddles +ResetOnQuit = TRUE +allowSoloNPC = TRUE + +*************************** +7. Other +*************************** + +In most cases you'll now be up and ready to go and you will probably not have had to do many of the above steps. + +If you use add-ons to the PMAC system, they should supply any necessary instructions for how to set them. +Add-ons are optional and do not need to be included in an object unless your set-up specifically uses them. + If a notecard supplies commands for one but the add-on script isn't present, the command is ignored but it +will in no way impair the operation of the core script. + +Unless you are a very advanced scripter I would strongly suggest you never change anything in the script +other than these settings at the top (up to the place where it warns you not to change anything below it). + diff --git a/PMAC/PARAMOUR MULTI-ANIMATION CONTROLLER (PMAC)/Object/!!!READ ME - 3. User Instructions.txt b/PMAC/PARAMOUR MULTI-ANIMATION CONTROLLER (PMAC)/Object/!!!READ ME - 3. User Instructions.txt new file mode 100644 index 00000000..a4fce36a --- /dev/null +++ b/PMAC/PARAMOUR MULTI-ANIMATION CONTROLLER (PMAC)/Object/!!!READ ME - 3. User Instructions.txt @@ -0,0 +1,211 @@ +// :SHOW: +// :CATEGORY:NPC +// :NAME:PMAC +// :AUTHOR:Aine Caoimhe +// :KEYWORDS: +// :CREATED:2015-11-24 20:38:40 +// :EDITED:2015-11-24 19:38:40 +// :ID:1095 +// :NUM:1873 +// :REV:1 +// :WORLD:Second Life +// :DESCRIPTION: +// PARAMOUR MULTI-ANIMATION CONTROLLER (PMAC) v1.02 (OSSL) +// :CODE: + +// Provided under Creative Commons Attribution-Non-Commercial-ShareAlike 4.0 International license. +// Please be sure you read and adhere to the terms of this license: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +PARAMOUR MULTI-ANIMATION CONTROLLER (PMAC) v1.0 (OSSL) +by Aine Caoimhe January 2015 + +USER INSTRUCTIONS + + +**** OVERVIEW ***** + +PMAC is designed with the intent that it be highly intuitive and easy to use +with absolutely no instruction at all. +Hopefully this will be the case for most situations, although there is one menu +that only the owner can access -- +the Edit menu -- that does require detailed instructions for safe use. + +**** GENERAL USE -- ANIMATIONS AND GROUPS **** + +To use a PMAC object it's as simple as "sit"....that's it! + +Just sit on the object and PMAC will position you and play the current +animation. If it's in auto mode it will change +animations every so often, cycling through the ones that are available based on +the currently loaded "animation group". +If it isn't in auto mode, or if you want to select animations manually, you'll +need to touch the object after you sit +on it to get the main controller menu. The first person who touches it will +automatically become the "controller". + +Only one person can be in control of the menus and they must be seated on the +object. If someone else is seated and +touches the object, they'll be asked if they want to "take control" away from +whoever has it (unless the owner has +set it not to allow this). If you take control away from someone else you'll be +in charge and the other person will +then be along for the ride. Since nobody likes a control war, it's usually a +good idea to speak to the current +controller before simply grabbing it away from them. + + +If you've any other multi-avatar system of this type (like MLP) things will look +very familiar to you even though +it works in a rather dramatically different way at the script level. An +"animation" in PMAC's list is actually a +set of animations that are in inventory and get played together for the users +that are seated (typically a couple, +but PMAC can be set to handle up to 9 concurrent sitters). when you select an +animation, PMAC handles which animation + is played for each user and where to position them and does so based on +information stored in a notecard. + +Usually animations are supplied in "groups" of animations -- perhaps one group +for sitting, one group for lying, +another group for cuddling, and so on -- each of which has a number of +appropriate animations assigned to it. You +pick a group, then pick the animation you want to play from that group. + +When you first use PMAC it will already have a default group selected and you'll +see the list of animations for that group. PMAC shows up to 6 animation names, +and if there are more than that you can use the PREV and NEXT buttons to scroll +through additional pages of them (only the 6 animation names at the top change, +the rest of the menu stays the same). + +When you pick a new animation the region has to send that to everyone's viewer +and some can take longer to arrive than others, so animations that are intended +to "fit" together can easily get out of synch the first time you use them. Even +if they look fine in your viewer, they might be out of synch for someone else. +Pressing the "SYNCH" button tells all of the viewers to restart the current +animation from the beginning and should fix that for everyone. + +The QUIT button does *almost* what you think it does. QUIT makes *everyone* +stand up and stop using the object, not just you. If you want to stop using it +but want to leave everyone else seated, simply "stand" using your viewer. Once +you do so, PMAC will ignore any button you click on the still-open dialog box so +you can either close it or just click any button. The next person who is still +seated and touches the object will take control (automatically). + +If you want to change to a different group of animations, press the GROUPS +button to get a list of the groups you can access. Just like the animation menu, +you may need to scroll through pages of them using the PREV and NEXT buttons to +find the one you want. When you pick one, you'll be taken back to the animation +menu with a list of the animations in the group you selected. + +If you accidentally close the dialog menu, just touch the object to have it +re-displayed. + + +**** THE OPTIONS MENU **** + +You can enter the options menu by clicking the OPTIONS button on either +animation or groups menu pages and what you see will depend a little on who you +are and what is currently happening (and how the item has been configured). The +buttons on the very top row (EDIT ON and MENUS LOCK/UNLOCK) are only ever +visible to the owner. The others can be available to any user. Here's a quick +look at each: + +QUIT does exactly the same thing as it does in all menus -- everyone is forced +to stand. + +SYNCH also does exactly the same thing. + +BACK will usually take you back to the animations page unless you're in auto +mode in which case it will take you to the groups menu instead. + +SWAP let's you move to another position unless the currently playing animtion is +only set up for a single user. If the animation is set up for two users you'll +immediately swap places (even if there's nobody currently occupying the other +spot). If the animation is for any more than two users you'll be asked what +position you want to move to and who (if anyone) is currently sitting there. You +can't directly make two other people swap places but you could either let one of +them take control for a moment, do the swap, then take control back; or you can +swap with one, then the other, then again with the first person again and you'll +achieve the same thing (it's a little inconvenient, granted, but much simpler on +the "behind the scenes" script level to do it that way). + +UNSIT allows you to force someone else to stand. Obviously that might not be a +very polite thing to do but there are a couple cases where it's useful: +- if the other person has gone AFK for a long time and you simply don't want +them standing there +- if one of the other users is an NPC, when you make the NPC stand it will be +removed instead, freeing up the spot for someone else to occupy +- if you want to load an animation group that is for a smaller number of users +than are currently seated you can make someone stand to free up the spot and +allow you to load the new group (be nice about it though...) +If there are only 2 users (yourself and one other) clicking UNSIT will +immediately unsit the other user. If there are more, you'll be asked which user +you want to force to stand. + +ADD NPC allows you to have a vacant position filled by an NPC. The button won't +be available if there aren't any empty positions. When you press it you'll be +given a list of the NPCs you can load (it's a multi-page list if there are many +possible options). Select one and wait a moment for the NPC to rez and join you. +To later remove the NPC you can either UNSIT them, or QUIT (which removes all +NPCs) or have all "real" users stand -- if there are no avatars still seated all +remaining NPCs are removed and the script treats it like a QUIT. + +[Slight technical note: +After adding an NPC to the last available vacant position the "ADD NPC" button +will often still be shown until you leave the options menu and then return to +it, even though there isn't an available position for the NPC to occupy. If you +try to add another one you'll be informed that there's no room and the button +will update correctly too. This is due to a slight delay in the time it takes an +NPC to sit down and be "registered" as a user which is almost always longer than +the time it takes to redisplay the options menu. While the script could be +changed to "fix" this, it would also mean the menu wouldn't reappear for up to +an additional few seconds (and it is already delayed a little by the need to +wait for the region to register the NPC) which becomes considerably more +annoying than the "phantom" button so I chose to leave this minor "sort-of-bug" +instead.] + +AUTO takes you to a menu where you can start or stop PMAC's auto mode. AUTO OFF +stops it. AUTO ON turns it on using whatever the last selected (or default) time +is. Selecting one of the time buttons will turn on auto mode if it isn't +already, and use that selected time. + +SPECIALS is a button that is only visible if you are using an PMAC add-on that +needs to offer you a menu button. If you have one that does, you'll find that +button in the list of buttons displayed in the SPECIALS menu. If you have a lot +of add-ons the buttons are sorted alphabetically by name and there could be +multple pages of them. You'll need to consult your add-on's instructions to find +out what their buttons do and sometimes this will temporarily transfer you to a +separate menu system controlled directly by the add-on. Once finished, you +should be automatically returned to the PMAC menu. + +For the curious, there is one additional empty button beside SPECIALS that will +always show "-" and be ignored. That's in case there's a need for another button +to do something in a future version of PMAC. + + +**** OWNER-ONLY OPTIONS **** + +The very top row of the options menu is only ever displayed for the owner and +also contains an empty "-" button that doesn't do anything and is for possible +future use. The other two buttons are: + +MENUS LOCK/UNLOCK. toggles between allowing other users being allowed to take +control and access the menus. If you LOCK menus then only you can ever take +control and use them. If you UNLOCK them, anyone who is seated can take control. +Be a little cautious with locking menus since PMAC remembers this setting even +after you quit. If the user settings don't cause the script to reset on quit, +you could accidentally leave everyone locked out of being able to use the +controls until you return and unlock if for them. Of course being able to +*intentionally* do this is the whole point of having the button there in the +first place. Just use with care. :) + +EDIT ON is probably the most important button on the entire menu for an owner, +but also requires considerably more detailed instructions which aren't suitable +for this basic user instructions notecard. Briefly, though, this switches you +into edit mode, allowing you to then adjust the positions of each animation and +either temporarily or permanently store those changes without ever touching the +notecards or doing any copy-pasting. You cannot enter edit mode unless all +available positions are occupied -- by avatars or NPCs -- and once in edit mode, +you will want to ensure that nobody stands until you've finished and stored your +changes. For all the sordid details, see the Basic Building instructions +notecard. diff --git a/PMAC/PARAMOUR MULTI-ANIMATION CONTROLLER (PMAC)/Object/!!!READ ME - 4. Basic Tweaking Instructions.txt b/PMAC/PARAMOUR MULTI-ANIMATION CONTROLLER (PMAC)/Object/!!!READ ME - 4. Basic Tweaking Instructions.txt new file mode 100644 index 00000000..5e03060d --- /dev/null +++ b/PMAC/PARAMOUR MULTI-ANIMATION CONTROLLER (PMAC)/Object/!!!READ ME - 4. Basic Tweaking Instructions.txt @@ -0,0 +1,210 @@ +// :SHOW: +// :CATEGORY:NPC +// :NAME:PMAC +// :AUTHOR:Aine Caoimhe +// :KEYWORDS: +// :CREATED:2015-11-24 20:38:40 +// :EDITED:2015-11-24 19:38:40 +// :ID:1095 +// :NUM:1874 +// :REV:1 +// :WORLD:Second Life +// :DESCRIPTION: +// PARAMOUR MULTI-ANIMATION CONTROLLER (PMAC) v1.2 (OSSL) +// :CODE: + +// Provided under Creative Commons Attribution-Non-Commercial-ShareAlike 4.0 International license. +// Please be sure you read and adhere to the terms of this license: https://creativecommons.org/licenses/by-nc-sa/4.0/ +by Aine Caoimhe January 2015 + +BASIC TWEAKING INSTRUCTIONS + + +**** OVERVIEW ***** + +Under the hood, PMAC is an extremely complex scripted system that allows advanced scripters to unleash the full potential of a highly flexible command system. The vast majority of owners will neither have the skills nor inclination to do this, though, so PMAC also include a set of integrated "basic" tweaking tools that are easy for almost anyone to use. + +As an owner, and using these basic tools, you can tweak or customize positions for each of the animations in your PMAC object, either temporarily or permanently. With very little additional effort you can add new NPCs and -- if you're even more adventurous -- you can even set up whole new animation groups. It's mostly a case of your comfort level, patience and willingness to devote time to doing it. + +If you haven't run away in terror yet, read on... + + +**** VERY BASIC TWEAKING **** + +Almost any owner will want to do some basic tweaking of positions at some point, and PMAC is designed to make this extremely easy. You'll make these adjustments using PMAC's built-in edit mode which you can access from the top row of the options menu by clicking the EDIT ON button. + +You can't enter edit mode until you pick an animation group and then select an animation. If you later pick a different animation group but haven't yet selected an animtion from it, you won't be able to enter edit mode yet (there are technical reasons for this that I won't bore you with). Also, all available positions for that group have to be occupied, so unless the group is for only one person you'll need a friend or two...or just ask some extremely patient NPCs to join you instead. Once all positions are filled you'll be able to switch to edit mode and you'll see a new menu: the EDIT menu. + +Once you do, you'll see PMAC rez a "positioning handle" at the location of each user. The handles are labelled and colour-coded to help easily distinguish them from one another (there are no "boy" or "girl" handles so it doesn't matter who is occupying a given position as long is it's set up the way you want it). Switch to edit in your viewer and you can then grab the handles and move them around just like any prim. + +As soon as you let go of your mouse button, the avatar associated with that handle will move to this new location. If you rotate a handle, the avatar will also rotate to match this rotation as soon as you release your mouse. It takes a little bit of getting used to the "move after release" approach to positioning but this is common (and necessary) for a no-poseball system -- it's because until you release the mouse button the region doesn't know you've moved the handle so it can't move the avatar to match it. + +If you need to re-synch your users, the edit menu's SYNCH button does exactly the same thing it does in all other menus. + +WARNING!!! The PREV and NEXT buttons don't!!! + +Before going any further, it's important to understand a tiny bit about how PMAC handles animation data. When you select an animation group the data for all animations in that group is loaded into the script's active memory. While you're editing positions, this doesn't change that stored script memory until you intentionally ask it to. Once you do, the existing data in memory is overwritten with the new positions and rotations and the old values can't be retrieved without leaving edit mode, then reloading the old values into memory from the notecard (you will need to switch to a different group first, then switch back again). However none of these stored changes in memory affect the permanent data stored in the animation group's notecard until you specifically tell PMAC to do so. + +While you're using the handles to position your users for the current animation, you can press the REVERT THIS button to have them moved back to the currently stored position and rotation for each. If you press STORE THIS it will update the script's memory with their current positions and rotations. Once you've done this, the REVERT THIS button would revert to that newly stored position, not your previous one still stored on the notecard. + +If you leave edit mode for any reason (using EDIT OFF or if someone stands) it also stores the current position and rotation for the animation to script memory before removing the handles so if you don't want this updated you'll need to REVERT THIS first. More on that in a moment. + +While in edit mode, the NEXT and PREV buttons will first store the current position and rotation + for the current animation to script memory, then change to either the next or previous animation + in the group. If you don't want the current one to change, be sure to REVERT before you use either button. + +Further complicating matters, a few PMAC add-ons may need to store special information as well and + the exact method they use could vary a little from one to another. If you're using add-ons you'll + need to double-check what their instructions require. For more complex ones there could be some +data that needs to be cut and pasted from chat and later inserted into a notecard (there's no way + to avoid this, unfortunately). A few could need you to press the "STORE ADDON" button before you +change to a different animation but in most cases you won't need to use this button (the button is + provided as a "just in case" thing for add-on scripters). + +None of these changes are permanent stored yet. If you leave edit mode PMAC will remember them only + while the current animation group remains loaded. As soon as you select a different animation group +-- even if you never pick an animation from it -- those changes are lost. If you want to keep them + for use later, you will need to tell PMAC to do so before you leave edit mode (or you can go back +into edit mode as long as you haven't yet changed animation group). You have two options: + +SAVE CARD will first update the current animation's position and save it to script memory. Then, after + a short delay, it will *overwrite" your current animation group's notecard with the new data. After + that, the new data will always load from that card but of course any old data will be lost. + +SAVE NEW will do almost the same thing, except instead of overwriting your existing notecard it will +save the data to a copy of the card with a number appended to the end of the name. In effect this +creates a new animation group so you'll see both your old group and your new one available in the +list of animation groups you can load. When you "save new" PMAC also switches to this new group +in memory so any additional edits you make will be applied to this copy, not your original group. +If you "save card" again, it will overwrite the data in your card copy, not the original. If you +"save new" again, it would create a third group with another number appended to it. + + +This allows you to keep one copy of a card for general use, and then a customized version for use with + a specific partner, and load whichever one is appropriate for the moment. + +Once you're done, use EDIT OFF to remove the positioning handles (they're actually rezzed and destroyed +as needed, not just hidden/unhidden). If for some reason you reset the PMAC script without having +first removed the positioning handles the script will no longer be able to remove them. You can +either delete them manually, or if they're still present when the region next restarts they'll be + automatically deleted by their own tiny script(that's all their internal script is used for... + +NOTE: Any PMAC add-ons that also need to store permanent object position changes will likely need you to + subsequently update your saved notecard with data they supply. Please consult their instructions +for specific details. + + +**** ABOUT ANIMATION GROUP NOTECARDS **** + +Other than possibly needing to edit a notecard's contents manually to add necessary data for add-ons, it + should never be necessary to edit a PMAC animation group notecard manually unless you are making +a major change such as adding a new animation to it or deleting an existing animation from it. +Details on that are in the Advanced Builders Instructions notecard. + +If you're doing custom set-ups, though, you will likely want to tweak the notecard *names* though so it's + important to know how an animation group notecard's name must be formatted. An animation group +notecard name is as follows: + + .menu[ss][p][r] [unique button name] + +Where: +- it must always begin with .menu (note the period at the start) +- then two numbers or letter (or a mix) that are used only for sorting...groups appear in the + same order in your menu as they do in inventory +- then one number between 1 and 9 (inclusive) that indicates how many positions are used by +*all* animations in this group +- then either the letter A, G, or O +.....A means anyone is allowed to have it in their groups list and load it +.....G means only people with the same *active* group as the PMAC object can see and load it +.....O means only the owner can see and load it +.....the owner can also always see and load group ones, even if the group isn't active +- then there must be a single space +- then everything after that is the name that will be used for the group and the group's button +.....a group name must be unique from any other group name in inventory, even if the .manusspr part is diffent +.....a group name can be no more than 25 characters long but it's best to keep it as short as possible to fit on a button + +An example of a correctly formatted name would be: .menu012A Sofa Sits +- it will be "early" in the group menu list by virtue of the 01 sort number +- the 2 indicates that all animations in it are for 2 positions +- the A indicates that anyone can see and load it +- the group name you'll see on the button is "Sofa Sits" and no other notecard is allowed to +have that exact name, even if it was called .menu123G Sofa Sits + +Names are case-sensitive, though, so you could have one another one called "Sofa sits" and yet + another one called "sofa sits" and still another called "SOFA SITS" and they would all be treated as different names from one another (but you might later not be able to remember which one is which so it's a good idea not to). + + +**** ABOUT NPC NOTECARDS **** + +You can easily add new NPCs to your PMAC object at any time (or delete ones you don't use). +Simply use any utility you like to create an appearance notecard, then place it into the +PMAC object's inventory and rename it to use the correct NPC naming format which is very +similar to the animation group format + + .NPC[ss][r] [Firstname] [Lastname] + +Where: +- it must begin with .NPC (note the period at the start) +- then two numbers or letters used for sorting +- then either the letter A, G or O which indicate the same viewing and loading permission restrictions as they +do for animation groups +- then a space +- then the first name to use for the NPC -- it *must* be a single word with no space +- then a space +- then a the last name to use for the NPC -- again it must be a single word with no space +....if you don't supply a last name a tilde symbol (~) will be added instead so the NPC would be named Firstname ~ +- the total of length of Firstname plus Lastname cannot exceed 24 characters (which becomes 25 character because of +the space between them) +.....and it's a good idea to try to keep the combined length shorter if at all possible since in most cases only the + first name and a bit of the last name will fit on a button + +Again, the name of the NPC must be unique from any other NPC even if the first part is different. + +An example of a correctly formatted NPC name would be: .NPC88A Cutie Pie +- it will appear fairly late in NPC list due to the 88 for its sort order +- the "A" means anyone can see and load it +- The NPC's name on the button (and when rezzed) will be "Cutie Pie" + +Your newly added NPC won't appear in the list until the PMAC Core script is reset. Similarly, if you delete a NPC +notecard you will need to reset the PMAC core script to have it removed from the list of available NPCs. + + +**** MORE DRASTIC EDITS OF ANIMATION GROUP NOTECARDS **** + +If you wish to, you can delete an entire animation from a notecard by opening it, finding the appropriate line for +the animation, then deleting the entire line. Be very careful when doing this, ensuring that you delete all of it +and without leaving a blank line or even a single extra space, either of which can cause a +failure to subsequently load the card and extract its data. It's safest to work with a *copy* +of your notecard, make the changes, then test to ensure that copy loads correctly. Only then +should you consider deleting your backup copy. + +Similarly, you can add a new animation to the card by supplying all of the necessary information. Unless you already + know the correct *relative* positions and rotations, it's best to supply generic starting points and then use PMAC's +tweaking tool to position and store the optimized final positions. + +A full line contains a pile-separated (|) list of the following (in order and with NO ADDITIONAL SPACES): +- animation button name (must be unique from any other animation name used in the same notecard and cannot contain +the pipe (|) symbol) +- any commands (see the Advanced Building Information notecard for details) or NO_COM for none +- for each position you then need to supply +.....the exact name of the animation in inventory +.....followed by a relative position (use something like <0.0,0.0,0.0> or slightly offset from that if you don't know it) +.....followed by a relative rotation (use something like <0.0, 0.0, 0.0, 0.0> if you don't know it) + +IMPORTANT: YOU MUST USE NUMERICAL VALUES FOR THE VECTORS AND QUATERNIONS, YOU CANNOT USE THE LSL CONSTANT NAMES + +JUST AS IMPORTANT: All animations in a single animation group notecard must use the exact same number of positions. + You can't supply a mixture. + +Here is an example for an animation line in a 2-position animation group notecard assuming that the names for the +animations we place in the inventory are "sofa_cuddle_m" and "sofa_cuddle_f" which we want to call "Sofa Cuddle" +and that we're adding no special add-on commands. Because we don't know a suitable relative offset +we'll displace each by a small amount on the local y-axis and then later load the group, enter edit + mode, and position them correctly relative to one another and the main PMAC object: + +Sofa Cuddle|NO_COM|sofa_cuddle_m|<0.0,0.1,0.0>|<0.0,0.0,0.0,0.0>|sofa_cuddle_f|<0.0,-0.1,0.0>|<0.0,0.0,0.0,0.0> + + +For more detailed, technical instructions geared for advanced scripters and builders, please see the final notecard + in this series. + diff --git a/PMAC/PARAMOUR MULTI-ANIMATION CONTROLLER (PMAC)/Object/!!!READ ME - 5. Advanced Builders Information.txt b/PMAC/PARAMOUR MULTI-ANIMATION CONTROLLER (PMAC)/Object/!!!READ ME - 5. Advanced Builders Information.txt new file mode 100644 index 00000000..fa5449d7 --- /dev/null +++ b/PMAC/PARAMOUR MULTI-ANIMATION CONTROLLER (PMAC)/Object/!!!READ ME - 5. Advanced Builders Information.txt @@ -0,0 +1,303 @@ +// :SHOW: +// :CATEGORY:NPC +// :NAME:PMAC +// :AUTHOR:Aine Caoimhe +// :KEYWORDS: +// :CREATED:2015-11-24 20:38:40 +// :EDITED:2015-11-24 19:38:40 +// :ID:1095 +// :NUM:1875 +// :REV:1 +// :WORLD:OpenSim +// :DESCRIPTION: +// PARAMOUR MULTI-ANIMATION CONTROLLER (PMAC) v1.02 (OSSL) +// :CODE: + +PARAMOUR MULTI-ANIMATION CONTROLLER (PMAC) v1.0 (OSSL) +by Aine Caoimhe January 2015 + +ADVANCED BUILDERS INFORMATION + + +**** OVERVIEW ***** + +This final notecard is more in the form of information rather than instructions and is intended as a resource for those with reasonably strong scripting skills. Before reading, you will probably wish to familiarize yourself with the contents of the Basic Tweaking Instructions notecard (particularly the final sections which talk about the notecard naming formats and the animation group card's animation line format) and take at least a cursory look through the PMAC core script. + +This card is primarily for people who wish to either + +(1) build an object using PMAC as its animation handler (requires only modest scripting knowledge), or + +(2) script add-on utilities or other specialized customizations for the PMAC system (requires a higher level of scripting skill). + +It is assumed that all of the terms and discussion points will be familiar to the reader or else fairly easily grasped. + + +**** BUILDING/CREATING A PMAC OBJECT **** + +Building a new system is fairly simple, although quite time-consuming. The basic steps are: + +1. Create the physical object (prim, sculpt, mesh, or linket combination thereof) +2. Add the 5 core READ ME notecards that should be included with all systems +3. Add the ~~~positioner object +4. Add the base priority 1 animation +5. Add any add-on scripts you intend to include in the setup as well as their supporting user documentation +6. Add one or more NPC notecards +7. Add one or more animation group notecards (see below for how to prepare these) +8. And then finally add the PMAC core script +9. Sit, load a group card, add some NPCs (or friends) to fill up the available positions +10. Enter edit mode, adjust the position of each as required, and save +11. Update any add-on command data that changes based on positions (I expect that many won't require it) +12. Repeat for each group card until they're all done and you have a finished product + +While the final few steps are time-consuming, the built-in editor should greatly facilitate this process so the only really "hard" step here is #7 -- preparing the initial animation group notecards. I can offer a couple of suggestions to help with this: + +- organize your animations -- if they're all in separate folders that correspond to (roughly) the groups you'd normally want to include in an object, it's a lot easier to build cards later +- take the time to rename most of your animations so matching sets have identical main names and then one or two (or more if absolutely necessary) standardized prefix or suffixes (like having matching pairs use the same name and then end them with _1 and _2 or _m and _f or something like that). +- doing the above lets you drop them into a box and then write a script to quickly build your blank notecard automatically +- once you have a card like that, save a copy of it in the same folder as the animations so you can simply reuse it in future objects...PMAC's design lends itself to this approach because each group has its own notecard. You might have 50 different group folders in your own inventory so simply decide which ones you want to include and drop them in +- don't worry about positioning when doing this unless you can easily set their positions relative to one another...positioning is very easy to do "live" in those final steps using PMAC's editor and ability to directly save to card +- depending on what add-ons you want to include, you can probably automate the generation of that data -- or at least starting points -- as well +- I will often write a script to read the PMAC notecard, parse it, automatically insert or manipulate the data as needed, then resave it +- on occasion it's easier to copy-paste the data to csv and manipulate it in something like Excel...just be careful not to introduce formating or spaces that will make the card unloadable after you bring it back in-world +- if you're familiar with other programming languages, you may find it just as easy to copy the notecard data to a csv or txt file and then manipulate it that way +- you will need to consult your add-on documentation for the internal structure they require for their commands + + +**** GENERAL NOTES ABOUT PMAC **** + +Before I go into the command and add-on system I want to indicate the primary design goals I had when creating the PMAC system. I wanted PMAC to adhere to the following: + +(1) PMAC should have an extremely low footprint on sim resources, bordering on non-existent when not in use. + +The script has zero active elements until a user sits, and as soon as the last user stands once more it becomes dormant again. During use, it has a single open handler (dialog menu) and nothing else. When auto mode is engaged, it uses a llSensorRepeat() call with parameters that cannot return a successful result, and the no_sensor event acts as the "timer" handler. The timer itself is only used when in edit mode with a 0.2 sec repeat to trigger repositioning of the avatars to "chase" the handles. All avatar movement uses llSetLinkPrimitiveParamsFast() and no functions with built-in sleeps are ever called. + +The only two occasions where a thread-locking function is ever employed by PMAC are two calls to llSleep: once when an avatar first sits (0.2 sec) in order to give enough time for the default LSL sit to engage; and then when doing a card save that "overwrites" a notecard that is already in inventory because it is necessary to give enough time (0.25 sec) for the old card to be deleted from inventory before storing its replacement with the same name. + +In edit mode, there is no communication at all between the handles and the core script. Only upon leaving edit mode is there a single osMessageObject to each which raises a dataserver event to instruct it to llDie(). + +Unlike LSL-based systems, HMAC leverages OSSL notecard functions to drastically reduce the memory requirements during use as well. Rather than having to parse large menu systems and hold huge amounts of animation data in memory, PMAC relegates this to on-the-fly card-reading and no-error-check parsing. Only a single group is ever loaded into memory at any one time, and the core script simply maintains a list of possible groups it can load. By keeping the total number of animations per group to a sane number (I'd suggest 32 or less since 6 pages of animations in a group is more than most users would want to scroll through anyway) this memory consumption can be kept extremely low. + +All communication with add-ons is done using llMessageLinked() and all add-ons must be in the same root prim as the core script, keeping that additional traffic to a minimum. + +(2) PMAC core should offer all key features of other such systems, including the ability to handle a very large number of simultaneous users (up to 9), yet be confined to a single script. + +This necessitated some rather painful decisions about what could and couldn't be made part of core. In the end, the two features I simply didn't have room for but would have liked to include (because both have some "fans") are triggering expressions for each avatar on a per-animation basis, and the ability to rez additional objects from inventory. Extended conversations with regular users led me to believe that both could be omitted from core provided a mechanism existed to allow adding back via add-ons. PMAC's integrated command system not only allows this, but is also considerably more powerful in conjunction with a creative scripter (see "Command System" below). + +In the end, PMAC's entire core feature set runs on only ~1100 LOC and for many users no add-ons will be needed at all. + +(3) PMAC for should maximize the speed of use, both in the initialization time and then also during regular use, and be as intuitive as possible for the average user. + +An animation system like this shouldn't be something the user has to think about or wait for. The user wants to enjoy the product, not mess around with the script. users don't want to spend their time and energy focussing on button-clicking, they want to interact with their fellow users. Ideally nothing should ever be more than 3 clicks away and for the most part PMAC achieves this. + +As was mentioned above, PMAC holds very little in memory and does a lot of its work on the fly using OSSL's capability to rapidly read an entire notecard in a single pull. As a result, initialization of the core system typically takes under 2 seconds even for a very extensive system, and a more general-use object is often ready in less than 1 second from reset. The only operations required are reading the animation group and NPC notecard names from memory and storing them to their respective lists. + +Whenever a user selects a group, PMAC loads it from inventory in a single osGetNotecard(0 string pull, then parses it with a single parse command (and no error checking!). Even for a large notecard this takes a fraction of a second after which the dialog is ready to be displayed. + +The only time the user ever has to wait for a dialog is immediately after rezzing a new NPC because the script pauses until the region is able to supply the new NPC's UUID -- typically 0.25-0.5 seconds though this can take a little longer depending on the simulator and the NPC's attachments. + +(4) PMAC should impose no limits that aren't absolutely necessary. + +PMAC's limits are very few: +- The number of animations in a single card is almost unlimited (though in practice I would keep it to 32 or fewer) +- The number of animation group notecards is almost unlimited (though a user isn't going to bother scrolling through too many so I'd keep this to under 32 too) +- The number of different NPC notecards is almost unlimited (though again, a user is unlikely to want to scroll through page after page of them) +- The number of simultaneous users is set to 9 for three reasons: (1) almost nobody is going to have any need to set up a system even for this number...in SL even 4-person systems are extremely few and far between; and (2) it keeps the notecard line sizes and in-script memory requirements to reasonable levels; and (3) keeping the number a single digit means it only needs one digit in the notecard name. +- The number of add-ons that could be active is not limited (though having too many will begin to defeat the design goal of minimizing the system's sim footprint. +- The core script must be in the root prim -- this is to reduce the total number of calculations needed when doing positioning -- particularly in edit mode. The math to do it from a child prim is simple, but adds to the memory footprint and increases the necessary calculations by about 20% for no good reason. Almost all systems would have placed the script and animations in the root anyway. + +Beyond these design goals there are a couple other things worth mentioning: + +The Options menu currently (v1.0) contains two unused buttons -- one on the top owner-only line and the other on the line below it -- which I've left for possible future development. If you have a specific use for them you can script them in a modified version of core but those additions will need to be small to fit within the total size limit of a script. Just be aware that in a future version I might use one (or bothy) of those buttons for something else...unless your addition is one that you can convince me ought to find a permanent home there and become part of core. I'm certainly open to suggestions as long as they're "general use" rather than highly case-specific ones. + +My hope for PMAC was to create a new system that would be highly appealing to users by achieving all of the above design goals, and would interest serious builders due to its flexibility. I released it as a no-charge CC licensed product to help spur its adoption as well as to be another of my contributions to the community at large. I urge add-on writers and builders to give serious consideration to follow suit. + + +**** COMMANDS FOR BUILDERS **** + +This section is for people using add-ons made by other people (but people interested in scripting their own add-ons need to be familiar with this as well). + +As I indicated in the basic tweaking notecard, each animation is defined on a line in a group notecard as follows: + name|COMMANDS|POS1_name|POS1_rel_pos|POS1_rel_rot|... +The "COMMMANDS" part of this is the heart of the command and add-on system and is essentially a concatenated list of commands particular to any add-ons you're using assembled into what I call a "command block". + +If you don't want to use any add-ons for an animation, use "NO_COM" for the command block (there must be *something* suppled). Otherwise, assemble the command block based on the requirements of each add-on you want the animation to incorporate. Command block syntax has a required structure: + COMMANDS=COMMAND_NAME{args}COMMAND_NAME{args}.... +Each of your add-ons will have documentation to indicate the correct COMMAND_NAME to use and provide instructions as to what arguments it takes. It doesn't matter what order you place them in...each add-on will look for the ones it understands and ignore ones it doesn't. + +DO NOT insert any extra spaces before or after command names or curly braces! That can break things. + + +**** COMMANDS FOR ADD-ON WRITERS **** + +Any time a new animation is called PMAC sends the commands associated with it using: + llMessageLinked(LINK_THIS,0,"GLOBAL_NEXT_AN|"+llList2String(currentAn,2),llDumpList2String(positions,"|")); (llList2String(currentAn,2) is the entire command block) + +Your add-on will use the link_message event as the trigger and then parse the string part of the message to find out which commands (if any) are being called. Keep in mind that you may still need to do something when a new animation is called even if it doesn't contain your add-on's specific command -- you might need to stop something your add-on is doing for the previous animation. + +With only a two exceptions the key part of every link_message is the pipe-separated list of the UUID of each position's occupant, in order. If a position is empty it will contain NULL_KEY (the 32-digit key constant value, not the text string). Otherwise it will contain the UUID of the avatar (or NPC) seated there. The exceptions are when the PMAC Core script is reset or when the last user stands and it is no longer in use at which time it sends a single NULL_KEY value (the constant, not the string) for this field. + +Rules for command names and arguments: +- the pipe symbol "|" is reserved as the field separator for animation data and all of PMAC's link message communications so it *cannot* be part of a command name or argument syntax +- the two curly braces symbols "{" and "}" are the command block separators so they are also reserved +- a COMMAND_NAME cannot be one of the reserved global commands (see below) +That's it...no other rules. You can structure the syntax and naming of your commands and arguments any way you want; however be at least a little careful when laying out your command name syntax since other add-ons will hear it as well. It needs to be unique enough for your add-on to know that it needs to do something without also tricking a different add-on into acting on it as well (unless you write multiple add-ons that should both respond to the same message). + +I would strongly suggest not beginning your command name with GLOBAL or MAIN. To keep the likelihood of incompatibility with other add-ons to a minimum I would suggest that your add-on prefix each command name with a unique identifier (like MY_ADDON_NAME_xxxxx). Any that I write will begin with PAO_ (for Paramour Add-On). + + +***** LINK MESSAGE STRUCTURE AND LIST OF GLOBAL COMMANDS ***** + +When developing an add-on, please keep the above-stated design goals in mind. Ideally your add-on shouldn't drastically impact the performance of the sim, the core script, or impede the user's enjoyment of *using* the system. PMAC does all dialog communication with the user via a listener on channel=0x80000000|(integer)("0x"+(string)llGetKey()) so DO NOT EVER have your add-on send anything on that channel (though you can eaves-drop on it if you need to). + +PMAC does all of its communication with add-ons using llMessageLinked(LINK_THIS,0,str,id) so all add-ons must be in the same prim as the PMAC Core script (and PMAC Core *must* be in the root prim of a linkset). + +PMAC Core ignores any message where the (integer) num!=-1 (allowing it to rapidly ignore any messages not specifically intended for it) + +For the most part this is a one-way communication -- PMAC Core only listens to three inbound link_message inputs -- and the only fields that are used to convey information are the (string) str and (key) id fields. In your add-on the triggers will (presumably) always be link_message event: + link_message(integer sender_num ,integer num, string str, key id) +Where: +- because PMAC must be in root, sender_num will usually be 1 (although it's possible it could have the value 0) +- PMAC always sends num = 0 +- PMAC only pays attention to messages where num = -1 +- str is the primary part of the message that you'll need to examine and parse +- as stated above, id will almost always be a pipe-separated list of the UUID of each position occupant. +- most add-ons will want to parse the str field into a list using parsed=llParseStringToList(str,["|"],[]) +- if you do this, parsed(0) will be the initial string that indicates what type of message this is and any addition data will be in subsequent fields +- similarly, parsing the id field will give you a list of the UUIDs of the currently seated avatars by using positionOccupants=llParseStringToList(id,["|"],[]) + +PMAC sends other "global" notifier messages at various times that all add-ons are likely to an interest in listening to. It's very important to keep in mind Opensim has asynchronous handling so your add-on should anticipate the likelihood of receiving messages in the incorrect order. + +If your add-on needs to communicate with other scripts, be sure to make the messages unique enough that other add-on won't accidentally respond to them. I suggest using a num value greater than 0 for these as well so scripts can ignore anything where num!=0. + +A full list of global commands that PMAC sends using link messages is as follows: + +>>> GLOBAL COMMANDS + +At the very end of state_entry when all other initialization steps are complete: + llMessageLinked(LINK_THIS,0,"GLOBAL_SYSTEM_RESET",NULL_KEY); + >> this is one of only two times PMAC sends NULL_KEY in the final message field + >> keep in mind that a single NULL_KEY for that field can also be sent when the current animation group is for a single user and there is nobody seated + +When there are no more users and PMAC switches back to its dormant (READY) state: + llMessageLinked(LINK_THIS,0,"GLOBAL_SYSTEM_GOING_DORMANT",NULL_KEY); + >> this is the only other time NULL_KEY is the final field + +When the system has been dormant (no users) and the first user sits down: + llMessageLinked(LINK_THIS,0,"GLOBAL_START_USING",llDumpList2String(positions,"|")); + >> this will be followed immediately with a new animation being played + >> due to asynchronous handling in Opensim, the new animation message *could* be received by an add-on before this message, although this should be fairly rare + +When a new animation is called: + llMessageLinked(LINK_THIS,0,"GLOBAL_NEXT_AN|"+llList2String(currentAn,2),llDumpList2String(positions,"|")); + >> where llList2String(currentAn,2) is the entire command block for that animation + >> to quickly split this into a list of its individual commands use list commands=llParseString2List(commandBlock,["{","}"],[]); + >> keep in mind that a newly called animation might not include a command for your addon but you might still need to act on it (stop whatever your add-on was doing for the previous animation) + >> in some cases a new animation could be re-calling the animation that was already playing (most commonly this happens in edit mode) + +When the system is already active and a new user sits down: + >> no message is sent because this will immediately also trigger playAnimation() of the animation that was already playing + >> if an add-on needs to know who sat it will need to maintain its own ongoing list of users and compare that to the key field data + +When someone stands + llMessageLinked(LINK_THIS,0,"GLOBAL_USER_STOOD|"+(string)i+"|"+(string)who,llDumpList2String(positions,"|")); + >> "i" is the position number they previously occupied + >> "who" is the UUID of the user who stood + >> this would also be sent when a user disconnects or teleports (in or out of region) + >> it is also be sent when a NPC is removed + >> this message is sent because an add-on may need to know the UUID of whoever stood + +When the user presses the SYNCH button on any menu: + llMessageLinked(LINK_THIS,0,"GLOBAL_ANIMATION_SYNCH_CALLED",llDumpList2String(positions,"|")); + >> the commands are not resent so an add-on may need to store the most recently-sent command + +When a new user takes control of the PMAC dialog menu system: + llMessageLinked(LINK_THIS,0,"GLOBAL_NEW_USER_ASSUMED_CONTROL|"+(string)user,llDumpList2String(positions,"|")); + >> where "user" is the UUID of the new person in control + >> keep in mind that PMAC still works perfectly even if nobody has yet touched it to take control + +When the owner first enters edit mode (by pressing EDIT ON in the options menu): + llMessageLinked(LINK_THIS,0,"GLOBAL_NOTICE_ENTERING_EDIT_MODE",llDumpList2String(positions,"|")); + +When in edit mode any time new position data is persisted to script memory: + llMessageLinked(LINK_THIS,0,"GLOBAL_EDIT_PERSIST_CHANGES",llDumpList2String(positions,"|")); + >> most of the time this will be followed immediately with a new call to playAnimation() + >> it could also be immediately followed by a new call to store a notecard + +When in edit mode and the "STORE ADDON" button is pressed: + llMessageLinked(LINK_THIS,0,"GLOBAL_STORE_ADDON_NOTICE",llDumpList2String(positions,"|")); + >> that is the only action taken when that button is pressed...there is no synch or persist in PMAC Core itself + +When in edit mode and a notecard is being stored (either overwriting an existing notecard or creating a new one) this is sent immediately *after* osMakeNotecard() is executed (so the newly stored data will be in the notecard and an add-on could potentially now make additional changes to it): + llMessageLinked(LINK_THIS,0,"GLOBAL_EDIT_STORE_TO_CARD|"+cardName,llDumpList2String(positions,"|")); + >> where "cardName" is the full string name of the notecard (including the .menuxxxx part) + >> it should always have been preceded by a persist changes message but it's possible asynch handling could cause this to arrive first + +Any time PMAC leaves edit mode: + llMessageLinked(LINK_THIS,0,"GLOBAL_NOTICE_LEAVING_EDIT_MODE",llDumpList2String(positions,"|")); + >> this could be a result of a user standing/disconnecting during edit, or could be the owner intentionally leaving edit mode +>>> END OF GLOBAL COMMANDS + +***** THE SPECIALS MENU ***** + +An add-on that needs to add a menu button to the user's dialog options may do so by registering the button and associated command string with the PMAC Core script. The button will then be added to the list of buttons to display in the SPECIALS menu. If there are no registered buttons the SPECIALS button will not be available in the options menu. When the user clicks a button, the command string associated with that button is sent and dialog control is passed to the addonto allow it to do whatever it needs to do (including additional dialog with the user). The add-on MUST then tell PMAC Core when to resume its own dialog (even if the add-on does something instantly without dialog). + +Any time control changes to a new user the current SPECIALS button list is erased (in case a special menu button should only be displayed to specific users) so your add-on will need to re-register the button any time it receives the global message GLOBAL_NEW_USER_ASSUMED_CONTROL. The message string is followed by the pipe symbol and then the new controller's UUID. + +To register a menu button your add-on script needs to send this message to the PMAC Core script: + llMessageLinked(LINK_THIS,-1,"MAIN_REGISTER_MENU_BUTTON|"+buttonName,commandString); + +When the user selects the menu button in the specials menu the command string sent by PMAC Core is the command string along with the user key and PMAC always then transfers dialog control to the add-on (even if it doesn't use dialogs!) + llMessageLinked(LINK_THIS,0,commandString+"|"+user,llDumpList2String(positions,"|")); + +To return to the main controller's dialog menu (which will redisplay the options menu) the add-on must send the command: + llMessageLinked(LINK_THIS,-1,"MAIN_RESUME_MAIN_DIALOG",NULL_KEY); + +To unregister a button use: + llMessageLinked(LINK_THIS,-1,"MAIN_UNREGISTER_MENU_BUTTON|"+buttonName,commandString); + +When registering a button please be aware that: +- when PMAC receives the registration command it checks to see if buttonName is already in its list of buttons for the specials menu +- it makes no check on the commandString +- if buttonName doesn't exist in the list, it (and its associated command) is added and the list is resorted in alphabetical order +- if buttonName already exists in the specials menu list it will replace it (allowing you to update the command string to send on pressing the button) +- the check is a simple search so make sure your buttonName is unique enough that it won't match another add-on's button +- buttonName can be up to 25 characters but should be kept short if possible (the full buttonName is displayed in the dialopg box's text) +- commandString has no character or length restrictions +- make sure your commandString is unique enough that only your own add-on will respond to it +- keep in mind that on button press a pipe symbol and the user's UUID is appended to the commandString + +When unregistering a button please be aware that: +- both buttonName and commandString must exactly match the existing values +- if match isn't found it fails silently + +CANNOT POSSIBLY STRESS STRONGLY ENOUGH: +PMAC ignores all message that have a num value other than -1 +DO NOT FORGET that any time a button is pressed PMAC Core will suspend its own dialog and MUST be told when to resume by sending MAIN_RESUME_MAIN_DIALOG. If you forget to do so (or send the message with a num field value other than -1) the main dialog will never reappear and the user will probably think the system is broken. + +OTHER CONSIDERATIONS: +Add-on scripts using buttons will need to account for the possibility of a variety of possibilities including: +- PMAC may be in auto mode so new animations could be called while dialog control is still with the add-on (you'll know this has happened via its link messages) +- the current controller might touch the object which will also bring back the core script's dialog - for many viewers this means your dialog box will be removed +- another user could touch the object and take control away from the current user at any time (you'll know this has happened via the global notifier) +- the current controller could stand and should then have all dialogs revoked (you'll know this has happened via the global notifier) +- the current controller could tp out of the region or disconnect +- a new user might sit down which will trigger an animation call but no other message (so compare user UUID lists if this is imporant) +- another user might stand, tp out of the region, or disconnect (you'll receive a global notifier of this) +- in Opensim a lot of things can unexpectedly happen asynchronously do don't bank on a strict order of events +- other possibilities I haven't thought of...but you should :p + + +**** A FINAL WORD ABOUT COMMANDS **** + +In addition to needing to account for the possibility all of the above "OTHER CONSIDERATIONS" it's worth remembering that there are a variety of cases where the playAnimation() UDF in PMAC Core will replay the same animation that is already playing. This will frequently happen in edit mode, or when additional users sit, so your add-on will need to keep track of users to avoid de/re-rezzing objects or executing lengthy code when unnecessary. + + +**** AND A COUPLE FINAL REQUESTS *** + +While not mandatory, I would ask that add-on creators give their scripts a name that begins with "..addon " (two dots, then addon then a space) followed by your addon name. That way all addons should generally appear near the top of the prim's inventory list, immediately below the PMAC core script. This will make it far easier for builders and users to tell at a glance which add-ons are included + +Please include documentation with your add-ons! I know it sucks writing documentation (heck, I've just done 5 notecards worth of it!!!) but there really *are* people out there who read them. (I would also prefer not to be flooded with IMs from builders who want me to figure out how someone else's addon works!) If it requires minimal explanation you could have it as comments in the script, but in general a notecard would be preferred with the name "!!READ ME: " then the addon name (for the same reason as above). + +Please keep the "chattiness" of your add-ons to a minimum or include a user-setting to control how much feedback is given. Most users want to enjoy using a product, not be flooded with text. Unless it's absolutely necessary to use general chat, please use llRegionSayTo() to communicate with users (it's the absolutely lowest resource user). DO NOT EVER USE llInstantMessage() unless it's absolutely necessary (it's a resource-hogging thread locking function). + +If you write what you think is a really great add-on and would like it include as part of a standard PMAC core package please send it to me along with a *working* setup to test. I'd be only too happy to add it if it seems bug free and adheres to the overall design goals for the system. diff --git a/PMAC/PARAMOUR MULTI-ANIMATION CONTROLLER (PMAC)/Object/.menu002A Dance.txt b/PMAC/PARAMOUR MULTI-ANIMATION CONTROLLER (PMAC)/Object/.menu002A Dance.txt new file mode 100644 index 00000000..a5d2826d --- /dev/null +++ b/PMAC/PARAMOUR MULTI-ANIMATION CONTROLLER (PMAC)/Object/.menu002A Dance.txt @@ -0,0 +1,20 @@ +// :SHOW: +// :CATEGORY:NPC +// :NAME:PMAC +// :AUTHOR:Aine Caoimhe +// :KEYWORDS: +// :CREATED:2015-11-24 20:38:40 +// :EDITED:2015-11-24 19:38:40 +// :ID:1095 +// :NUM:1876 +// :REV:1 +// :WORLD:Second Life +// :DESCRIPTION: +// PARAMOUR MULTI-ANIMATION CONTROLLER (PMAC) v1.02 (OSSL) +// :CODE: +Slowdance|NO_COM|Slowdance (Female)|<0.0274,-0.0735,1.3718>|<0,0.0175,-0.9998,0>|Slowdance (Male)|<0,-0.0483,1.382>|<0.0175,0,0,0.9998> + +BeautifulDream|NO_COM|BeautifulDream (Female)|<0,-0.0493,1.4136>|<0,0,0,1.>|BeautifulDream (Male)|<0,-0.0477,1.367>|<0,0,0,1.> +Bite Me|NO_COM|Bite-Me (Female)|<0,-0.045,1.2894>|<0,0,0,-1.>|Bite-Me (Male)|<0,-0.045,1.2894>|<0,0,0,-1.> +Eternal Love|NO_COM|Eternal-Love (Female)|<0.0786,-0.6648,1.3915>|<0,0,0,-1.>|Eternal-Love (Male)|<0,-0.6168,1.2796>|<0,0,0,-1.> + diff --git a/PMAC/PARAMOUR MULTI-ANIMATION CONTROLLER (PMAC)/Object/Animation.ani b/PMAC/PARAMOUR MULTI-ANIMATION CONTROLLER (PMAC)/Object/Animation.ani new file mode 100644 index 00000000..e69de29b diff --git a/PMAC/PARAMOUR MULTI-ANIMATION CONTROLLER (PMAC)/Object/Animation2.ani b/PMAC/PARAMOUR MULTI-ANIMATION CONTROLLER (PMAC)/Object/Animation2.ani new file mode 100644 index 00000000..e69de29b diff --git a/PMAC/PARAMOUR MULTI-ANIMATION CONTROLLER (PMAC)/Object/PMAC Core v1.02.lsl b/PMAC/PARAMOUR MULTI-ANIMATION CONTROLLER (PMAC)/Object/PMAC Core v1.02.lsl new file mode 100644 index 00000000..5efd14a1 --- /dev/null +++ b/PMAC/PARAMOUR MULTI-ANIMATION CONTROLLER (PMAC)/Object/PMAC Core v1.02.lsl @@ -0,0 +1,1227 @@ +// :SHOW: +// :CATEGORY:NPC +// :NAME:PMAC +// :AUTHOR:Aine Caoimhe +// :KEYWORDS: +// :CREATED:2015-11-24 20:38:40 +// :EDITED:2015-11-24 19:38:40 +// :ID:1095 +// :NUM:1877 +// :REV:1 +// :WORLD:OpenSim +// :DESCRIPTION: +// PARAMOUR MULTI-ANIMATION CONTROLLER (PMAC) v1.02 (OSSL) +// :CODE: + + +// PARAMOUR MULTI-ANIMATION CONTROLLER (PMAC) v1.02 +// by Aine Caoimhe (Mata Hari)(c. LACM) March 2015-May 2015 +// Provided under Creative Commons Attribution-Non-Commercial-ShareAlike 4.0 International license. +// Please be sure you read and adhere to the terms of this license: https://creativecommons.org/licenses/by-nc-sa/4.0/ +// +// UserConfig added by Seth Nygard March 2015 +// modified to store positions for NC_PROPS addon Neo Cortex April 2015 +// additional tweaks by Aine Caoimhe May 2015 +// *** THIS SCRIPT REQUIRES (AND WILL ONLY WORK) IN REGIONS WHERE THE SCRIPT OWNER HAS OSSL FUNCTION PERMISSIONS *** +// *** THIS SCRIPT MUST ALWAYS BE LOCATED IN THE ROOT PRIM OF A LINKSET *** +// +// ********************************************************* +// ***** GENERAL USER SETTINGS - ADJUST AS PREFERED ***** +// ********************************************************* +string defaultGroup="Dance"; // name of a group (not the full card name!) to load by default (regardless of its permission setting) + // +integer resetOnQuit=FALSE; // TRUE = when no more sitters, reset the script (will also load default group again) + // FALSE = leave most recently loaded animation active +integer ownerUseReq=FALSE; // TRUE = the owner must be the first to sit and other people can only sit while the owner is still present and seated + // FALSE = no restriction...anyone can sit and use it at any time +integer ownerOnlyMenus=FALSE; // TRUE = only the owner can access the dialog menus (can be turned off in options menu until script is reset or it is turned on again) - NOT RECOMMENDED unless ownerUseReq=TRUE + // FALSE = anyone can be the controller +integer ownerUseUnlocksPerms=FALSE; // TRUE = if owner is a current user, all users then have access to all groups and NPCs + // FALSE = only the owner can ever load an owner-only Group or NPC +integer autoOn=TRUE; // TRUE = will start in auto mode after a reset + // FALSE = will start in manual mode after a reset -- after use will remain in whatever state it was left in unless resetOnQuit=TRUE +float autoTimer=120.0; // default time to use for the autotimer (in seconds) - after use will remain at whatever timer was last set to unless resetOnQuit=TRUE; +string gs_ConfigName=".PMAC-CONFIG"; // Name of optional notecard with user defined configuration that overide the above values if present +integer showGroupsMenuFirst=FALSE; // TRUE = when first initiating dialog, show the groups menu instead of the current group's animation menu; FALSE = show current group's animation menu +integer allowSoloNPC=TRUE; // TRUE = NPCs can be left rezzed even if there are no avatars seated; FALSE = kill all NPCs if there are no remaining avatars +// +// *************************************** +// ***** ADVANCED/BUILDER SETTINGS ***** +// *************************************** +string handleName="~~~positioner"; // inventory object to rez as a handle for positioning +list handleColours=[<1.000, 0.004, 0.667>,<0.004, 0.667, 1.000>,<0.667, 1.000, 0.004>, // colours to use for handles (must supply 9) + <1.000, 0.004, 0.004>,<0.004, 0.004, 1.000>,<1.000, 0.667, 0.004>, + <0.667, 0.004, 1.000>,<0.004, 1.000, 0.667>,<0.004, 1.000, 0.004>]; +vector handleSize=<0.2,0.2,3.0>; // size of handles +float handleAlpha=0.5; // alpha of handles +string baseAn="~~~~~base_DO_NOT_DELETE_ME!!!!!"; // name of the P1 animation to use for synch +// +// ************************************************ +// ***** DO NOT CHANGE ANYTHING BELOW HERE ***** +// ***** UNLESS YOU KNOW WHAT YOU'RE DOING! ***** +// ************************************************ +key user; +list positions; +list invNpc; // fullName | A/G/O | buttonName +integer invNpcStride=3; +list npcList; +integer npcPage; +list invGroups; // fullName | positions | A/G/O | buttonName +integer invGroupStride=4; +list groupList; +integer groupPage; +integer myChannel; +integer diaHandle; +string menu; +string txtDia; +list butDia; +string currentGroup; +list anData; // anName | command | A1Name | A1 Pos | A1 Rot | ... +integer anStride; +list anList; +list currentAn; // groupName | anName | command | A1Name | A1 Pos | A1 Rot | ... +integer anPage; +list editHandles; +integer rezzingHandles; +float editTimer=0.2; +string myState="INITIALIZING"; +list specials; //buttonName | stringToSend +integer specPage; +integer gi_HaveConfig=0; +// NC_PROP ADDON +integer gi_NC_PROP_CHANGED=FALSE; // global integer flag to indicate if new prop values have been sent form NC_PROPS addon +string gs_NC_PROP_DATA=""; // global string containing changed props data + +showAnMenu() +{ + integer maxAn=llGetListLength(anList); + integer showStart=(anPage+1); + integer showEnd=(anPage+6); + if (showEnd>maxAn) showEnd=maxAn; + txtDia=""+"ANIMATION MENU: Select an animation\n"+currentlyPlaying()+"\nShowing animation"; + if (showEnd!=showStart) txtDia+="s "+(string)showStart+" to "+(string)showEnd; + else txtDia+=" "+(string)showStart; + txtDia+=" of "+(string)maxAn+ " total animations in this group\n\n"+llDumpList2String(llList2List(anList,anPage,anPage+5),"\n"); + butDia=[]+llList2List(anList,anPage,anPage+5); + while (llGetListLength(butDia)<6) { butDia=[]+butDia+["-"]; } + butDia=[]+butDia+["< PREV","SYNCH","NEXT >","GROUPS","OPTIONS","QUIT"]; + menu="MENU_ANIM"; + startListening(); +} +showGroupsMenu() +{ + txtDia=""+"GROUPS MENU: Select a group of animations\n"+currentlyPlaying()+"\nShowing groups "+(string)(groupPage+1)+" to "+(string)(groupPage+6)+" of "+(string)llGetListLength(groupList)+ " total groups\n\n"+llDumpList2String(llList2List(groupList,groupPage,groupPage+5),"\n"); + butDia=[]+llList2List(groupList,groupPage,groupPage+5); + while (llGetListLength(butDia)<6) { butDia=[]+butDia+["-"]; } + butDia=[]+butDia+["< PREV","SYNCH","NEXT >","<< BACK","OPTIONS","QUIT"]; + menu="MENU_GROUPS"; + startListening(); +} +showEditMenu() +{ + txtDia=""+"EDIT MODE ACTIVE!!!\n"+currentlyPlaying()+" (#"+(string)(llListFindList(anList,[llList2String(currentAn,1)])+1)+" of "+(string)llGetListLength(anList)+")\n\nPlease ensure you have read and are familiar with the PMAC instructions for using the edit menu, particularly if you use add-on modules. You have been warned..."; + butDia=[]+["< PREV","SYNCH","NEXT >","REVERT THIS","STORE THIS","STORE ADDON","EDIT OFF","SAVE CARD","SAVE NEW"]; + menu="MENU_EDIT"; + startListening(); +} +showOptionsMenu() +{ + txtDia=""+"OPTIONS MENU:\n\n"; + butDia=[]; + if (user==llGetOwner()) + { + if (ownerOnlyMenus) + { + txtDia+="EDIT ON enters edit mode (all positions must be filled)\nMENUS UNLOCK allows other users to take control\n"; + butDia=[]+butDia+["EDIT ON","MENUS UNLOCK","-"]; + } + else + { + txtDia+="EDIT ON enters edit mode (all positions must be filled)\nMENUS LOCK prevents other users from taking control\n"; + butDia=[]+butDia+["EDIT ON","MENUS LOCK","-"]; + } + } + txtDia+="AUTO is used to enable, disable or adjust auto mode\n"; + butDia=[]+butDia+["AUTO","-"]; + if (llGetListLength(specials)>0) + { + txtDia+="SPECIAL access special add-on menus\n"; + butDia=[]+butDia+["SPECIAL"]; + } + else butDia=[]+butDia+["-"]; + txtDia+="SWAP to swap positions\nUNSIT to force someone to stand up or remove a npc\n"; + butDia=[]+butDia+["SWAP","UNSIT"]; + if (llListFindList(positions,[NULL_KEY])>-1) + { + txtDia+="ADD NPC to have a npc join you\n"; + butDia=[]+butDia+["ADD NPC"]; + } + else butDia=[]+butDia+["-"]; + butDia=[]+butDia+["<< BACK","SYNCH","QUIT"]; + menu="MENU_OPTIONS"; + startListening(); +} +showSpecialsMenu() +{ + txtDia=""+"SPECIALS MENU\n\nThese are options supplied by any add-ons you have installed. Please consult their instructions for details.\n"; + butDia=[]; + integer i=specPage; + while (llGetListLength(butDia)<9) + { + if (i"]; + menu="MENU_SPECIALS"; + startListening(); +} +showAutoMenu() +{ + txtDia="AUTO MENU\n\nAuto mode is currently "; + butDia=[]+["120","300","600","30","60","90"]; + if (autoOn) + { + txtDia+="ON and set to "+(string)llRound(autoTimer)+" seconds\n\nSelect a different time if you wish, or AUTO OFF to switch to manual mode"; + butDia=[]+butDia+["AUTO OFF","-","CANCEL"]; + } + else + { + txtDia+="OFF\n\nSelect AUTO ON to use the last preset time or pick your preferred timer"; + butDia=[]+butDia+["AUTO ON","-","CANCEL"]; + } + menu="MENU_SELECT_AUTO_MODE"; + startListening(); +} +showAddNpcMenu() +{ + txtDia=""+"ADD NPC\n\nSelect the NPC to add. It will occupy the first available position\n\n"+llDumpList2String(llList2List(npcList,npcPage,npcPage+8),"\n"); + butDia=[]+llList2List(npcList,npcPage,npcPage+8); + while (llGetListLength(butDia)<9) { butDia=[]+butDia+["-"]; } + butDia=[]+butDia+["< PREV","CANCEL","NEXT >"]; + menu="MENU_ADD_NPC"; + startListening(); +} +playAnimation(string name) +{ + if (autoOn) llSensorRemove(); + integer i=llGetListLength(positions); + while (--i>=0) { if (llList2Key(positions,i)!=NULL_KEY) osAvatarStopAnimation(llList2Key(positions,i),llList2String(currentAn,3+i*3)); } + integer indexAn=llListFindList(anData,[name]); + list nextAn=[currentGroup]+llList2List(anData,indexAn,indexAn+anStride-1); + integer have=llGetListLength(positions); + integer need=llRound(((float)llGetListLength(nextAn)-3.0)/3.0); + while(haveneed) && (llListFindList(positions,[NULL_KEY])>=0)) { positions=[]+llDeleteSubList(positions,llListFindList(positions,[NULL_KEY]),llListFindList(positions,[NULL_KEY]));have--; } + if (have>need) + { + llSay(0,"Encountered an error in attempting to start a new animation: there are currently too many users seated. You will need to reduce the number of seated users by "+(string)(have-need)+" to play it, or select a different group of animations"); + return; + } + i=llGetListLength(positions); + while (--i>=0) + { + key who=llList2Key(positions,i); + if (who!=NULL_KEY) + { + osAvatarPlayAnimation(who,llList2String(nextAn,3+i*3)); + setPosition(who,getUserLink(who),llList2Vector(nextAn,4+i*3),llList2Rot(nextAn,5+i*3)); + if (myState=="EDIT") setHandle(llList2Key(editHandles,i),llList2Vector(nextAn,4+i*3),llList2Rot(nextAn,5+i*3)); + } + } + currentAn=[]+nextAn; + llMessageLinked(LINK_THIS,0,"GLOBAL_NEXT_AN|"+llList2String(currentAn,2),llDumpList2String(positions,"|")); + if (autoOn) llSensorRepeat("THIS_WILL_NEVER_RETURN_A_SENSOR_RESULT",NULL_KEY,AGENT,0.001,0.0,autoTimer); +} +doSynch() +{ + integer i=llGetListLength(positions); + while (--i>=0) + { + key who=llList2Key(positions,i); + if (who!=NULL_KEY) + { + osAvatarStopAnimation(who,llList2String(currentAn,3+i*3)); + osAvatarPlayAnimation(who,llList2String(currentAn,3+i*3)); + } + } + llMessageLinked(LINK_THIS,0,"GLOBAL_ANIMATION_SYNCH_CALLED",llDumpList2String(positions,"|")); +} +doSwapPositions(integer indFrom,integer indTo) +{ + key whoFrom=llList2Key(positions,indFrom); + if (whoFrom!=NULL_KEY) + { + osAvatarStopAnimation(whoFrom,llList2String(currentAn,3+indFrom*3)); + setPosition(whoFrom,getUserLink(whoFrom),llList2Vector(currentAn,4+indTo*3),llList2Rot(currentAn,5+indTo*3)); + } + key whoTo=llList2Key(positions,indTo); + if (whoTo!=NULL_KEY) + { + osAvatarStopAnimation(whoTo,llList2String(currentAn,3+indTo*3)); + setPosition(whoTo,getUserLink(whoTo),llList2Vector(currentAn,4+indFrom*3),llList2Rot(currentAn,5+indFrom*3)); + } + positions=[]+llListReplaceList(positions,[whoFrom],indTo,indTo); + positions=[]+llListReplaceList(positions,[whoTo],indFrom,indFrom); + doSynch(); +} +doChangeUser(key who) +{ + if (user!=NULL_KEY) llRegionSayTo(user,0,llGetUsername(who)+" has now taken control of me"); + user=who; + buildGroupList(); + specials=[]; + llMessageLinked(LINK_THIS,0,"GLOBAL_NEW_USER_ASSUMED_CONTROL|"+(string)user,llDumpList2String(positions,"|")); + if (llListFindList(groupList,[currentGroup])==-1) loadGroup(llList2String(groupList,0)); + else if (autoOn) showGroupsMenu(); + else if (showGroupsMenuFirst) showGroupsMenu(); + else showAnMenu(); +} +doQuit() +{ + if (myState=="EDIT") removeHandles(); + integer i=llGetListLength(positions); + while (--i>=0) + { + key who=llList2Key(positions,i); + if (who!=NULL_KEY) + { + if (osIsNpc(who)) osNpcRemove(who); + else + { + llRegionSayTo(who,0,"Quit called"); + llUnSit(who); + } + } + } +} +loadGroup(string name) +{ + if (name==currentGroup) + { + if (user!=NULL_KEY) showAnMenu(); + return; + } + integer indToLoad=llListFindList(invGroups,[name])-3; + integer newUserCount=llList2Integer(invGroups,indToLoad+1); + if (newUserCount-1) { if (llList2Key(positions,a)!=NULL_KEY) agents++; } + if (agents>newUserCount) + { + llRegionSayTo(user,0,"You cannot use animations from the "+name+" group at the moment because it is for a maximum of "+(string)newUserCount+" users and there are currently "+(string)agents+" seated. Please select a different group of animations, remove NPCs, or ask someone to stand."); + if (user!=NULL_KEY) startListening(); + return; + } + } + currentGroup=name; + anData=[]+llParseString2List(osGetNotecard(llList2String(invGroups,indToLoad)),["|","\n"],[""]); + anStride=2+(3*newUserCount); + anPage=0; + anList=llList2ListStrided(anData,0,-1,anStride); + if (autoOn && (myState=="RUNNING")) + { + playAnimation(llList2String(anList,0)); + if (user!=NULL_KEY) + { + llRegionSayTo(user,0,"Now automatically playing animations from "+name); + showGroupsMenu(); + } + } + else if (user!=NULL_KEY) showAnMenu(); +} +loadConfig() +{ + integer i; + float n; + string sParam; + string sValue; + list lParams=[]; + for(i=0;i=0) ) ) groupList=[]+groupList+[llList2String(invGroups,i+3)]; + i+=invGroupStride; + } +} +buildNpcList() +{ + npcList=[]; + npcPage=0; + integer i; + while (i=0) ) ) npcList=[]+npcList+[llList2String(invNpc,i+2)]; + i+=invNpcStride; + } + showAddNpcMenu(); +} +buildInventoryLists() +{ + invGroups=[]; + invNpc=[]; + integer i=llGetInventoryNumber(INVENTORY_NOTECARD); + while (--i>-1) + { + string name=llGetInventoryName(INVENTORY_NOTECARD,i); + if (llSubStringIndex(name,".menu")==0) + { + if (llListFindList(invGroups,[llGetSubString(name,10,-1)])>-1) llOwnerSay("ERROR! You have two groups notecards where the name \""+llGetSubString(name,10,-1)+"\" is used for the button! Group button names must be unique"); + else invGroups=[]+[name,(integer)(llGetSubString(name,7,7)),llGetSubString(name,8,8),llGetSubString(name,10,-1)]+invGroups; + } + else if (llSubStringIndex(name,".NPC")==0) + { + if (llListFindList(invNpc,[llGetSubString(name,8,-1)])>-1) llOwnerSay("ERROR! You have two NPC notecards where the NPC name is \""+llGetSubString(name,8,-1)+"\" but NPC names must be unique"); + else invNpc=[]+[name,llGetSubString(name,6,6),llGetSubString(name,8,-1)]+invNpc; + } + else if (llSubStringIndex(name,gs_ConfigName)==0) + { + gi_HaveConfig=1; + } + } +} +saveCard(string cardName) +{ + if (llGetInventoryType(cardName)==INVENTORY_NOTECARD) + { + llRemoveInventory(cardName); + llSleep(0.25); + } + integer i; + integer l=llGetListLength(anData); + string dataToStore; + while (i1) && (ret==FALSE)) + { + key this=llGetLinkKey(link); + if(this==who) ret=link; + link--; + } + } + return ret; +} +startListening() +{ + llDialog(user,txtDia,llList2List(butDia,9,11)+llList2List(butDia,6,8)+llList2List(butDia,3,5)+llList2List(butDia,0,2),myChannel); +} +string currentlyPlaying() +{ + string strToReturn="Currently playing: "+llList2String(currentAn,0)+" > "+llList2String(currentAn,1); + if (autoOn) strToReturn+="\nAUTO mode is on and set to "+(string)llRound(autoTimer)+" seconds"; + return strToReturn; +} +persistChanges() +{ + integer u=llGetListLength(positions); + while(--u>=0) + { + list avData=getPosition(llList2Key(positions,u),getUserLink(llList2Key(positions,u))); + vector pos=llList2Vector(avData,0); + rotation rot=llList2Rot(avData,1); + string strPos="<"+trimF(pos.x)+","+trimF(pos.y)+","+trimF(pos.z)+">"; + string strRot="<"+trimF(rot.x)+","+trimF(rot.y)+","+trimF(rot.z)+","+trimF(rot.s)+">"; + currentAn=[]+llListReplaceList(currentAn,[strPos,strRot],4+u*3,5+u*3); + } + integer anIndex=llListFindList(anData,llList2List(currentAn,1,1)); + if(gi_NC_PROP_CHANGED) { // NC_PROPS + string old_cmd=llList2String(currentAn,2); + integer NC_PROP_data_start=llSubStringIndex(old_cmd,"NC_PROP{")+8; // find start + // test is little weird, as i added +8 to compensate for search string length + if (NC_PROP_data_start != 7) { + // search ending delimiter in rest of string + integer NC_PROP_data_end=NC_PROP_data_start+llSubStringIndex(llGetSubString(old_cmd, NC_PROP_data_start,-1),"}"); + string new_cmd=llGetSubString(old_cmd, 0, NC_PROP_data_start -1) + gs_NC_PROP_DATA + llGetSubString(old_cmd, NC_PROP_data_end,-1); + currentAn=[]+llListReplaceList(currentAn,[new_cmd],2,2); + } + gi_NC_PROP_CHANGED=FALSE; + } + anData=[]+llListReplaceList(anData,llList2List(currentAn,1,-1),anIndex,anIndex+anStride-1); + llMessageLinked(LINK_THIS,0,"GLOBAL_EDIT_PERSIST_CHANGES",llDumpList2String(positions,"|")); +} +rezHandles() +{ + if (llGetListLength(editHandles)=0) { osSetPrimitiveParams( llList2Key(editHandles,h),[PRIM_SIZE,handleSize,PRIM_COLOR,ALL_SIDES,llList2Vector(handleColours,h),handleAlpha,PRIM_TEXT,"pos "+(string)(h+1),llList2Vector(handleColours,h),1.0,PRIM_NAME,"pos "+(string)(h+1)]); } + playAnimation(llList2String(currentAn,1)); + llSetTimerEvent(editTimer); + doSynch(); + showEditMenu(); + } +} +removeHandles() +{ + llSetTimerEvent(0.0); + myState="RUNNING"; + llMessageLinked(LINK_THIS,0,"GLOBAL_NOTICE_LEAVING_EDIT_MODE",llDumpList2String(positions,"|")); + integer l=llGetListLength(editHandles); + while (--l>=0) { osMessageObject(llList2Key(editHandles,l),"HANDLE_DIE"); } + editHandles=[]; + showOptionsMenu(); +} +setHandle(key prim, vector relPos, rotation relRot) +{ + vector pos=relPos*llGetRot()+llGetPos(); + rotation rot=relRot*llGetRot(); + osSetPrimitiveParams(prim,[PRIM_POSITION,pos,PRIM_ROTATION,rot]); +} +setPosition(key who, integer link, vector pos, rotation rot) +{ + vector size = llGetAgentSize(who); + float fAdjust = ((((0.008906 * size.z) + -0.049831) * size.z) + 0.088967) * size.z; + llSetLinkPrimitiveParamsFast(link,[PRIM_POS_LOCAL, ((pos + <0.0, 0.0, 0.4>) - (llRot2Up(rot) * fAdjust)), PRIM_ROT_LOCAL, rot]); +} +list getPosition(key who, integer link) +{ + vector size = llGetAgentSize(who); + float fAdjust = ((((0.008906 * size.z) + -0.049831) * size.z) + 0.088967) * size.z; + list avData=llGetLinkPrimitiveParams(link,[PRIM_POS_LOCAL,PRIM_ROT_LOCAL]); + vector avPos=llList2Vector(avData,0); + rotation avRot=llList2Rot(avData,1); + vector avPosUnadjusted=(avPos - <0.0, 0.0, 0.4>) + (llRot2Up(avRot) * fAdjust); + return [avPosUnadjusted,avRot]; +} +list regToRel(vector regionPos,rotation regionRot) +{ + vector relPos=(regionPos - llGetPos()) / llGetRot(); + rotation relRot=regionRot/ llGetRot(); + return [relPos,relRot]; +} +list relToReg(vector refPos,rotation refRot) +{ + vector regionPos=refPos*llGetRot()+llGetPos(); + rotation regionRot=refRot*llGetRot(); + return [regionPos,regionRot]; +} +string trimF(float value) +{ + integer newVal=llRound(value*10000); + integer negFlag=FALSE; + if (newVal<0) + { + negFlag=TRUE; + newVal*=-1; + } + integer strLength; + string retStr; + if (newVal==0) retStr="0"; + else if (newVal<10) retStr="0.000"+(string)newVal; + else if (newVal<100) retStr="0.00"+(string)newVal; + else if (newVal<1000) retStr="0.0"+(string)newVal; + else if (newVal<10000) retStr="0."+(string)newVal; + else + { + retStr=(string)newVal; + strLength=llStringLength(retStr); + retStr=llGetSubString(retStr,0,strLength-5)+"."+llGetSubString(retStr,strLength-4,strLength-1); + } + while (llGetSubString(retStr,strLength,strLength)=="0") + { + retStr=llGetSubString(retStr,0,strLength-1); + strLength-=1; + } + if (negFlag) retStr="-"+retStr; + return retStr; +} +default +{ + state_entry() + { + if (llGetAttached()) return; + if (llGetLinkNumber()>1) + { + myState="ERROR"; + llOwnerSay("ERROR! The main PMAC controller script must always be located in the root prim of a linkset!"); + return; + } + myChannel=0x80000000|(integer)("0x"+(string)llGetKey()); + user=NULL_KEY; + buildInventoryLists(); + if (gi_HaveConfig) + loadConfig(); + if (llGetInventoryType(baseAn)!=INVENTORY_ANIMATION) + { + llOwnerSay("ERROR! Unable to find the base priority 1 animation to use for synch: "+baseAn); + myState="ERROR"; + return; + } + + if (llListFindList(invGroups,[defaultGroup])==-1) + { + myState="ERROR"; + llOwnerSay("ERROR! Unable to find the specified default group \""+defaultGroup+"\" in inventory. Make sure you supplied the simple group name, not the full card name"); + return; + } + loadGroup(defaultGroup); + integer i=llList2Integer(invGroups,llListFindList(invGroups,[defaultGroup])-2); + positions=[]; + while (--i>=0) { positions=[]+positions+[NULL_KEY]; } + currentAn=[]+[currentGroup]+llList2List(anData,0,anStride-1); + myState="READY"; + llMessageLinked(LINK_THIS,0,"GLOBAL_SYSTEM_RESET",NULL_KEY); + llSitTarget(<0.0,0,0.001>,ZERO_ROTATION); + llOwnerSay("Initialization complete and ready to use"); + } + on_rez(integer num) + { + llResetScript(); + } + object_rez(key id) + { + if (!rezzingHandles) return; + editHandles=[]+editHandles+[id]; + rezHandles(); + } + sensor(integer num) + { + llOwnerSay("ERROR! Sensor event inexplicably returned a result!"); + llSensorRemove(); + autoOn=FALSE; + } + no_sensor() + { + if (!autoOn) + { + llOwnerSay("ERROR! Sensor repeat triggered but auto mode is off. Figure out how this can happen and fix. Sensor removed"); + llSensorRemove(); + return; + } + if (myState!="RUNNING") + { + llOwnerSay("ERROR! Sensor repeat triggered while not in normal running state: Please figure out how this happened and fix. Sensor removed and auto turned off.\nState was=: "+myState); + autoOn=FALSE; + llSensorRemove(); + return; + } + integer i=llListFindList(anList,[llList2String(currentAn,1)]); + if (i==-1) + { + llOwnerSay("ERROR! Auto timer was unable to determine the index of the currently playing animation!"); + llSensorRemove(); + autoOn=FALSE; + } + else + { + i++; + if (i>=llGetListLength(anList)) i=0; + playAnimation(llList2String(anList,i)); + } + } + timer() + { + if(myState=="EDIT") + { + llSetTimerEvent(0.0); + integer l=llGetListLength(positions); + while (--l>-1) + { + key who=llList2Key(positions,l); + if (who==NULL_KEY) + { + llOwnerSay("ERROR! NULL_KEY user while processing timer event edit mode. Leaving edit mode without saving changes."); + removeHandles(); + return; + } + if (llGetAgentSize(who)==ZERO_VECTOR) + { + llOwnerSay("ERROR! Cannot detect user in region while processing timer event edit mode. Leaving edit mode without saving changes"); + removeHandles(); + return; + } + list handleData=llGetObjectDetails(llList2Key(editHandles,l),[OBJECT_POS,OBJECT_ROT]); + if (llGetListLength(handleData)==0) + { + llOwnerSay("ERROR! Unable to detect a handle! Leaving edit mode without saving changes"); + removeHandles(); + return; + } + handleData=[]+regToRel(llList2Vector(handleData,0),llList2Rot(handleData,1)); + setPosition(who,getUserLink(who),llList2Vector(handleData,0),llList2Rot(handleData,1)); + } + llSetTimerEvent(editTimer); + } + else llSetTimerEvent(0.0); + } + link_message(integer sender,integer num,string message,key command) + { + if (num!=-1) return; + if (message=="MAIN_RESUME_MAIN_DIALOG") showOptionsMenu(); + else if (llSubStringIndex(message,"MAIN_REGISTER_MENU_BUTTON")==0) + { + string buttonName=llList2String(llParseString2List(message,["|"],[]),1); + integer locationToAdd=llListFindList(specials,[buttonName]); + if (locationToAdd==-1) specials=[]+specials+[buttonName,command]; + else specials=[]+llListReplaceList(specials,[buttonName,command],locationToAdd,locationToAdd+1); + specials=[]+llListSort(specials,2,TRUE); + } + else if (llSubStringIndex(message,"MAIN_UNREGISTER_MENU_BUTTON")==0) + { + string buttonName=llList2String(llParseString2List(message,["|"],[]),1); + integer locationToKill=llListFindList(specials,[buttonName,command]); + if (locationToKill==-1) return; + else specials=[]+llDeleteSubList(specials,locationToKill,locationToKill+1); + specials=[]+llListSort(specials,2,TRUE); + } + else if (llSubStringIndex(message,"NC_PROP_UPDATE")==0) // NC_PROP addon + { + gi_NC_PROP_CHANGED=TRUE; + gs_NC_PROP_DATA=llGetSubString(message,15,-1); + } + } + changed (integer change) + { + if (change & CHANGED_LINK) + { + if (llGetLinkNumber()>1) + { + llOwnerSay("ERROR! You have changed the linkset and the PMAC main script is no longer located in the root prim!"); + myState="ERROR"; + return; + } + integer i=llGetNumberOfPrims(); + integer l=llGetObjectPrimCount(llGetKey()); + list seated; + integer realAvi; + while (i>l) + { + key who=llGetLinkKey(i); + if ((myState=="ERROR") || (myState=="INITIALIZING")) + { + if (osIsNpc(who)) osNpcRemove(who); + else + { + if (myState=="ERROR") llRegionSayTo(who,0,"Sorry, I have encountered an error and must shut down until it is corrected"); + else llRegionSayTo(who,0,"Sorry, you cannot sit while I am initializing. Please wait a moment and try again."); + llUnSit(who); + } + } + else + { + seated=[]+[who]+seated; + if (!osIsNpc(who)) realAvi++; + if (llListFindList(positions,[who])==-1) + { + // new sitter + integer indexToSit=llListFindList(positions,[NULL_KEY]); + if (ownerUseReq && (who!=llGetOwner()) && (llListFindList(positions,[llGetOwner()])==-1)) + { + llRegionSayTo(who,0,"Sorry, the system is set to require that the owner is using me before anyone else may sit."); + llUnSit(who); + } + else if (indexToSit==-1) + { + llRegionSayTo(who,0,"Sorry, there are no available positions for you to occupy. Please wait for someone to stand"); + llUnSit(who); + } + else + { + positions=[]+llListReplaceList(positions,[who],indexToSit,indexToSit); + llSleep(0.2); + list anToStop=llGetAnimationList(who); + osAvatarPlayAnimation(who,baseAn); + integer stop=llGetListLength(anToStop); + key dontStop=llGetInventoryKey(baseAn); + while (--stop>-1) { osAvatarStopAnimation(who,llList2Key(anToStop,stop)); } + if (myState=="READY") + { + if ((osIsNpc(who)) && (!allowSoloNPC)) + { + llSay(0,"Sorry, an NPC cannot sit until there is a human user in control. Unsitting your NPC."); + llUnSit(who); + positions=[]+llListReplaceList(positions,[NULL_KEY],indexToSit,indexToSit); + osAvatarStopAnimation(who,baseAn); + } + else + { + myState="RUNNING"; + llMessageLinked(LINK_THIS,0,"GLOBAL_START_USING",llDumpList2String(positions,"|")); + playAnimation(llList2String(currentAn,1)); + } + } + else playAnimation(llList2String(currentAn,1)); + } + } + } + i--; + } + i=llGetListLength(positions); + while (--i>=0) + { + key who=llList2Key(positions,i); + if (who!=NULL_KEY) + { + if ((!realAvi)&&osIsNpc(who)) + { + if (!allowSoloNPC) + { + osNpcRemove(who); + positions=[]+llListReplaceList(positions,[NULL_KEY],i,i); + } + } + else if (llListFindList(seated,[who])==-1) + { + if (myState=="EDIT") + { + llOwnerSay("WARNING! Someone stood! Leaving edit mode and no changes will be stored to card"); + removeHandles(); + if (who!=llGetOwner())showOptionsMenu(); + } + if (llGetAgentSize(who)!=ZERO_VECTOR) + { + osAvatarPlayAnimation(who,"stand"); + osAvatarStopAnimation(who,llList2String(currentAn,3+i*3)); + osAvatarStopAnimation(who,baseAn); + if (who==user) user=NULL_KEY; + } + positions=[]+llListReplaceList(positions,[NULL_KEY],i,i); + llMessageLinked(LINK_THIS,0,"GLOBAL_USER_STOOD|"+(string)i+"|"+(string)who,llDumpList2String(positions,"|")); + } + } + } + if (!realAvi) + { + if (diaHandle) + { + llListenRemove(diaHandle); + diaHandle=FALSE; + } + if (llGetListLength(seated)<1) + { + if (autoOn) llSensorRemove(); + myState="READY"; + llMessageLinked(LINK_THIS,0,"GLOBAL_SYSTEM_GOING_DORMANT",NULL_KEY); + if (resetOnQuit) llResetScript(); + } + } + } + else if (change & CHANGED_REGION_START) llResetScript(); + else if (change & CHANGED_OWNER) llResetScript(); + } + touch_start(integer num) + { + if (llGetAttached()) return; + key who=llDetectedKey(0); + if (osIsNpc(llDetectedKey(0))) return; + if (myState=="ERROR") + { + if (who==llGetOwner()) llOwnerSay("PMAC somehow entered ERROR state. Please check your chat log for one or more messages indicating the nature of the error, correct it, then reset the script"); + else llRegionSayTo(who,0,"Sorry, I encountered an error and am waiting for the owner to correct the issue and restart me"); + } + else if (myState=="INITIALIZING") llRegionSayTo(who,0,"Please wait until initialization is complete, then try again"); + else if (myState=="READY") llRegionSayTo(who,0,"Please sit to begin using me"); + else if (myState=="EDIT") + { + if (who!=llGetOwner()) llRegionSayTo(who,0,"Sorry, the owner is currently editting positions and must remain in control of the dialog until this is complete."); + else startListening(); + } + else if (myState=="RUNNING") + { + if (llListFindList(positions,[who])==-1) llRegionSayTo(who,0,"Only current users may access the controls. Please sit, then try again"); + else if (ownerOnlyMenus && (who!=llGetOwner())) llRegionSayTo(who,0,"Sorry, this item is currently set to only allow the owner to access the controls."); + else if (who!=user) + { + if (user==NULL_KEY) + { + if (!diaHandle) diaHandle=llListen(myChannel,"",NULL_KEY,""); + doChangeUser(who); + } + else llDialog(who,"Please confirm that you want to take control of me",["TAKE CONTROL","CANCEL"],myChannel); + } + else startListening(); + } + else llOwnerSay("ERROR! Unexpected state when touched. State is: "+myState); + } + listen (integer channel, string name, key who, string message) + { + if (who!=user) + { + if (message=="TAKE CONTROL") + { + if (myState!="RUNNING") + { + if (who==llGetOwner()) llOwnerSay("Cannot give you control because I am not in RUNNING state. Current state is: "+myState); + else llRegionSayTo(who,0,"Sorry, you cannot take control at the moment because I am not currently in normal operation mode."); + return; + } + doChangeUser(who); + } + else if (message=="CANCEL") return; + else return; + } + else if (message=="-") startListening(); + else if (message=="SYNCH") + { + doSynch(); + startListening(); + } + else if (message=="OPTIONS") showOptionsMenu(); + else if (message=="GROUPS") showGroupsMenu(); + else if (message=="<< BACK") loadGroup(llList2String(currentAn,0)); + else if (message=="QUIT") doQuit(); + else if (menu=="MENU_GROUPS") + { + if ((message=="< PREV") || (message=="NEXT >")) + { + if (message=="< PREV") groupPage-=6; + else groupPage+=6; + if (groupPage>=llGetListLength(groupList)) groupPage=0; + else if (groupPage<=-6) groupPage=llGetListLength(groupList)-6; + if (groupPage<0) groupPage=0; + showGroupsMenu(); + } + else loadGroup(message); + } + else if (menu=="MENU_ANIM") + { + if ((message=="< PREV") || (message=="NEXT >")) + { + if (message=="< PREV") anPage-=6; + else anPage+=6; + if (anPage>=llGetListLength(anList)) anPage=0; + else if (anPage<=-6) anPage=llGetListLength(anList)-6; + if (anPage<0) anPage=0; + showAnMenu(); + } + else + { + playAnimation(message); + showAnMenu(); + } + } + else if (menu=="MENU_ADD_NPC") + { + if (message=="CANCEL") showOptionsMenu(); + else if (llListFindList(positions,[NULL_KEY])==-1) + { + llRegionSayTo(user,0,"Cannot add a NPC as there are no longer any vacant positions"); + showOptionsMenu(); + } + else if ((message=="< PREV") || (message=="NEXT >")) + { + if (message=="< PREV") npcPage-=9; + else npcPage+=9; + if (npcPage>=llGetListLength(npcList)) npcPage=0; + else if (npcPage<=-9) npcPage=llGetListLength(npcList)-9; + if (npcPage<0) npcPage=0; + showAddNpcMenu(); + } + else + { + string npcToLoad=llList2String(invNpc,llListFindList(invNpc,[message])-2); + if (llGetInventoryType(npcToLoad)!=INVENTORY_NOTECARD) + { + llRegionSayTo(user,0,"ERROR! Unable to locate the appearance notecard for this NPC.\nButton was: "+message+"\nCard should be: "+npcToLoad); + startListening(); + return; + } + list thisNames=llParseString2List(message,[" "],[]); + if (llGetListLength(thisNames)==1) thisNames=[]+thisNames+["~"]; + key npc=osNpcCreate(llList2String(thisNames,0),llList2String(thisNames,1),llGetPos()+<0.0,0.0,2.0>,npcToLoad,OS_NPC_SENSE_AS_AGENT); + osNpcSit(npc,llGetKey(),OS_NPC_SIT_NOW); + showOptionsMenu(); + } + } + else if (menu=="MENU_SWAP_TO_POSITION") + { + if (message=="CANCEL") showOptionsMenu(); + else doSwapPositions(llListFindList(positions,[user]),((integer)message)-1); + showOptionsMenu(); + } + else if (menu=="MENU_UNSIT_POSITION") + { + if (message=="CANCEL") showOptionsMenu(); + else + { + integer posToUnsit=(integer)message-1; + key thisKey=llList2Key(positions,posToUnsit); + if (osIsNpc(thisKey)) + { + positions=[]+llListReplaceList(positions,[NULL_KEY],posToUnsit,posToUnsit); + osNpcRemove(thisKey); + } + else llUnSit(thisKey); + showOptionsMenu(); + } + } + else if (menu=="MENU_SELECT_AUTO_MODE") + { + if (message=="AUTO OFF") + { + autoOn=FALSE; + llSensorRemove(); + llRegionSayTo(user,0,"Auto mode now off"); + } + else if (message!="CANCEL") + { + autoOn=TRUE; + if(message!="AUTO ON") autoTimer=(float)message; + llSensorRepeat("THIS_WILL_NEVER_RETURN_A_SENSOR_RESULT",NULL_KEY,AGENT,0.001,0.0,autoTimer); + llRegionSayTo(user,0,"Auto mode is on and set to "+(string)llRound(autoTimer)+" seconds"); + } + showOptionsMenu(); + } + else if (menu=="MENU_SPECIALS") + { + if (message=="CANCEL") showOptionsMenu(); + else if ((message=="< PREV") || (message=="NEXT >")) + { + if (message=="< PREV") specPage-=18; + else specPage+=18; + if (specPage>=llGetListLength(specials)) specPage=0; + else if (specPage<=-18) specPage=llGetListLength(specials)-18; + if (specPage<0) specPage=0; + showSpecialsMenu(); + } + else llMessageLinked(LINK_THIS,0,llList2String(specials,llListFindList(specials,[message])+1)+"|"+user,llDumpList2String(positions,"|")); + } + else if (menu=="MENU_OPTIONS") + { + if (message=="AUTO") showAutoMenu(); + else if (message=="SPECIAL") showSpecialsMenu(); + else if (llSubStringIndex(message,"MENUS")==0) + { + ownerOnlyMenus=!ownerOnlyMenus; + if (ownerOnlyMenus) llRegionSayTo(who,0,"Menus are now locked. Only the owner can take control of me"); + else llRegionSayTo(who,0,"Menus are now unlocked. Any user can now take control of me"); + showOptionsMenu(); + } + else if (message=="EDIT ON") + { + if (llListFindList(positions,[NULL_KEY])>=0) + { + llRegionSayTo(who,0,"Sorry, all positions must be filled before you can enter edit mode and there is currently at least one that is empty."); + showOptionsMenu(); + } + else if (currentGroup!=llList2String(currentAn,0)) + { + llRegionSayTo(user,0,"Cannot enter edit mode because the animation currently playing is not from the same notecard as your currently loaded group. Either select an animation from this card or load the group that the current animation belongs to."); + showOptionsMenu(); + } + else + { + if (autoOn) + { + autoOn=FALSE; + llSensorRemove(); + llOwnerSay("Auto mode switched off"); + } + integer l=llGetListLength(positions); + while (--l>=0) { if (!osIsNpc(llList2Key(positions,l))) llRegionSayTo(llList2Key(positions,l),0,"The Owner has entered edit mode and all functions are temporarily disabled. Please do not stand while these adjustments are being made."); } + myState="EDIT"; + llMessageLinked(LINK_THIS,0,"GLOBAL_NOTICE_ENTERING_EDIT_MODE",llDumpList2String(positions,"|")); + editHandles=[]; + rezzingHandles=TRUE; + rezHandles(); + } + } + else if (message=="SWAP") + { + integer i=llGetListLength(positions); + if (i==1) + { + llRegionSayTo(user,0,"The current animation group is for only one position so cannot swap positions."); + showOptionsMenu(); + } + else if (i==2) + { + doSwapPositions(0,1); + showOptionsMenu(); + } + else + { + txtDia=""; + butDia=[]; + while (--i>=0) + { + key thisKey=llList2Key(positions,i); + if (thisKey==user) txtDia="\n"+(string)(i+1)+". (you are curently here)"+txtDia; + else + { + butDia=[]+[(string)(i+1)]+butDia; + if (thisKey==NULL_KEY) txtDia="\n"+(string)(i+1)+". (empty)"+txtDia; + else txtDia="\n"+(string)(i+1)+". "+llGetUsername(thisKey)+txtDia; + } + } + txtDia=""+"SWAP POSITION\n\nSelect the position number you would like to swap with\n"+txtDia; + while (llGetListLength(butDia)<8) { butDia=[]+butDia+["-"]; } + butDia=[]+butDia+["CANCEL"]; + while (llListFindList(butDia,["-","-","-"])>=0) { butDia=[]+llDeleteSubList(butDia,llListFindList(butDia,["-","-","-"]),llListFindList(butDia,["-","-","-"])+2); } + menu="MENU_SWAP_TO_POSITION"; + startListening(); + } + } + else if (message=="UNSIT") + { + list whoCanUnsit; + integer i=llGetListLength(positions); + while (--i>=0) + { + key thisKey=llList2Key(positions,i); + if ((thisKey!=user) && (thisKey!=llGetOwner()) && (thisKey!=NULL_KEY)) whoCanUnsit=[]+[i,thisKey]+whoCanUnsit; + } + integer l=llGetListLength(whoCanUnsit); + if (l==0) + { + llRegionSayTo(user,0,"There are no users you can remove"); + showOptionsMenu(); + } + else if (l==2) + { + integer thisInd=llList2Integer(whoCanUnsit,0); + key thisUser=llList2Key(whoCanUnsit,1); + if (osIsNpc(thisUser)) + { + positions=[]+llListReplaceList(positions,[NULL_KEY],thisInd,thisInd); + osNpcRemove(thisUser); + } + else llUnSit(thisUser); + showOptionsMenu(); + } + else + { + txtDia="UNSIT A USER\n\nSelect the user to unsit (selecting NPC it will remove it). The number is the position they currently occupy.\n"; + butDia=[]; + i=0; + while (i=0) { butDia=[]+llDeleteSubList(butDia,llListFindList(butDia,["-","-","-"]),llListFindList(butDia,["-","-","-"])+2); } + menu="MENU_UNSIT_POSITION"; + startListening(); + } + } + else if (message=="ADD NPC") + { + if (llListFindList(positions,[NULL_KEY])==-1) + { + llRegionSayTo(user,0,"Cannot add a new NPC at this time as there are no vacant positions"); + showOptionsMenu(); + } + else buildNpcList(); + } + else llOwnerSay("ERROR! Received unexpected message from Options menu: "+message); + } + else if (menu=="MENU_EDIT") + { + if (myState!="EDIT") + { + llOwnerSay("No longer in edit mode so unable to handle a response from that menu."); + showOptionsMenu(); + } + llSetTimerEvent(0.0); + integer valid=TRUE; + integer check=llGetListLength(positions); + while (valid && (--check>=0)) { if (!getUserLink(llList2Key(positions,check))) valid=FALSE; } + if (!valid) + { + llOwnerSay("ERROR! A user appears to have stood. Cannot remain in edit mode unless all positions are occupied. Ignoring your selection, reverting to stored position for this animation, and leaving edit mode without saving any changes. Once all positions are filled once more you may return to edit mode to resume work or save the stored data."); + playAnimation(llList2String(currentAn,1)); + removeHandles(); + return; + } + if (message=="STORE ADDON") llMessageLinked(LINK_THIS,0,"GLOBAL_STORE_ADDON_NOTICE",llDumpList2String(positions,"|")); + else if (message=="REVERT THIS") playAnimation(llList2String(currentAn,1)); + else + { + persistChanges(); + if (message=="EDIT OFF") removeHandles(); + else if ((message=="STORE THIS") || (message=="< PREV") || (message=="NEXT >")) + { + integer anToPlay=llListFindList(anList,[llList2String(currentAn,1)]); + if (message=="< PREV") anToPlay--; + else if (message=="NEXT >") anToPlay++; + if (anToPlay<0) anToPlay=llGetListLength(anList)-1; + if (anToPlay>=llGetListLength(anList)) anToPlay=0; + playAnimation(llList2String(anList,anToPlay)); + } + else if ((message=="SAVE CARD") || (message=="SAVE NEW")) + { + string cardName=llList2String(invGroups,llListFindList(invGroups,[currentGroup])-3); + string strToSay; + if (message=="SAVE NEW") + { + integer num=2; + while (llGetInventoryType(cardName+(string)num)==INVENTORY_NOTECARD) { num++; } + cardName=""+cardName+(string)num; + currentGroup=llGetSubString(cardName,10,-1); + invGroups=[]+invGroups+[cardName,(integer)(llGetSubString(cardName,7,7)),llGetSubString(cardName,8,8),currentGroup]; + groupList=[]+groupList+[currentGroup]; + currentAn=[]+llListReplaceList(currentAn,[currentGroup],0,0); + strToSay="All data stored to a new notecard and now working with the newly created group: "+currentGroup; + } + else strToSay="All data now updated in notecard for the group: "+currentGroup; + saveCard(cardName); + llRegionSayTo(user,0,strToSay); + } + else llOwnerSay("ERROR! Message not expected in Edit menu handling: "+message); + } + if (myState=="EDIT") + { + llSetTimerEvent(editTimer); + showEditMenu(); + } + } + } +} diff --git a/PMAC/PARAMOUR MULTI-ANIMATION CONTROLLER (PMAC)/Object/Presentation Add On.lsl b/PMAC/PARAMOUR MULTI-ANIMATION CONTROLLER (PMAC)/Object/Presentation Add On.lsl new file mode 100644 index 00000000..fab1b336 --- /dev/null +++ b/PMAC/PARAMOUR MULTI-ANIMATION CONTROLLER (PMAC)/Object/Presentation Add On.lsl @@ -0,0 +1,165 @@ +// :SHOW: +// :CATEGORY:NPC +// :NAME:PMAC +// :AUTHOR:Aine Caoimhe +// :KEYWORDS: +// :CREATED:2015-11-24 20:39:02 +// :EDITED:2015-11-24 19:39:02 +// :ID:1095 +// :NUM:1879 +// :REV:1.1 +// :WORLD:OpenSim +// :DESCRIPTION: +// PARAMOUR MULTI-ANIMATION CONTROLLER (PMAC) v1.02(OSSL) +// :CODE: + + + + +// by Aine Caoimhe January 2015 +// Provided under Creative Commons Attribution-Non-Commercial-ShareAlike 4.0 International license. +// Please be sure you read and adhere to the terms of this license: https://creativecommons.org/licenses/by-nc-sa/4.0/ +// +// rev 1.1 Added GIF capability - Ferd Frederix +// Images from the Gif2SL program can be used. These have the name Texture;X;Y;FPS. The script detects the X, Y and FPS +// numbers and animates the face of the prim to match +// Example: PMAC002;5;4;10 will animate a 5 X 4 GIF at 10 FPS + +integer display=1; // face for the main display +integer page; +integer showPageNum=FALSE; +vector floatyColour=<0.0, 1.0, 0.0>; +integer tCount; +integer init=TRUE; + +integer textStep; +string textToSay; +list said; + +sayText() +{ + if (textToSay=="") + { + llOwnerSay("Text finished. Next: slide "+(string)(page+1)); + return; + } + string thisText; + integer stop=llSubStringIndex(textToSay,"*DELAY*"); + if (stop>-1) + { + thisText=llGetSubString(textToSay,0,stop-1); + float delay=(float)llGetSubString(textToSay,stop+7,stop+11); + textToSay=""+llGetSubString(textToSay,stop+12,-1); + llSetTimerEvent(delay); + } + else + { + thisText=textToSay; + textToSay=""; + llSetTimerEvent(0.25); + } + llSay(0," \n"+thisText); +} + + +gif(integer face, string t) +{ + + list gif = llParseString2List(t,[";"],[]); + + + string aname = llList2String(gif,0); + + integer X = (integer) llList2String(gif,1); + integer Y = (integer) llList2String(gif,2); + float FPS = (float) llList2String(gif,3); + + float product = X * Y; + + // Set the new prim texture + llSetLinkPrimitiveParamsFast(LINK_THIS,[PRIM_TEXTURE,face,t,<1,1,0>,ZERO_VECTOR,0.0]); + + if (X && face == 1) { + llSetTextureAnim( ANIM_ON | LOOP, 1, X, Y, 0.0, product, FPS); + } else if (!X && face == 1) { + llSetTextureAnim( LOOP, 1, 1,1, 0.0, 1 , FPS); + } + +} + + +showPage() +{ + integer face; + while (face<4) + { + integer tInd=page-1+face; + if ((tInd>=0) && (tInd + + + + + + + + + + + + + + + + + + + + + + diff --git a/PMAC/PMAC.sol b/PMAC/PMAC.sol new file mode 100644 index 00000000..3b450ac0 --- /dev/null +++ b/PMAC/PMAC.sol @@ -0,0 +1,3 @@ + + + diff --git a/Paramour Builder's Buddy/Paramour Builder's Buddy.sol b/Paramour Builder's Buddy/Paramour Builder's Buddy.sol new file mode 100644 index 00000000..2ab1f531 --- /dev/null +++ b/Paramour Builder's Buddy/Paramour Builder's Buddy.sol @@ -0,0 +1,3 @@ + + + diff --git a/Paramour Builder's Buddy/Paramour Builder's Buddy/Paramour Builder's Buddy HUD/Paramour Builder's Buddy Instructions.txt b/Paramour Builder's Buddy/Paramour Builder's Buddy/Paramour Builder's Buddy HUD/Paramour Builder's Buddy Instructions.txt new file mode 100644 index 00000000..78626740 --- /dev/null +++ b/Paramour Builder's Buddy/Paramour Builder's Buddy/Paramour Builder's Buddy HUD/Paramour Builder's Buddy Instructions.txt @@ -0,0 +1,56 @@ +// :SHOW: +// :CATEGORY:Building +// :NAME:Paramour Builder's Buddy +// :AUTHOR:Aine Caoimhe +// :KEYWORDS: +// :CREATED:2015-11-24 20:37:09 +// :EDITED:2015-11-24 19:37:09 +// :ID:1092 +// :NUM:1866 +// :REV:1 +// :WORLD:OpenSim +// :DESCRIPTION: +// Paramour Builder's Buddy v1.0 +// :CODE: + +PARAMOUR BUILDER'S BUDDY v1.0 + +The Paramour Builder's Buddy is provided under Creative Commons Attribution-Non-Commercial-ShareAlike 4.0 International license. (https://creativecommons.org/licenses/by-nc-sa/4.0/) + +INSTRUCTIONS + + +This is a simple HUD tool that I find handy for working with prims where it's difficult to select their individual faces, although there are other possible applications for it. It isn't fancy but it's useful at times. + +REQUIRES OSSL +This tool requires access to the following OSSL functions: +- osIsUUID() used to confirm that a supplied UUID for a target is valid +- osGetPrimitiveParams() used to retrieve current texture data +- osSetPrimitiveParams() used to set new texture data + +How to use it: + +1. Wear the HUD +2. In edit more in your viewer, select the in-world object (your "target") that you want to texture and copy its UUID (copy keys button) +3. Touch the HUD's "Set Target" button...a text box dialog will pop up +4. Paste the key into the text box...you can only supply 1 key at a time +5. (Optional) If you want to retrieve the current texture data from that object, click the "Get Textures" button on the HUD +6. At the bottom of the HUD are 8 boxes, one for each possible face of the target (face 0,1,2,3 on the upper row, and 4,5,6,7 on the lower). A red X indicates the target doesn't have this face due to its shape +7. In edit mode on your viewer, switch into "select face" mode (the exact name of this varies depending on which viewer you use) +8. Change the texture on the corresponding face of the 8 HUD boxes. The settings it can transfer are: + - Texture + - Texture tiling + - Texture offset + - Texture rotation + - Colour + - Alpha + - Shiny setting (only "high", "medium", "low" or "none"...Opensim doesn't support setting advanced materials via script) + - Bump setting (again, only the old SL ones...Opensim doesn't support setting advanced materials via script) + - fullbright (unfortunately HUDs always display as fullbright but you can change the setting here and it will transfer it) + - glow (HUDs cannot display glow but it will transfer any value you set) +9. Click the "Set Textures" button to have them applied to your target + +At any time you can "Get Textures" again or set a new target +The "Reset" button clears the current target from memory and resets the HUD's textures just in case you have an accident and forget to switch to face select mode before doing texturing. +There are two textures in the HUD's inventory that are used for its display. Don't delete them :) + diff --git a/Paramour Builder's Buddy/Paramour Builder's Buddy/Paramour Builder's Buddy HUD/Paramour Builders Buddy.lsl b/Paramour Builder's Buddy/Paramour Builder's Buddy/Paramour Builder's Buddy HUD/Paramour Builders Buddy.lsl new file mode 100644 index 00000000..dc9a8e46 --- /dev/null +++ b/Paramour Builder's Buddy/Paramour Builder's Buddy/Paramour Builder's Buddy HUD/Paramour Builders Buddy.lsl @@ -0,0 +1,188 @@ +// :SHOW: +// :CATEGORY:Building +// :NAME:Paramour Builder's Buddy +// :AUTHOR:Aine Caoimhe +// :KEYWORDS: +// :CREATED:2015-11-24 20:37:09 +// :EDITED:2015-11-24 19:37:09 +// :ID:1092 +// :NUM:1867 +// :REV:1 +// :WORLD:OpenSim +// :DESCRIPTION: +// Paramour Builder's Buddy v1.0 +// :CODE: +// +// by Aine Caoimhe (LACM) Nov. 2015 +// Provided under Creative Commons Attribution-Non-Commercial-ShareAlike 4.0 International license. +// Please be sure you read and adhere to the terms of this license: https://creativecommons.org/licenses/by-nc-sa/4.0/ +// +// USER SETTINGS +// normally you shouldn't need to change these +key blankTexture="buddy_no_tex"; // builder's buddy face texture for no face...should be in the prim's inventory +string builderTexture="buddy"; // builder's buddy buttons texture name...should be in the prim's inventory +float timeOut=60.0; // how many seconds to wait for a target key to be supplied before closing the listener +// +// DON'T CHANGE ANYTHING BELOW HERE UNLESS YOU KNOW WHAT YOU'RE DOING! +integer tPrim=1; +integer bPrim=2; +key target; +list faces; +integer FACE_SET_TARGET=0; +integer FACE_RESET_FACES=1; +integer FACE_GET_TEXTURES=2; +integer FACE_SET_TEXTURES=3; +integer FACE_NONE=5; +integer myChannel; +integer handle; + +getTextures() +{ + if (!confirmTarget()) return; + integer f; + while (f<8) + { + if (llListFindList(faces,[f])>-1) + { + list data=osGetPrimitiveParams(target,[ + PRIM_TEXTURE,f, + PRIM_COLOR,f, + PRIM_BUMP_SHINY,f, + PRIM_FULLBRIGHT,f, + PRIM_GLOW,f + ]); + data=[]+[PRIM_TEXTURE,f]+llList2List(data,0,3)+[PRIM_COLOR,f]+llList2List(data,4,5)+[PRIM_BUMP_SHINY,f]+llList2List(data,6,7)+[PRIM_FULLBRIGHT,f,llList2Integer(data,8),PRIM_GLOW,f,llList2Float(data,9)]; + llSetLinkPrimitiveParamsFast(tPrim,data); + } + f++; + } + llOwnerSay("Textures retrieved"); +} +setTextures() +{ + if (!confirmTarget()) return; + integer l=llGetListLength(faces); + while (--l>-1) + { + integer f=llList2Integer(faces,l); + list data=llGetLinkPrimitiveParams(tPrim,[ + PRIM_TEXTURE,f, + PRIM_COLOR,f, + PRIM_BUMP_SHINY,f, + PRIM_FULLBRIGHT,f, + PRIM_GLOW,f + ]); + data=[]+[PRIM_TEXTURE,f]+llList2List(data,0,3)+[PRIM_COLOR,f]+llList2List(data,4,5)+[PRIM_BUMP_SHINY,f]+llList2List(data,6,7)+[PRIM_FULLBRIGHT,f,llList2Integer(data,8),PRIM_GLOW,f,llList2Float(data,9)]; + osSetPrimitiveParams(target,data); + } + llOwnerSay("Textures set"); +} +integer confirmTarget() +{ + if (llKey2Name(target)=="") + { + llOwnerSay("Cannot locate the target object with key "+(string)target+" in this region. Please set a new target"); + target=NULL_KEY; + return FALSE; + } + else return TRUE; +} +countFaces() +{ + faces=[]; + integer f; + while (f<8) + { + key tex=llList2Key(osGetPrimitiveParams(target,[PRIM_TEXTURE,f]),0); + if (tex!="") faces=[]+faces+[f]; + else llSetLinkPrimitiveParamsFast(tPrim,[ + PRIM_TEXTURE,f,blankTexture,<1,1,0>,ZERO_VECTOR,0.0, + PRIM_COLOR,ALL_SIDES,<1,1,1>,1, + PRIM_BUMP_SHINY,ALL_SIDES,PRIM_SHINY_NONE,PRIM_BUMP_NONE, + PRIM_FULLBRIGHT,ALL_SIDES,FALSE, + PRIM_GLOW,ALL_SIDES,0.0 + ]); + f++; + } + string targetName=llList2String(llGetObjectDetails(target,[OBJECT_NAME]),0); + llOwnerSay("Set target to the prim \""+targetName+"\" which has "+(string)llGetListLength(faces)+" faces"); +} +resetFaces() +{ + llSetLinkPrimitiveParamsFast(tPrim,[ + PRIM_TEXTURE,ALL_SIDES,blankTexture,<1,1,0>,ZERO_VECTOR,0.0, + PRIM_COLOR,ALL_SIDES,<1,1,1>,1, + PRIM_BUMP_SHINY,ALL_SIDES,PRIM_SHINY_NONE,PRIM_BUMP_NONE, + PRIM_FULLBRIGHT,ALL_SIDES,FALSE, + PRIM_GLOW,ALL_SIDES,0.0 + ]); + llSetLinkPrimitiveParamsFast(bPrim,[PRIM_TEXTURE,ALL_SIDES,builderTexture,<1,1,0>,<0,0,0>,0]); + target=NULL_KEY; +} +getTarget() +{ + myChannel=0x80000000 | (integer)("0x"+(string)llGetKey()); + handle=llListen(myChannel,"",llGetOwner(),""); + llTextBox(llGetOwner(),"Paste the key of your desired target object into this text box",myChannel); + llSetTimerEvent(timeOut); +} +default +{ + state_entry() + { + target=NULL_KEY; + } + timer() + { + llOwnerSay("I did not hear a response yet. Closing the listener"); + llListenRemove(handle); + llSetTimerEvent(0.0); + } + touch_start(integer num) + { + key toucher=llDetectedKey(0); + if (toucher!=llGetOwner()) return; + integer prim=llDetectedLinkNumber(0); + integer face=llDetectedTouchFace(0); + if (prim==tPrim) return; + else if (face==FACE_NONE) return; + else if (face==FACE_RESET_FACES) resetFaces(); + else if (target==NULL_KEY) + { + if ((prim==bPrim)&&(face==FACE_SET_TARGET)) getTarget(); + else llRegionSayTo(toucher,0,"No target set"); + return; + } + else if (face==FACE_GET_TEXTURES) getTextures(); + else if (face==FACE_SET_TEXTURES) setTextures(); + } + changed(integer change) + { + if (change & CHANGED_OWNER) llResetScript(); + else if (change & CHANGED_REGION_START) llResetScript(); + } + on_rez(integer foo) + { + llResetScript(); + } + listen(integer channel, string name, key who, string message) + { + llListenRemove(handle); + llSetTimerEvent(0.0); + message=""+llStringTrim(message,STRING_TRIM); + if (message!="") + { + if (osIsUUID(message)) + { + if (llGetAgentSize((key)message)==ZERO_VECTOR) + { + target=(key)message; + if (confirmTarget()) countFaces(); + } + else llOwnerSay("You can't use this on an avatar"); + } + else llOwnerSay("The key \""+message+"\" is not a valid UUID"); + } + else llOwnerSay("Can't set an empty string as the target"); + } +} diff --git a/Paramour Builder's Buddy/Paramour Builder's Buddy/Paramour Builder's Buddy HUD/Two prim HUD.jpg b/Paramour Builder's Buddy/Paramour Builder's Buddy/Paramour Builder's Buddy HUD/Two prim HUD.jpg new file mode 100644 index 00000000..85340102 Binary files /dev/null and b/Paramour Builder's Buddy/Paramour Builder's Buddy/Paramour Builder's Buddy HUD/Two prim HUD.jpg differ diff --git a/Paramour Builder's Buddy/Paramour Builder's Buddy/Paramour Builder's Buddy HUD/buddy.png b/Paramour Builder's Buddy/Paramour Builder's Buddy/Paramour Builder's Buddy HUD/buddy.png new file mode 100644 index 00000000..7691acaf Binary files /dev/null and b/Paramour Builder's Buddy/Paramour Builder's Buddy/Paramour Builder's Buddy HUD/buddy.png differ diff --git a/Paramour Builder's Buddy/Paramour Builder's Buddy/Paramour Builder's Buddy HUD/buddy_no_tex.png b/Paramour Builder's Buddy/Paramour Builder's Buddy/Paramour Builder's Buddy HUD/buddy_no_tex.png new file mode 100644 index 00000000..200668c4 Binary files /dev/null and b/Paramour Builder's Buddy/Paramour Builder's Buddy/Paramour Builder's Buddy HUD/buddy_no_tex.png differ diff --git a/Paramour Builder's Buddy/Paramour Builder's Buddy/Paramour Builder's Buddy.prj b/Paramour Builder's Buddy/Paramour Builder's Buddy/Paramour Builder's Buddy.prj new file mode 100644 index 00000000..41928fba --- /dev/null +++ b/Paramour Builder's Buddy/Paramour Builder's Buddy/Paramour Builder's Buddy.prj @@ -0,0 +1,12 @@ + + + + + + + + + + + diff --git a/Paramour Multi-Purpose NPC Rez & Pose/Paramour Multi-Purpose NPC Rez & Pose.sol b/Paramour Multi-Purpose NPC Rez & Pose/Paramour Multi-Purpose NPC Rez & Pose.sol new file mode 100644 index 00000000..9e10d831 --- /dev/null +++ b/Paramour Multi-Purpose NPC Rez & Pose/Paramour Multi-Purpose NPC Rez & Pose.sol @@ -0,0 +1,3 @@ + + + diff --git a/Paramour Multi-Purpose NPC Rez & Pose/Paramour Multi-Purpose NPC Rez & Pose/Object/Readme.txt b/Paramour Multi-Purpose NPC Rez & Pose/Paramour Multi-Purpose NPC Rez & Pose/Object/Readme.txt new file mode 100644 index 00000000..6f284ef9 --- /dev/null +++ b/Paramour Multi-Purpose NPC Rez & Pose/Paramour Multi-Purpose NPC Rez & Pose/Object/Readme.txt @@ -0,0 +1,98 @@ +// :SHOW: +// :CATEGORY:NPC +// :NAME:Paramour Multi-Purpose NPC Rez & Pose +// :AUTHOR:Aine Caoimhe +// :KEYWORDS: +// :CREATED:2015-11-24 20:38:24 +// :EDITED:2015-11-24 19:38:24 +// :ID:1093 +// :NUM:1868 +// :REV:1 +// :WORLD:Second Life +// :DESCRIPTION: +// NPC Controller +// :CODE: +Paramour Multi-Purpose NPC Rez & Pose +by Aine Caoimhe (Mata Hari)(c. LACM) April 2015 + +Provided under Creative Commons Attribution-Non-Commercial-ShareAlike 4.0 International license. +Please be sure you read and adhere to the terms of this license: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +INSTRUCTIONS + +This item is designed as a multi-purpose NPC utility for a wide variety of general uses. You can place the script in any object you want, then adjust its settings to whatever general application you have in mind. For more complex situations you may need to make additions or alterations to the script which you are free to do provided you adhere to the license. + +1. Either rez this object to ground or place the script into another object you'd like to use as your controller. If you wear it, it will be deactivated although if you have a bit of scripting knowledge you could alter the script to allow this but then you're responsible for making sure you don't rez a NPC that then attempts to sit on a worn object and then ends up stranded. + +2. If you want this script to handle basic animation for you, you'll need to add at least 1 animation to the object's inventory. If you prefer, you can have another script handle animations instead provided it's designed to work for a sitting NPC. Most basic furniture sit scripts can do this but more complicated systems may not (unless modified). + +3. If you want to use a NPC that you've already stored as an appearance notecard, you can simply place a copy of that notecard into the object's inventory and rename it to meet the name requirements of this script (see below). + +4. Now open the script and adjust the user settings, then save. Depending on your settings and whether you've supplied a NPC notecard, resetting the script will either rez a NPC or wait for you to touch it. + +5. If the object is controlling a NPC, touching the object will remove the NPC. If it isn't currently controlling a NPC, touching it will rez one. + +6. If you change the inventory (add/remove animations or notecards) you should reset the script as well to have the changes picked up. I don't do it automatically in case you wish to add/remove many items at once which can result in extremely long processing and is a PITA. + +NPC APPEARANCE NOTECARD NAMING + +This item is quite flexible but it's best to use the same naming format as the PMAC system where the name must be in the form: + .NPCxxp Firstname Lastname +- it must begin with .NPC (dot and then capital letters NPC) +- it is then immediately followed by two more letters or numbers that only affect its sort order in inventory. For use with this script they are optional +- then one final letter which is also optional for this script but in PMAC is used for permission check +- then a space +- then the first name to use for the NPC +- then a space +- then the last name to use for the NPC +- for this script if no names are supplied a generic name will be used + +So for this script you just need a card that starts with ".NPC" but it might be a good idea to get into the habit of using PMAC formatting for added convenience should use use that system at some point in the future. + +When this script creates an appearance notecard it is automatically named using the source avatar's name and is correctly formatted for PMAC with sort order 00 and permission set to A (all). + + +USER SETTINGS + +ownerOnly +can be set to either TRUE or FALSE +- if TRUE, only the owner can touch the object to rez/remove the NPC +- if FALSE, anyone can do so + +hideInUse +can be set to either TRUE or FALSE +- if TRUE, when the NPC is rezzed the prim containing the script will be set to alpha = 0 to hide it, and any floaty text will also be hidden. The object and floaty text will re-appear when touched. This is very handy when using this as an invisible poseball for NPCs to sit on...you just need to remember where you put it so you can locate it again to touch it while it's invisible. when made visible the alpha is set to 1.0 which might not be suitable for your object so you may need to change this value inside the script itself for custom applications == just search for "llSetLinkAlpha" and adjust as appropriate) +- if FALSE, the alpha of the object won't be changed and no text will be applied + +target +can be left at "" or you can supply the key (UUID) of any object in the region that you want the NPC to sit on when it is rezzed +- if no key is supplied, the object containing this script will be the object the NPC sits on +- if you supply a key but the object isn't in the region or isn't valid, it will revert to use the object containing this script instead +- if the object is more than 10m from the object containing this script, the NPC will first be teleported to the location of the object. There is no distance limit (provided the target remains in the region) + +animateNpc +can be set to either TRUE or FALSE +- if FALSE, the NPC will be rezzed and sit on the target and then it's assumed that another script will handle animations (if any) and this script's function will simply be to control rezzing and removing the NPC +- if TRUE, this script will handle basic animation of the NPC provided you place one or more animations in the script object's inventory (even if the NPC is sitting on a different target!). If more than 1 animation is supplied it will cycle through all available animations + +animationTimer +can be any value greater than 0.0 +This determines how often to change animation if there is more than 1 animation in inventory and if animateNpc is TRUE. If there is only a single animation (or you set animateNpc to FALSE) it is ignored + +randomAnim +can be set to either TRUE or FALSE +- if TRUE, the list of animations will play in random order. After each animation has played once, the list is re-shuffled so it's possible that the same animation could play twice in a row + +autoRez +can be set to TRUE or FALSE +- if TRUE and there's a NPC appearance notecard in inventory, the NPC will automatically rez and sit on the target when the script is reset or if the region restarts -- highly useful for populating your region with NPCs even if you aren't there when the region restarts +- if FALSE, or if there is no appearance notecard in inventory, the object will wait for you to touch it before rezzing the NPC + +storeToucher +can be set to TRUE or FALSE +- if TRUE and if the object does not already contain a NPC appearance notecard, the toucher will be asked whether they are willing to be cloned and have that appearance stored as a notecard in the prim's inventory. If they agree, the script will do so using the correct naming format. If they decline, the script will abort. +- if FALSE, and if there is no appearance notecard in inventory, the toucher will be cloned and rezzed directly to the NPC without a permission check but the appearance is NOT stored +- in either case, if there is one or more appearance notecard in inventory the first one found will be used instead + +floatyText and floatyTextColour +You can set text to display above the object or leave it blank ( "" ). The colour uses standard LSL vector values. diff --git a/Paramour Multi-Purpose NPC Rez & Pose/Paramour Multi-Purpose NPC Rez & Pose/Object/Script.lsl b/Paramour Multi-Purpose NPC Rez & Pose/Paramour Multi-Purpose NPC Rez & Pose/Object/Script.lsl new file mode 100644 index 00000000..40c3fd64 --- /dev/null +++ b/Paramour Multi-Purpose NPC Rez & Pose/Paramour Multi-Purpose NPC Rez & Pose/Object/Script.lsl @@ -0,0 +1,563 @@ +// :SHOW: +// :CATEGORY:NPC +// :NAME:Paramour Multi-Purpose NPC Rez & Pose +// :AUTHOR:Aine Caoimhe (Mata Hari) +// :KEYWORDS: +// :CREATED:2015-11-24 20:38:29 +// :EDITED:2015-11-24 19:38:29 +// :ID:1093 +// :NUM:1869 +// :REV:1 +// :WORLD:OpenSim +// :DESCRIPTION: +Paramour Multi-Purpose NPC Rez & Pose//:License: Creative Commons Attribution-Non-Commercial-ShareAlike 4.0 International license. +//CODE: +// Paramour Multi-Purpose NPC Rez & Pose +// by Aine Caoimhe (Mata Hari)(c. LACM) April 2015 +// Provided under Creative Commons Attribution-Non-Commercial-ShareAlike 4.0 International license. +// Please be sure you read and adhere to the terms of this license: https://creativecommons.org/licenses/by-nc-sa/4.0/ +// +// - Place this script in any object +// - Optionally set a different target for the NPC to sit on when rezzed +// - Optionally set the script to auto-rez an NPC if there is a notecard in inventory to use for the NPC's appearance (autorez will occur on script reset or region restart) +// - Optionally, add one or more animations for the NPC to use if controlling animations from this object. If more than one is found the NPC will cycle through them +// - Optionally, add one NPC notecard with the name ".NPCxxp Firstname Lastname" to the object. If more than one is found, the first one will be used. If none are +// found the toucher's appearance is used and (optionally) stored to the object +// The name conforms to the PMAC sytem NPC naming convensions where xx is used to sort the card order and p is the permission seting where A=all, G=group, and O=owner +// but these permission settings are ignored by this script nor is there any error-checking for them -- your card will work as long as it is 3 words where the first word +// begins with ".NPC" (dot then capital letters N P C) +// +// If you change the contents of the object (add/remove animations or notecards) you MUST reset the script to pick up those changes +// If the object containing the script is worn, it is disabled +// If the script is controlling a NPC, touching the object will remove the NPC +// If the script isn't controlling a NPC, touching the object will rez the NPC either from notecard or via cloning the toucher +// If a NPC is found standing on this object when reset, the script will assume control of that NPC. It can't do that for NPCs on other targets so remember +// to remove one before resetting this script. If you accidentally strand a NPC, use the Paramour NPC Manager to remove it. +// +// OSSL functions required: +// osIsUUID() +// osIsNpc() +// osNpcCreate() +// osNpcRemove() +// osTeleportAgent() if rezzing to a target that is more than 10m away from the object containing this script +// osAvatarPlayAnimation() if the option to animate the NPC is being used +// osAvatarStopAnimation() if the option to animate the NPC is being used +// osAgentSaveAppearance() if the option to store toucher appearance is being used +// +// USER SETTINGS +integer ownerOnly=TRUE; // TRUE = only owner can touch to rez/remove NPC; FALSE = anyone can +integer hideInUse=TRUEE; // TRUE = set this object to invisible when a NPC is being controlled by it (useful when using this as a hidden poseball though then you'll have to + // remember where you placed it); FALSE = don't hide this object. Note if this is part of a linkset only this prim is hidden +key target=1b600ed6-c416-457c-bae0-7f3397befa42""; // supply the key of the object you want the NPC to sit on or leave empty ("") to have the NPC sit on this object +integer animateNpc=FALSE; // TRUE = this object will handle animations if one or more are found in inventory; FALSE = another script will handle aniamtions +float animationTimer=60.0; // If this script is handling animations and more than one is found, how often to switch to the next animation (it also does a presense check) +integer randomAnim=TRUE; // If more than 1 animation is found in inventory, play animations in random order (list will be re-randomized after each has played once) +integer autoRez=TRUE; // TRUE = if there is an appearance notecard in inventory, auto-rez that NPC any time the region restarts or this script is reset; FALSE = only rez on touch +integer storeToucher=TRUE; // TRUE = if there is no appearance notecard in inventory, store the toucher's appearance when cloning them (subject to permission from the target) +string floatyText="Paramour Multi-Purpose NPC Rez & Pose"; // text to have floating above the object or supply an empty string ( "") for none +vector floatyTextColour=<1.000, 0.906, 0.502>; // LSL vector colour to use for the text (<0.0, 0.0, 0.0> = black, <1.0, 1.0, 1.0> = white) +// +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// DON'T CHANGE ANYTHING BELOW HERE UNLESS YOU KNOW WHAT YOU'RE DOING +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +list anims; +integer indAnim; +key npc=NULL_KEY; +string npcToRez; +string firstName; +string lastName; +integer myChannel; +integer handle; + +startAnimation() +{ + buildAnimList(); + if (llGetListLength(anims)==0) return; + string anToPlay=llList2String(anims,indAnim); + key dontStop=llGetInventoryKey(anToPlay); + osAvatarPlayAnimation(npc,anToPlay); + list anToStop=llGetAnimationList(npc); + integer stop=llGetListLength(anToStop); + while (--stop>=0) { if (llList2Key(anToStop,stop)!=dontStop) osAvatarStopAnimation(npc,llList2String(anToStop,stop)); } + if (llGetListLength(anims)>1) llSetTimerEvent(animationTimer); +} +doRezNpc(key clone, integer perm) +{ + if (npcToRez=="") + { + if (storeToucher) + { + if (!perm) + { + myChannel=0x80000000|(integer)("0x"+(string)llGetKey()); + handle=llListen(myChannel,"",clone,""); + llSensorRepeat("",clone,AGENT,32.0,PI,60.0); + llDialog(clone,"This object would like your permission to clone and store your appearance to a NPC notecard. Will you permit this?\nA no response will be assumed if you don't respond within 60 seoncds",["YES","NO","CANCEL"],myChannel); + return; + } + else + { + getNameData(clone); + osAgentSaveAppearance(clone,npcToRez); + llSleep(0.25); // give a little time to store the data and have it register to prim's inventory + } + } + else + { + getNameData(clone); + npcToRez=clone; + } + } + else if (llGetInventoryType(npcToRez)!=INVENTORY_NOTECARD) + { + llRegionSayTo(clone,0,"Unable to locate the expected appearance notecard in inventory. Perhaps you deleted it without resetting the script? Resetting the script now. Please wait a moment, then touch me again to resume"); + llResetScript(); + return; + } + npc=osNpcCreate(firstName,lastName,llGetPos()+<0.0,0.0,2.0>,npcToRez,OS_NPC_SENSE_AS_AGENT); + if (hideInUse) + { + llSetLinkAlpha(LINK_THIS,0.0,ALL_SIDES); + llSetText(floatyText,floatyTextColour,0.0); + } + vector targetPos=llGetPos(); + if (target=="") target=llGetKey(); + else if (target==NULL_KEY) target=llGetKey(); + else if (osIsUUID(target)) + { + list data=llGetObjectDetails(target,[OBJECT_POS]); + if (data==[]) + { + llRegionSayTo(clone,0,"Unable to find the target specified in the script in this region. Using this object as the target instead"); + target=llGetKey(); + } + else targetPos=llList2Vector(data,0); + } + else + { + llRegionSayTo(clone,0,"Your target does not appear to be a valid key. Using this object as the target instead"); + target=llGetKey(); + } + if (llVecDist(targetPos,llGetPos())>10.0) osTeleportAgent(npc,targetPos+<0.0,0.0,2.0>,ZERO_VECTOR); + osNpcSit(npc,target,OS_NPC_SIT_NOW); + if (animateNpc) + { + llSleep(0.25); // have to wait for the LSL sit animation to register to the npc's animation list + startAnimation(); + } +} +getNameData(key name) +{ + list nameParsed=llParseString2List(llKey2Name(name),["."," "],[]); + firstName=llList2String(nameParsed,0); + lastName=llList2String(nameParsed,1); + if (firstName=="") firstName="Noname"; + if (lastName=="") lastName="NPC"; + npcToRez=".NPC00A "+firstName+" "+lastName; +} +buildAnimList() +{ + anims=[]; + integer i=llGetInventoryNumber(INVENTORY_ANIMATION); + while (--i>=0){ anims=[]+[llGetInventoryName(INVENTORY_ANIMATION,i)]+anims; } + if (randomAnim) anims=[]+llListRandomize(anims,1); + indAnim=0; + if (llGetListLength(anims)==0) llOwnerSay("WARNING! Script is set to handle animations but none could be found in inventory. NPC will play stock SL sit animation"); +} +findNpcToRez() +{ + npcToRez=""; + firstName=""; + lastName=""; + integer i; + while ((npcToRez=="") && (illGetListLength(anims)) + { + indAnim=0; + if (randomAnim) anims=[]+llListRandomize(anims,1); + } + string animToStart=llList2String(anims,indAnim); + if (animToStart!=animToStop) + { + osAvatarPlayAnimation(npc,animToStart); + osAvatarStopAnimation(npc,animToStop); + } + } + touch_start(integer num) + { + if (llGetAttached()) return; + key toucher=llDetectedKey(0); + if (ownerOnly && (toucher!=llGetOwner())) + { + llRegionSayTo(toucher,0,"Sorry, you don't have permission to use this"); + return; + } + if (npc==NULL_KEY) doRezNpc(toucher,FALSE); + else if (llGetAgentSize(npc)==ZERO_VECTOR) + { + npc=NULL_KEY; + llSetTimerEvent(0.0); + doRezNpc(toucher,FALSE); + } + else + { + osNpcRemove(npc); + npc=NULL_KEY; + llSetTimerEvent(0.0); + llRegionSayTo(toucher,0,"NPC removed"); + if (hideInUse) + { + llSetLinkAlpha(LINK_THIS,1.0,ALL_SIDES); + llSetText(floatyText,floatyTextColour,1.0); + } + llResetScript(); + } + } +} +// :CODE: +//:AUTHOR: Aine Caoimhe (Mata Hari) +//:DESCRIPTION:Paramour Multi-Purpose NPC Rez & Pose +//:License: Creative Commons Attribution-Non-Commercial-ShareAlike 4.0 International license. +//CODE: +// Paramour Multi-Purpose NPC Rez & Pose +// by Aine Caoimhe (Mata Hari)(c. LACM) April 2015 +// Provided under Creative Commons Attribution-Non-Commercial-ShareAlike 4.0 International license. +// Please be sure you read and adhere to the terms of this license: https://creativecommons.org/licenses/by-nc-sa/4.0/ +// +// - Place this script in any object +// - Optionally set a different target for the NPC to sit on when rezzed +// - Optionally set the script to auto-rez an NPC if there is a notecard in inventory to use for the NPC's appearance (autorez will occur on script reset or region restart) +// - Optionally, add one or more animations for the NPC to use if controlling animations from this object. If more than one is found the NPC will cycle through them +// - Optionally, add one NPC notecard with the name ".NPCxxp Firstname Lastname" to the object. If more than one is found, the first one will be used. If none are +// found the toucher's appearance is used and (optionally) stored to the object +// The name conforms to the PMAC sytem NPC naming convensions where xx is used to sort the card order and p is the permission seting where A=all, G=group, and O=owner +// but these permission settings are ignored by this script nor is there any error-checking for them -- your card will work as long as it is 3 words where the first word +// begins with ".NPC" (dot then capital letters N P C) +// +// If you change the contents of the object (add/remove animations or notecards) you MUST reset the script to pick up those changes +// If the object containing the script is worn, it is disabled +// If the script is controlling a NPC, touching the object will remove the NPC +// If the script isn't controlling a NPC, touching the object will rez the NPC either from notecard or via cloning the toucher +// If a NPC is found standing on this object when reset, the script will assume control of that NPC. It can't do that for NPCs on other targets so remember +// to remove one before resetting this script. If you accidentally strand a NPC, use the Paramour NPC Manager to remove it. +// +// OSSL functions required: +// osIsUUID() +// osIsNpc() +// osNpcCreate() +// osNpcRemove() +// osTeleportAgent() if rezzing to a target that is more than 10m away from the object containing this script +// osAvatarPlayAnimation() if the option to animate the NPC is being used +// osAvatarStopAnimation() if the option to animate the NPC is being used +// osAgentSaveAppearance() if the option to store toucher appearance is being used +// +// USER SETTINGS +integer ownerOnly=TRUE; // TRUE = only owner can touch to rez/remove NPC; FALSE = anyone can +integer hideInUse=TRUEE; // TRUE = set this object to invisible when a NPC is being controlled by it (useful when using this as a hidden poseball though then you'll have to + // remember where you placed it); FALSE = don't hide this object. Note if this is part of a linkset only this prim is hidden +key target=1b600ed6-c416-457c-bae0-7f3397befa42""; // supply the key of the object you want the NPC to sit on or leave empty ("") to have the NPC sit on this object +integer animateNpc=FALSE; // TRUE = this object will handle animations if one or more are found in inventory; FALSE = another script will handle aniamtions +float animationTimer=60.0; // If this script is handling animations and more than one is found, how often to switch to the next animation (it also does a presense check) +integer randomAnim=TRUE; // If more than 1 animation is found in inventory, play animations in random order (list will be re-randomized after each has played once) +integer autoRez=TRUE; // TRUE = if there is an appearance notecard in inventory, auto-rez that NPC any time the region restarts or this script is reset; FALSE = only rez on touch +integer storeToucher=TRUE; // TRUE = if there is no appearance notecard in inventory, store the toucher's appearance when cloning them (subject to permission from the target) +string floatyText="Paramour Multi-Purpose NPC Rez & Pose"; // text to have floating above the object or supply an empty string ( "") for none +vector floatyTextColour=<1.000, 0.906, 0.502>; // LSL vector colour to use for the text (<0.0, 0.0, 0.0> = black, <1.0, 1.0, 1.0> = white) +// +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// DON'T CHANGE ANYTHING BELOW HERE UNLESS YOU KNOW WHAT YOU'RE DOING +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +list anims; +integer indAnim; +key npc=NULL_KEY; +string npcToRez; +string firstName; +string lastName; +integer myChannel; +integer handle; + +startAnimation() +{ + buildAnimList(); + if (llGetListLength(anims)==0) return; + string anToPlay=llList2String(anims,indAnim); + key dontStop=llGetInventoryKey(anToPlay); + osAvatarPlayAnimation(npc,anToPlay); + list anToStop=llGetAnimationList(npc); + integer stop=llGetListLength(anToStop); + while (--stop>=0) { if (llList2Key(anToStop,stop)!=dontStop) osAvatarStopAnimation(npc,llList2String(anToStop,stop)); } + if (llGetListLength(anims)>1) llSetTimerEvent(animationTimer); +} +doRezNpc(key clone, integer perm) +{ + if (npcToRez=="") + { + if (storeToucher) + { + if (!perm) + { + myChannel=0x80000000|(integer)("0x"+(string)llGetKey()); + handle=llListen(myChannel,"",clone,""); + llSensorRepeat("",clone,AGENT,32.0,PI,60.0); + llDialog(clone,"This object would like your permission to clone and store your appearance to a NPC notecard. Will you permit this?\nA no response will be assumed if you don't respond within 60 seoncds",["YES","NO","CANCEL"],myChannel); + return; + } + else + { + getNameData(clone); + osAgentSaveAppearance(clone,npcToRez); + llSleep(0.25); // give a little time to store the data and have it register to prim's inventory + } + } + else + { + getNameData(clone); + npcToRez=clone; + } + } + else if (llGetInventoryType(npcToRez)!=INVENTORY_NOTECARD) + { + llRegionSayTo(clone,0,"Unable to locate the expected appearance notecard in inventory. Perhaps you deleted it without resetting the script? Resetting the script now. Please wait a moment, then touch me again to resume"); + llResetScript(); + return; + } + npc=osNpcCreate(firstName,lastName,llGetPos()+<0.0,0.0,2.0>,npcToRez,OS_NPC_SENSE_AS_AGENT); + if (hideInUse) + { + llSetLinkAlpha(LINK_THIS,0.0,ALL_SIDES); + llSetText(floatyText,floatyTextColour,0.0); + } + vector targetPos=llGetPos(); + if (target=="") target=llGetKey(); + else if (target==NULL_KEY) target=llGetKey(); + else if (osIsUUID(target)) + { + list data=llGetObjectDetails(target,[OBJECT_POS]); + if (data==[]) + { + llRegionSayTo(clone,0,"Unable to find the target specified in the script in this region. Using this object as the target instead"); + target=llGetKey(); + } + else targetPos=llList2Vector(data,0); + } + else + { + llRegionSayTo(clone,0,"Your target does not appear to be a valid key. Using this object as the target instead"); + target=llGetKey(); + } + if (llVecDist(targetPos,llGetPos())>10.0) osTeleportAgent(npc,targetPos+<0.0,0.0,2.0>,ZERO_VECTOR); + osNpcSit(npc,target,OS_NPC_SIT_NOW); + if (animateNpc) + { + llSleep(0.25); // have to wait for the LSL sit animation to register to the npc's animation list + startAnimation(); + } +} +getNameData(key name) +{ + list nameParsed=llParseString2List(llKey2Name(name),["."," "],[]); + firstName=llList2String(nameParsed,0); + lastName=llList2String(nameParsed,1); + if (firstName=="") firstName="Noname"; + if (lastName=="") lastName="NPC"; + npcToRez=".NPC00A "+firstName+" "+lastName; +} +buildAnimList() +{ + anims=[]; + integer i=llGetInventoryNumber(INVENTORY_ANIMATION); + while (--i>=0){ anims=[]+[llGetInventoryName(INVENTORY_ANIMATION,i)]+anims; } + if (randomAnim) anims=[]+llListRandomize(anims,1); + indAnim=0; + if (llGetListLength(anims)==0) llOwnerSay("WARNING! Script is set to handle animations but none could be found in inventory. NPC will play stock SL sit animation"); +} +findNpcToRez() +{ + npcToRez=""; + firstName=""; + lastName=""; + integer i; + while ((npcToRez=="") && (illGetListLength(anims)) + { + indAnim=0; + if (randomAnim) anims=[]+llListRandomize(anims,1); + } + string animToStart=llList2String(anims,indAnim); + if (animToStart!=animToStop) + { + osAvatarPlayAnimation(npc,animToStart); + osAvatarStopAnimation(npc,animToStop); + } + } + touch_start(integer num) + { + if (llGetAttached()) return; + key toucher=llDetectedKey(0); + if (ownerOnly && (toucher!=llGetOwner())) + { + llRegionSayTo(toucher,0,"Sorry, you don't have permission to use this"); + return; + } + if (npc==NULL_KEY) doRezNpc(toucher,FALSE); + else if (llGetAgentSize(npc)==ZERO_VECTOR) + { + npc=NULL_KEY; + llSetTimerEvent(0.0); + doRezNpc(toucher,FALSE); + } + else + { + osNpcRemove(npc); + npc=NULL_KEY; + llSetTimerEvent(0.0); + llRegionSayTo(toucher,0,"NPC removed"); + if (hideInUse) + { + llSetLinkAlpha(LINK_THIS,1.0,ALL_SIDES); + llSetText(floatyText,floatyTextColour,1.0); + } + llResetScript(); + } + } +} diff --git a/Paramour Multi-Purpose NPC Rez & Pose/Paramour Multi-Purpose NPC Rez & Pose/Paramour Multi-Purpose NPC Rez & Pose.prj b/Paramour Multi-Purpose NPC Rez & Pose/Paramour Multi-Purpose NPC Rez & Pose/Paramour Multi-Purpose NPC Rez & Pose.prj new file mode 100644 index 00000000..c20ae5fd --- /dev/null +++ b/Paramour Multi-Purpose NPC Rez & Pose/Paramour Multi-Purpose NPC Rez & Pose/Paramour Multi-Purpose NPC Rez & Pose.prj @@ -0,0 +1,8 @@ + + + + + + + diff --git a/Pendulum/Pendulum.sol b/Pendulum/Pendulum.sol new file mode 100644 index 00000000..e3c9a22d --- /dev/null +++ b/Pendulum/Pendulum.sol @@ -0,0 +1,3 @@ + + + diff --git a/Pendulum/Pendulum/Object/Pendulum script.lsl b/Pendulum/Pendulum/Object/Pendulum script.lsl new file mode 100644 index 00000000..f8456f80 --- /dev/null +++ b/Pendulum/Pendulum/Object/Pendulum script.lsl @@ -0,0 +1,76 @@ +// :SHOW: +// :CATEGORY:Pendulum +// :NAME:Pendulum +// :AUTHOR:Dora Gustafson, Studio Dora +// :KEYWORDS: +// :CREATED:2015-11-24 20:38:39 +// :EDITED:2015-11-24 19:38:39 +// :ID:1094 +// :NUM:1870 +// :REV:1 +// :WORLD:Second Life +// :DESCRIPTION: +// Will swing a prim like a simple pendulum pivoting at an axis parallel to the prim's Y-axis +// :CODE: +// Pendulum motion by Dora Gustafson, Studio Dora 2012 +// Will swing a prim like a simple pendulum pivoting at an axis parallel to the prim's Y-axis +// The pivot axis will be at the top of a prim with the Z-axis pointing up +// Quote from http://en.wikipedia.org/wiki/Pendulum_(mathematics) +// • A simple pendulum is an idealization of a real pendulum using the following assumptions: +// • The rod or cord on which the bob swings is massless, inextensible and always remains taut; +// • Motion occurs only in two dimensions, i.e. the bob does not trace an ellipse but an arc. +// • The motion does not lose energy to friction or air resistance. +// The periode time increase with the Z dimension (the pendulum length)... +// If it is too small the motion will not be well because of the time limitation with Key Framed Motions +// The parameters set in the script works nice with a 3m long pendulum +// If the pendulum is moved, rotated or resized the script must be reset to update the motion + +float angle=1.0; // max swing from resting (radians) +float steps=12.0; // number of Key Frames +float step=0.0; +list KFMlist=[]; +vector U; +vector V; +float angleU=0.0; +float angleV; +integer swing=TRUE; +vector basePos; +rotation baseRot; + +default +{ + state_entry() + { + llSetMemoryLimit( llGetUsedMemory()+0x1000); + llSetPrimitiveParams([PRIM_PHYSICS_SHAPE_TYPE, PRIM_PHYSICS_SHAPE_CONVEX]); + basePos = llGetPos(); + baseRot = llGetRot(); + vector v1 = llGetScale(); + float periode = TWO_PI*llSqrt( v1.z/9.81); + float dT = periode/steps; + dT = llRound(45.0*dT)/45.0; + if ( dT < 0.11111111 ) dT = 0.11111111; + v1.x = 0.0; + v1.y = 0.0; + v1 = -0.5*v1*llGetRot(); + U = v1; + while ( step < steps ) + { + step += 1.0; + angleV = angle*llCos( TWO_PI*step/steps + PI_BY_TWO); + V = v1*llAxisAngle2Rot(llRot2Fwd(llGetRot()), angleV); + KFMlist += [V-U, llEuler2Rot(< angleV-angleU, 0.0, 0.0>), dT]; + angleU = angleV; + U = V; + } + } + touch_start( integer n) + { + llSetKeyframedMotion( [], []); + llSleep(0.2); + llSetPrimitiveParams([PRIM_POSITION, basePos, PRIM_ROTATION, baseRot]); + if ( swing ) llSetKeyframedMotion( KFMlist, [ KFM_MODE, KFM_LOOP]); + swing = !swing; + } + on_rez( integer n) { llResetScript(); } +} diff --git a/Pendulum/Pendulum/Pendulum.prj b/Pendulum/Pendulum/Pendulum.prj new file mode 100644 index 00000000..2b92593b --- /dev/null +++ b/Pendulum/Pendulum/Pendulum.prj @@ -0,0 +1,6 @@ + + + + + diff --git a/Phaze TipJar/Phaze TipJar.sol b/Phaze TipJar/Phaze TipJar.sol new file mode 100644 index 00000000..0ab21c06 --- /dev/null +++ b/Phaze TipJar/Phaze TipJar.sol @@ -0,0 +1,3 @@ + + + diff --git a/Phaze TipJar/Phaze TipJar/Base/Butterflies.lsl b/Phaze TipJar/Phaze TipJar/Base/Butterflies.lsl new file mode 100644 index 00000000..0fb1c03e --- /dev/null +++ b/Phaze TipJar/Phaze TipJar/Base/Butterflies.lsl @@ -0,0 +1,168 @@ +// Particle Script 0.4j +// Created by Ama Omega 3-7-2004 +// Updated by Jopsy Pendragon 5-11-2004 +// For classes/tutorials/tricks, visit the Particle Labratory in Teal +// Values marked with (*) are defaults. + +integer flagger = 0; + +// SECTION ONE: APPEARANCE -- These settings affect how each particle LOOKS. +integer glow = TRUE; // TRUE or FALSE(*) +vector startColor = <1,1,1>; // RGB color, black<0,0,0> to white<1,1,1>(*) +vector endColor = <1,1,1>; // +float startAlpha = 1.0; // 0.0 to 1.0(*), lower = more transparent +float endAlpha = 1.0; // +vector startSize = <0.0,0.0,0>; // <0.04,0.04,0>(min) to <10,10,0>(max>, <1,1,0>(*) +vector endSize = <0.05,0.05,0>; // (Z part of vector is discarded) +string texture = "44c04376-637b-ef18-9404-723acd0fce91"; // Texture used for particles. Texture must be in prim's inventory. + +// SECTION TWO: FLOW -- These settings affect how Many, how Quickly, and for how Long particles are created. +// Note, +integer count = 1; // Number of particles created per burst, 1(*) to 4096 +float rate = 1; // Delay between bursts of new particles, 0.0 to 60, 0.1(*) +float age = 5.0; // How long each particle lives, 0.1 to 60, 10.0(*) +float life = 0.0; // When to stop creating new particles. never stops if 0.0(*) + +// SECTION THREE: PLACEMENT -- Where are new particles created, and what direction are they facing? +float radius = 0.15; // 0.0(default) to 64? Distance from Emitter where new particles are created. +float innerAngle = .1 ; // "spread", for all ANGLE patterns, 0(default) to PI +float outerAngle = .1; // "tilt", for ANGLE patterns, 0(default) to TWO_PI, can use PI_BY_TWO or PI as well. +integer pattern = PSYS_SRC_PATTERN_ANGLE_CONE; // Choose one of the following: + // PSYS_SRC_PATTERN_EXPLODE (sends particles in all directions) + // PSYS_SRC_PATTERN_DROP (ignores minSpeed and maxSpeed. Don't bother with count>1 ) + // PSYS_SRC_PATTERN_ANGLE_CONE (set innerangle/outerange to make rings/cones of particles) + // PSYS_SRC_PATTERN_ANGLE (set innerangle/outerangle to make flat fanshapes of particles) +vector omega = <0,0,0>; // How much to rotate the emitter around the axises. <0,0,0>(*) + // Warning, there's no way to RESET the emitter direction once you use Omega!! + // You must attach the script to a new prim to clear the effect of omega. + +// SECTION FOUR: MOVEMENT -- How do the particles move once they're created? +integer followSource = FALSE; // TRUE or FALSE(*), Particles move as the emitter moves, (TRUE disables radius!) +integer followVel = TRUE; // TRUE or FALSE(*), Particles rotate towards their direction +integer wind = FALSE; // TRUE or FALSE(*), Particles get blown away by wind in the sim +integer bounce = FALSE; // TRUE or FALSE(*), Make particles bounce on Z altitude of emitter +float minSpeed = 0.04; // 0.01 to ? Min speed each particle is spit out at, 1.0(*) +float maxSpeed = 0.08; // 0.01 to ? Max speed each particle is spit out at, 1.0(*) +vector push = <0,0,-0.03>; // Continuous force pushed on particles, use small settings for long lived particles +key target = ""; // Select a target for particles to arrive at when they die + // can be "self" (emitter), "owner" (you), "" or any prim/persons KEY. + +// SECTION FIVE: Ama's "Create Short Particle Settings List" +integer enableoutput = FALSE; // If this is TRUE, clicking on your emitter prim will cause it to speak + // very terse "shorthand" version of your particle settings. You can cut'n'paste + // this abbreviated version into a call to llParticleSystem([ ]); in another script. + // Pros: Takes up far less scripting space, letting you focus on the rest of your code. + // Cons: makes tune your settings afterwards rather awkward + +// === Don't muck about below this line unless you're comfortable with the LSL scripting language ==== + +// Script variables +integer pre = 2; //Adjust the precision of the generated list. +integer flags; +list sys; +integer type; +vector tempVector; +rotation tempRot; +string tempString; +integer i; + + +Play() +{ + //llOwnerSay("Touched"); + count = 10; + age = 30; + //minSpeed = 1.0; + maxSpeed = 0.5 ; + endSize = <1.5,1.5,0>; // (Z part of vector is discarded) + float x = llFrand(0.1); + float y = llFrand(0.1); + push = ; // Continuous force pushed on particles + wind = TRUE; + updateParticles(); + llSleep(8); + reset(); + updateParticles(); +} + + + +reset() +{ + + count = 1; // Number of particles created per burst, 1(*) to 4096 + age = 5.0; // How long each particle lives, 0.1 to 60, 10.0(*) + maxSpeed = 0.08; // speed issued at + endSize = <0.05,0.05,0>; // (Z part of vector is discarded) + push = <0,0,-0.03>; // Continuous force pushed on particles + wind = FALSE; + + +} + +updateParticles() +{ + flags = 0; + if (target == "owner") target = llGetOwner(); + if (target == "self") target = llGetKey(); + if (glow) flags = flags | PSYS_PART_EMISSIVE_MASK; + if (bounce) flags = flags | PSYS_PART_BOUNCE_MASK; + if (startColor != endColor) flags = flags | PSYS_PART_INTERP_COLOR_MASK; + if (startSize != endSize) flags = flags | PSYS_PART_INTERP_SCALE_MASK; + if (wind) flags = flags | PSYS_PART_WIND_MASK; + if (followSource) flags = flags | PSYS_PART_FOLLOW_SRC_MASK; + if (followVel) flags = flags | PSYS_PART_FOLLOW_VELOCITY_MASK; + if (target != "") flags = flags | PSYS_PART_TARGET_POS_MASK; + sys = [ PSYS_PART_MAX_AGE,age, + PSYS_PART_FLAGS,flags, + PSYS_PART_START_COLOR, startColor, + PSYS_PART_END_COLOR, endColor, + PSYS_PART_START_SCALE,startSize, + PSYS_PART_END_SCALE,endSize, + PSYS_SRC_PATTERN, pattern, + PSYS_SRC_BURST_RATE,rate, + PSYS_SRC_ACCEL, push, + PSYS_SRC_BURST_PART_COUNT,count, + PSYS_SRC_BURST_RADIUS,radius, + PSYS_SRC_BURST_SPEED_MIN,minSpeed, + PSYS_SRC_BURST_SPEED_MAX,maxSpeed, + PSYS_SRC_TARGET_KEY,target, + PSYS_SRC_INNERANGLE,innerAngle, + PSYS_SRC_OUTERANGLE,outerAngle, + PSYS_SRC_OMEGA, omega, + PSYS_SRC_MAX_AGE, life, + PSYS_SRC_TEXTURE, texture, + PSYS_PART_START_ALPHA, startAlpha, + PSYS_PART_END_ALPHA, endAlpha + ]; + float newrate = rate; + if (newrate == 0.0) newrate=.01; + if ( (age/rate)*count < 4096) llParticleSystem(sys); + else { + llInstantMessage(llGetOwner(),"Your particle system creates too many concurrent particles."); + llInstantMessage(llGetOwner(),"Reduce count or age, or increate rate."); + llParticleSystem( [ ] ); + } + +} + +default +{ + state_entry() + { + reset(); + updateParticles(); + } + + touch_start(integer total_number) + { + llSay(0,"Butterfly Tip Jar for 'Phase Demesnes', pay it to pop!"); + } + + link_message(integer sender_num, integer num, string msg, key id) + { + Play(); + } + + +} \ No newline at end of file diff --git a/Phaze TipJar/Phaze TipJar/Phaze TipJar.prj b/Phaze TipJar/Phaze TipJar/Phaze TipJar.prj new file mode 100644 index 00000000..2582a26b --- /dev/null +++ b/Phaze TipJar/Phaze TipJar/Phaze TipJar.prj @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/Phaze TipJar/Phaze TipJar/Top Half sphere/Script.lsl b/Phaze TipJar/Phaze TipJar/Top Half sphere/Script.lsl new file mode 100644 index 00000000..507c0e8f --- /dev/null +++ b/Phaze TipJar/Phaze TipJar/Top Half sphere/Script.lsl @@ -0,0 +1,66 @@ +//Keknehv Psaltery Updated Version of DONATION BOX By jean cook, ama omega, and nada epoch Debugged by YadNi Monde. +// changed into a butterfly dispenser by Ferd Frederix +// (LoL) Yea, that s a Bunch O Peeps =) + +//Summary: The following script will make an object accept donations on your behalf. +//Usage: stick it on any object you own(my favorite is a top hat), and it will promptly display: +//"'s donation hat. +//Donate if you are so inclined." +//at which point anyone can right click on it and give you a tip. also, the script tells the donator thanks, and then tells you who donated how much +//also shows the total amount donated + + + +integer totaldonated = 103793; +string owner; + +default +{ + on_rez( integer sparam ) + { + llResetScript(); + } + state_entry() + { + llSetPayPrice(PAY_HIDE, [50 ,100, 200, 500]); + owner = llKey2Name( llGetOwner() ); + llSetText( owner + "'s Tip Jar.\nRelease the butterflies!",<.25,1,.65>,1); + } + + money(key id, integer amount) + { + + totaldonated += amount; + string name= llKey2Name(id); + llSetText( owner + "'s Tip Jar.\nRelease the butterflies!\n \nLast Donor:\n " + name + " gave $L" + (string)amount ,<.25,1,.65>,1); + //llInstantMessage(id,"Thanks for the tip! I really appreciate it. ~ Ferd Frederix"); + + if (amount == 1) + llSay(0,"Uhh, thanks I think, " + name ); + + if (amount > 0) + llSay(0,"Thanks for the tip, " + name +"! I really appreciate it. ~ Ferd Frederix"); + else if (amount > 100) + llSay(0,"Thanks for the awesome tip, " + name +"! I appreciate it. ~ Ferd Frederix"); + else if (amount > 300) + llSay(0,"Thanks for the AWESOME tip, " + name +"! I really appreciate it. ~ Ferd Frederix"); + + llInstantMessage(llGetOwner(),(string)llKey2Name(id)+" donated $" + (string)amount); + + llMessageLinked(LINK_ALL_OTHERS, 0, "touched", ""); + llPlaySound("pop",1.0); + llSetAlpha(0,ALL_SIDES); + llSetTimerEvent(10.0); + llEmail("fred@mitsi.com",(string)llKey2Name(id)+" donated $" + (string)amount,""); + } + touch_start(integer total_number) + { + llWhisper(0,"Butterfly Tip Jar for 'Phase Demesnes', total donated so far: " + (string) totaldonated ); + } + + timer() + { + llSetAlpha(0.1,ALL_SIDES); + llSetTimerEvent(0.0); + } +} \ No newline at end of file diff --git a/Phaze TipJar/Phaze TipJar/Top Half sphere/pop b/Phaze TipJar/Phaze TipJar/Top Half sphere/pop new file mode 100644 index 00000000..e69de29b diff --git a/Presentation Board with GIF capability/Presentation Board with GIF capability.sol b/Presentation Board with GIF capability/Presentation Board with GIF capability.sol new file mode 100644 index 00000000..de44320a --- /dev/null +++ b/Presentation Board with GIF capability/Presentation Board with GIF capability.sol @@ -0,0 +1,3 @@ + + + diff --git a/Presentation Board with GIF capability/Presentation Board with GIF capability/Object/Dragon-10-june.gif b/Presentation Board with GIF capability/Presentation Board with GIF capability/Object/Dragon-10-june.gif new file mode 100644 index 00000000..ea5df973 Binary files /dev/null and b/Presentation Board with GIF capability/Presentation Board with GIF capability/Object/Dragon-10-june.gif differ diff --git a/Presentation Board with GIF capability/Presentation Board with GIF capability/Object/Script for Board.lsl b/Presentation Board with GIF capability/Presentation Board with GIF capability/Object/Script for Board.lsl new file mode 100644 index 00000000..3dba4083 --- /dev/null +++ b/Presentation Board with GIF capability/Presentation Board with GIF capability/Object/Script for Board.lsl @@ -0,0 +1,171 @@ +// :SHOW: +// :CATEGORY:Presentation +// :NAME:Presentation Board with GIF capability +// :AUTHOR:Lum Pfohl +// :KEYWORDS: +// :CREATED:2015-11-24 20:39:02 +// :EDITED:2015-11-24 19:39:02 +// :ID:1096 +// :NUM:1880 +// :REV:1 +// :WORLD:Second Life +// :DESCRIPTION: +// LumVision-Presentation Script v 0.2 +// :CODE: +// Mods by Ferd Frederix to support GIFS +// +// LumVision-Presentation Script v 0.2 +// +// Written by Lum Pfohl December 11, 2007 +// +// January 02, 2008 - Added code to precache the next texture after a short time delay + + +// set to TRUE if you want debug messages written to chat screen +integer __debug = FALSE; +string __version_id = "Presentation Script v 0.3"; + +// global variables +integer interval; +integer currentTexture = 0; + +integer totalTextures = 0; +list textureList =[]; +integer messageChannel = 999888; +list dynMenu =["Back", "Version", "Forward", "Reset"]; + + +gif() +{ + string t = llList2String(textureList, currentTexture); + list gif = llParseString2List(t,[";"],[]); + string aname = llList2String(gif,0); + integer X = (integer) llList2String(gif,1); + integer Y = (integer) llList2String(gif,2); + float FPS = (float) llList2String(gif,3); + float product = X * Y; + + + + // Set the new prim texture + + llSetTexture(llList2String(textureList, currentTexture), 0); + + if (X) { + llSetTextureAnim( ANIM_ON | LOOP, ALL_SIDES, X, Y, 0.0, product, FPS); + } else { + llSetTextureAnim( LOOP, ALL_SIDES, 1,1, 0.0, 1 , FPS); + } + +} +default { +state_entry() { + + +// read in the textures in the prim and store it + integer typeCount = llGetInventoryNumber(INVENTORY_TEXTURE); + + integer j; + + for (j = 0; j < typeCount; ++j) { + string invName = llGetInventoryName(INVENTORY_TEXTURE, j); + if (__debug) { + llWhisper(0, "Inventory " + invName); + } + textureList += invName; + ++totalTextures; + } + + if (__debug) { + llWhisper(0, "Found " + (string) totalTextures + " textures"); + } + + llSetTexture(llList2String(textureList, 0), 0); + gif(); + + // initialize the channel on which the vendor will talk to the owner via dialog + messageChannel = (integer) llFrand(2000000000.0); + llListen(messageChannel, "", NULL_KEY, ""); + + currentTexture = 0; + } + +on_rez(integer start_param) { + llResetScript(); +} + + +touch_start(integer total_number) { + + if (llDetectedKey(0) == llGetOwner()) { + llDialog(llDetectedKey(0), "What do you want to do?", dynMenu, messageChannel); + } + +} + +// listen for for dialog box messages and respond to them as appropriate + +listen(integer channel, string name, key id, string message) { + + if (id != llGetOwner()) { + return; + } + + if (message == "Version") { + llWhisper(0, __version_id); + return; + } + + if (message == "Reset") { + llResetScript(); + } + + if (message == "Back" && currentTexture > 0) { + --currentTexture; + } else if (message == "Forward") { + ++currentTexture; + } else { + llDialog(llGetOwner(), "What do you want to do?", dynMenu, messageChannel); + return; + } + + // If there are textures to apply, do so now. Otherwise - quietly + // do nothing. + if (totalTextures > 0) { + + // Ensure that we do not go out of bounds with the index + if (currentTexture >= totalTextures) { + currentTexture = 0; + } + if (currentTexture < 0 ) { + currentTexture = 0; + } + + gif(); + + // set up so that in 3 seconds, we display the next image on a different face (hidden) + llSetTimerEvent(3.00); + + llDialog(llGetOwner(), "What do you want to do?", dynMenu, messageChannel); + } +} + + timer() { + // Cancel any further timer events + llSetTimerEvent(0.00); + + // set the next texture (as a pre-cache) on the reverse face + integer nextTexture = currentTexture + 1; + if (nextTexture >= totalTextures) { + nextTexture = 0; + } + llSetTexture(llList2String(textureList, nextTexture), 1); + } + + changed(integer what) + { + if (what & CHANGED_INVENTORY) + llResetScript(); + } + +} diff --git a/Presentation Board with GIF capability/Presentation Board with GIF capability/Object/dragon lady.gif b/Presentation Board with GIF capability/Presentation Board with GIF capability/Object/dragon lady.gif new file mode 100644 index 00000000..803eaf81 Binary files /dev/null and b/Presentation Board with GIF capability/Presentation Board with GIF capability/Object/dragon lady.gif differ diff --git a/Presentation Board with GIF capability/Presentation Board with GIF capability/Object/dragon lady5;2;5.jpg b/Presentation Board with GIF capability/Presentation Board with GIF capability/Object/dragon lady5;2;5.jpg new file mode 100644 index 00000000..bf6bfb24 Binary files /dev/null and b/Presentation Board with GIF capability/Presentation Board with GIF capability/Object/dragon lady5;2;5.jpg differ diff --git a/Presentation Board with GIF capability/Presentation Board with GIF capability/Object/dragon;4;4;5.jpg b/Presentation Board with GIF capability/Presentation Board with GIF capability/Object/dragon;4;4;5.jpg new file mode 100644 index 00000000..fe5f9be0 Binary files /dev/null and b/Presentation Board with GIF capability/Presentation Board with GIF capability/Object/dragon;4;4;5.jpg differ diff --git a/Presentation Board with GIF capability/Presentation Board with GIF capability/Presentation Board with GIF capability.prj b/Presentation Board with GIF capability/Presentation Board with GIF capability/Presentation Board with GIF capability.prj new file mode 100644 index 00000000..65ed6695 --- /dev/null +++ b/Presentation Board with GIF capability/Presentation Board with GIF capability/Presentation Board with GIF capability.prj @@ -0,0 +1,6 @@ + + + + + diff --git a/Prim_Animation_Compiler/Prim_Animation_Compiler/Object/Prim_Animation_Compiler by Patchouli Woollahra.lsl b/Prim_Animation_Compiler/Prim_Animation_Compiler/Object/Prim_Animation_Compiler by Patchouli Woollahra.lsl new file mode 100644 index 00000000..d512fee6 --- /dev/null +++ b/Prim_Animation_Compiler/Prim_Animation_Compiler/Object/Prim_Animation_Compiler by Patchouli Woollahra.lsl @@ -0,0 +1,250 @@ +// :SHOW: +// :CATEGORY:Prim +// :NAME:Prim_Animation_Compiler +// :AUTHOR:Ferd Frederix +// :KEYWORDS: Animation, Puppeteer +// :CREATED:2013-02-25 10:47:09.853 +// :EDITED:2015-10-20 14:27:52 +// :ID:648 +// :REV:1.11 +// :WORLD:Second Life, Opensim +// :DESCRIPTION: +// This script produces optimized LSL code that can be triggered by link messages. +// For more details on how this script works, see http://www.outworldz.com/secondlife/posts/prim-compiler/ +// :CODE: +// fred@mitsi.com +// 3-7-2012 +// Rev 1.1, added a missing } at the end of multiple recordings. +// 20-10-2015 +// Rev 1.11 - SLRes: Patchouli Woollahra (PW) + // modified SayCode to take devious advantage of new SL-specific commenting behaviors to significantly reduce + // editing time required between compile and publish, at minimal cost in compiled script load, readability of script + + +// Author: Ferd Frederix +// Based on an excellent script by Allen Firethorn +// This is free software, it is not for sale at any price. Yu can use it and sell the object. + +// Tunable stuff: +integer OpenSim = FALSE; // Set to FALSE for Second Life to save memory (EDIT: take advantage of SL-specific optimizations - PW) +string fast="Fast"; // Set this to an empty string if you don't want to use the fast animation +integer debug = FALSE; // set to TRUE to see stuff inside as it runs + +list animations; // holds a list of all animation names +list receivedData; // the data from the main program +integer STRIDE = 5; // size of the data packets in receivedData +integer currentLine; // the line we are processing + +// Link messages +integer CLEAR = -234756; +integer DATA = -234757; +integer COMPILE = -234758; +integer DIE = -234759; + +DEBUG(string msg) +{ + if (debug) llOwnerSay("// " + msg); +} + +// speak without the name of the prim in the way +SayCode (string story){ + +//Modification By Patchouli Woollahra, 20 October 2015 + //Rationale: + // Recent versions of SL server support block commenting + // C-style with /* */ tags. By encapsulating timestamps, + // object names that are part of each SayCode + // (plus any unnecessary chat that crops up during export on says) + // produce script where object names, timestamps, unnecessary chatter + // are ignored by compiler, also easier to search out and delete if + // absolute cleanliness in compiled animation script is desired! + // Until OpenSim version of LSL consistently supports C-style blockcommenting too, + // this trick is gated off the OpenSim variable and used only if + //flagged as being compiled on Second Life + if(OpenSim) { + //Code running in OpenSim + llOwnerSay (story); + } else { + //code running in SL - SayCode outputs block commenting tags as prepend-postpend to story. + // Why is block closing tag used first, and block opening tag last? + // This is an exercise left to the user - + // examine a testcompile and iterate through the code manually, you might learn something! >:D + + llOwnerSay("<--*/\n "+ story +"\n/*-->"); + } + + llSleep(0.1); + +} + +default { + + link_message(integer sender_num,integer num, string msg, key id) { + + if (num == DIE && msg =="die") // clear + { + llOwnerSay("Compiler has been removed"); + if (! debug) { + llRemoveInventory(llGetScriptName()); + } + } + + if (num == CLEAR) // clear + { + animations = []; + receivedData = []; + currentLine = 0; + llOwnerSay("Compiler Ready"); + } + else if (num == DATA) // data + { + // data formatted like this: + // name|primnum|vector Pos|rotation rot|vector size + // test1|2|<0.026306,-0.150208,0.191069>|<-0.000008,0.956309,-0.292356,0.000000>|<0.074010,0.074010,0.074010> + list primList = llParseString2List(msg,["|"],[]); + string name = llList2String(primList,0); + + receivedData += name; + receivedData += (float) llList2String(primList,1); // prim Number or ms to sleep + receivedData += (vector) llList2String(primList,2); // pos + receivedData += (rotation) llList2String(primList,3); // rot + receivedData += (vector) llList2String(primList,4); // size + + // Store the name of the animation if it is not already stored + integer i = llListFindList(animations, [name]); + + if( i < 0 ){ + DEBUG("Adding Animation named " + name); + animations += name; + } + + currentLine++; + } + else if (num == COMPILE) // finish, compile it + { + + //DEBUG("Animations:" + llDumpList2String(animations,":")); + //DEBUG("Length:" + ((string) llGetListLength(animations))); + //DEBUG("history:" + llDumpList2String(receivedData,":")); + //DEBUG("Length:" + ((string) llGetListLength(receivedData))); + + + string code; + integer i; + + llOwnerSay("Compiler processing " + (string)currentLine + " prim movements and " + (string) llGetListLength(animations) + " animations"); + + llOwnerSay("Copy everything below this line and paste it into a new script\n"); + + string oldname = llGetObjectName (); + llSetObjectName ("Compiler"); + + vector scale = llGetScale(); + SayCode("// Prim animation compiler //\n" + + "// Ferd Frederix - http://www.outworldz.com\n" + + "integer playbackchannel = 1; // The default llMessageLinked number\n" + + "rotation calcChildRot(rotation rdeltaRot){\n" + + "\tif (llGetAttached())\n" + + "\t\treturn rdeltaRot/llGetLocalRot();\n" + + "\telse\n" + + "\t\treturn rdeltaRot/llGetRootRotation();\n" + + "}\n" + + "vector originalScale = " + + (string) scale + + ";" + ); + + + + // Go through each animation and create a function for each one + for(i = 0; i < llGetListLength(animations); i++){ + integer j; + string animationName = llList2String(animations, i); + + //DEBUG("Processing " + animationName); + + SayCode(animationName + + "(){\n" + + "\tvector currentSize = llGetScale();\n" + + "\tfloat scaleby = currentSize.x/originalScale.x;\n" + ); + + + + //Read through the list and print out the instructions for this animation + integer count = llGetListLength(receivedData); + for(j=0; j < count; j += STRIDE) + { + string name = llList2String(receivedData,j); + float primNum = (float) llList2String(receivedData,j+1); + + //DEBUG("name: " + name + " Prim Num: " + primNum); + if( name == animationName){ + if(primNum > 1){ // not a root prim or sleep + SayCode( "\tllSetLinkPrimitiveParams" + + fast + + "(" + + (string) ((integer)primNum) + + ", [PRIM_POSITION, " + + (string) llList2Vector(receivedData,j+2) + + "*scaleby, " + + "PRIM_ROTATION,calcChildRot(" + + (string) llList2Rot(receivedData,j+3) + + "), " + + "PRIM_SIZE, " + + (string) llList2Vector(receivedData,j+4) + + "*scaleby]);" + ); + } else { + SayCode("\tllSleep(" + +(string)(primNum*-1) // negative numbers are sleep times + +");" + ); + } + } // if name + } // for j + SayCode ("\n}"); + } // for animations + + SayCode ( + "\n\n" + + "default{\n" + ); + if (!OpenSim) + SayCode("\tstate_entry(){\n" + +"\t\tllSetMemoryLimit(llGetUsedMemory() + 512);\n" + + "\t\t}"); + + code = "\n\tlink_message(integer sender_num, integer num, string message, key id){\n"+ + "\t\tif(num == playbackchannel){\n"; + for(i=0; i + + diff --git a/Skidz Parts Exchange 1.0/Skidz Parts Exchange 1.0/Object/Install.txt b/Skidz Parts Exchange 1.0/Skidz Parts Exchange 1.0/Object/Install.txt new file mode 100644 index 00000000..a6221a65 --- /dev/null +++ b/Skidz Parts Exchange 1.0/Skidz Parts Exchange 1.0/Object/Install.txt @@ -0,0 +1,117 @@ +// :SHOW: +// :CATEGORY:Money +// :NAME:Skidz Parts Exchange 1.0 +// :AUTHOR:Skidz Parts +// :KEYWORDS: +// :CREATED:2015-11-24 20:39:05 +// :EDITED:2015-11-24 19:39:05 +// :ID:1097 +// :NUM:1881 +// :REV:1 +// :WORLD:Second Life +// :DESCRIPTION: +// Skidz Partz - Exchange: Content Management System +// :CODE: + + + +Skidz Partz - Exchange: Content Management System +================================= + +PLEASE READ CAREFULLY THE FOLLOWING INSTRUCTIONS. IF YOU DO THIS, +YOU WILL HAVE YOUR SITE UP AND RUNNING IN JUST A FEW MINUTES. + +Base Requirements +----------------- + +In order to setup Skidz Partz - Exchange the folowing requirements are necessary. + +- A Computer with Linux, Windows, Mac OS X or any UNIX flavor installed and working properly. +- Apache Web Server (http://www.apache.org) +- PHP version 4.2.x or better (mod_php) Apache module (http://www.php.net) +- MySQL or any other supported SQL database server + +NOTE: Skidz Partz - Exchange supports MySQL, SQLite, mSQL, PostgreSQL, PostgreSQL_local, ODBC, ODBC_Adabas, Sybase +and Interbase servers. + +The official test and development system is an Apple iMac computer running Mac OS X 10.5.2 with +the package MAMP (MySQL, Apache and PHP). + +Setup of these required packages is beyond the scope of this document +and if you are unsure you should check with each of the appropriate web +sites for further details. + + +Installing the Package +---------------------- + +Unzip the package into the directory you want to use on you web server, if you're unsure where +that is, ask you system administrator. + +Point your browser to your site. + +The system will show you an error message with a link to the web based installer utility, follow +the very easy steps and you're done. + + +First Run +--------- + + After installing the system you should have a Super User account. + Go to the administration system and click on "Preferences" and change whatever you want to fit your +site info. All the options you need to change are in the Preferences menu. + + +Security Tips +------------- + +1) It's a good choice to put your config.php file outside the Web Server path, then +you can create a new config.php with the line: + + + +2) Remember to immediately rename the file "admin.php" to something else and set the new filename +in the config.php file. + +3) Even if it can't hurt, delete the "install" folder + + +Final Notes +----------- + + That's it! You're done with Skidz Partz - Exchange. Just navigate around, play with it, +abuse it and if you like Skidz Partz - Exchange, use it. Spend some time getting used to the +administration interface. + + To change your site's logo, go to the themes directory and make a custom +logo for each individual theme. Just navigate around all themes and change +the graphics you want. + + A note for the Skidz Partz - Exchange site's administrators: Only admins (authors) can +view the following data in the site: + + - Administration Menu + - Number of New Submissions + - Edit button for each link in Web Links + - Edit and Delete options for each Article + - Delete option for each comment + - IP/Host address of the comment poster + - Moderation option and button (if set for admin only) + - and MANY more... + + If you plan to give something in change for Skidz Partz - Exchange as a contribution, +please read the file SUPPORT included in this package to have some ideas. + + + +*** Special Thanks to all the people who help sending bugs reports, fixes, new +modules, requests new features, etc... I really appreciate you all! *** + +********************************* +* Skidz Partz - Exchange URL: http://code.google.com/p/skidz-partz/ * +* Skidz Partz Group: http://groups.google.ca/group/skidz-partz +********************************* + +Enjoy! + +-- Revolution Perenti diff --git a/Skidz Parts Exchange 1.0/Skidz Parts Exchange 1.0/Object/License.txt b/Skidz Parts Exchange 1.0/Skidz Parts Exchange 1.0/Object/License.txt new file mode 100644 index 00000000..4cb23fb1 --- /dev/null +++ b/Skidz Parts Exchange 1.0/Skidz Parts Exchange 1.0/Object/License.txt @@ -0,0 +1,30 @@ +// :SHOW: +// :CATEGORY:Money +// :NAME:Skidz Parts Exchange 1.0 +// :AUTHOR:Skidz Parts +// :KEYWORDS: +// :CREATED:2015-11-24 20:39:05 +// :EDITED:2015-11-24 19:39:05 +// :ID:1097 +// :NUM:1882 +// :REV:1 +// :WORLD:Second Life +// :DESCRIPTION: +// Skidz Partz - Exchange: Content Management System +// :CODE: + +Licensed as GNU GPL v3 - original is at https://code.google.com/p/skidz-partz/ + +The Second Life Marketplace Script is the website to buy an amazing assortment of virtual items sold by fellow Residents. In preparation, get started with what you need to know to about our Secondlife Marketplace Script. + +Our Secondlife Marketplace Script is an clone of sites similar to xstreetsl.com or slexchange.com and many others, what this script does is deliver inventory the the residents of Secondlife. + +What our scripts contains is a combination of Secondlife LSL scripts, which is used for the in-world Terminal to activate users accounts onto the website and deposit Lindens for use to buy Secondlife inventory on your website. and your terminal is also used to check account balance. + +Your Secondlife magicbox is used to store your secondlife Inventory, which is used to deliver your Merchandise to your Residents. + +And your bank server is used by your avatar to store linden currency which is deposited and can be withdrawal from your residents account. + +Our script also includes an complete modular portal system written in PHP and MYSQL database backend which stores your Inventory to be delivered to your Residents, And so your Residents can locate an in-world Terminal and so your Residents can communicate via portals forum and submit news for your sites front page. + +for more information please visit http://www.dazzlesoftware.ca diff --git a/Skidz Parts Exchange 1.0/Skidz Parts Exchange 1.0/Object/exchange_1.0.zip b/Skidz Parts Exchange 1.0/Skidz Parts Exchange 1.0/Object/exchange_1.0.zip new file mode 100644 index 00000000..c432a6e4 Binary files /dev/null and b/Skidz Parts Exchange 1.0/Skidz Parts Exchange 1.0/Object/exchange_1.0.zip differ diff --git a/Skidz Parts Exchange 1.0/Skidz Parts Exchange 1.0/Skidz Parts Exchange 1.0.prj b/Skidz Parts Exchange 1.0/Skidz Parts Exchange 1.0/Skidz Parts Exchange 1.0.prj new file mode 100644 index 00000000..786bb807 --- /dev/null +++ b/Skidz Parts Exchange 1.0/Skidz Parts Exchange 1.0/Skidz Parts Exchange 1.0.prj @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/SuperCar/SuperCar/SuperCar.prj b/SuperCar/SuperCar/SuperCar.prj index d63ddbcc..a37daeb5 100644 --- a/SuperCar/SuperCar/SuperCar.prj +++ b/SuperCar/SuperCar/SuperCar.prj @@ -1,6 +1,6 @@ - - - diff --git a/Synced Pose balls/Synced Pose balls.sol b/Synced Pose balls/Synced Pose balls.sol new file mode 100644 index 00000000..0349b64b --- /dev/null +++ b/Synced Pose balls/Synced Pose balls.sol @@ -0,0 +1,3 @@ + + + diff --git a/Synced Pose balls/Synced Pose balls/Poseball1/Animation.ani b/Synced Pose balls/Synced Pose balls/Poseball1/Animation.ani new file mode 100644 index 00000000..e69de29b diff --git a/Synced Pose balls/Synced Pose balls/Poseball1/Animation2.ani b/Synced Pose balls/Synced Pose balls/Poseball1/Animation2.ani new file mode 100644 index 00000000..e69de29b diff --git a/Synced Pose balls/Synced Pose balls/Poseball1/Script.lsl b/Synced Pose balls/Synced Pose balls/Poseball1/Script.lsl new file mode 100644 index 00000000..aaf9a1c5 --- /dev/null +++ b/Synced Pose balls/Synced Pose balls/Poseball1/Script.lsl @@ -0,0 +1,89 @@ +//:AUTHOR: Ferd Frederix +//:DESCRIPTION: Two Or more people sit on two or more pose balls. The pose balls play a series of animation in sync +//:CODE: +// To use this, just put several dances in a prim and add this script. +// Shift-copy the prim. Anyone who touches it will dance in sync with anyone else who touches it. + + +float RATE = 10.0; // rate to play animations + +string lastDance; // the last dance played on the slave script +string animation ; // the currwent animation name we must play +integer index; // the num,ber of that animation found in inventory +key avatarK; // the key of tjhe seated avatar +integer channel = -12134; // some random number +integer granted = FALSE; + +default +{ + state_entry() + { + llSetClickAction(CLICK_ACTION_SIT); + llListen(channel,"","",""); + } + + on_rez(integer p) + { + llResetScript(); + } + + + changed (integer detected) + { + if (detected & CHANGED_LINK) + { + avatarK = llAvatarOnSitTarget(); + if (avatarK != NULL_KEY) + { + index = 0; + llRequestPermissions(avatarK, PERMISSION_TRIGGER_ANIMATION); + } else { + granted = FALSE; + llSetTimerEvent(0); + } + } + + } + + + run_time_permissions(integer perm) + { + if (perm & PERMISSION_TRIGGER_ANIMATION) + { + granted = TRUE; + animation = llGetInventoryName(INVENTORY_ANIMATION, index); // first animation + llSay(channel,animation); // tell another poseball to let them dance. + llStartAnimation(animation); + llSetTimerEvent(RATE); + } + } + + timer() + { + integer newindex = index; // We must stop old animation + newindex++; // and play new one + + // test to see if we are past the end + if (newindex >= llGetInventoryNumber(INVENTORY_ANIMATION)) + newindex = 0; + + llSay(channel,llGetInventoryName(INVENTORY_ANIMATION, newindex)); // tell another poseball to let them dance. + llStartAnimation(llGetInventoryName(INVENTORY_ANIMATION, newindex)); + llStopAnimation(llGetInventoryName(INVENTORY_ANIMATION, index)); + + index = newindex; // get ready for the next animation + } + + listen(integer channel, string name, key id, string message) + { + llSetTimerEvent(0); // last one to send a message wins control + + if (granted) + { + llStartAnimation(message); + if (llStringLength(lastDance)) + llStopAnimation(lastDance); + lastDance = message; + } + } +} diff --git a/Synced Pose balls/Synced Pose balls/Synced Pose balls.prj b/Synced Pose balls/Synced Pose balls/Synced Pose balls.prj new file mode 100644 index 00000000..129c400f --- /dev/null +++ b/Synced Pose balls/Synced Pose balls/Synced Pose balls.prj @@ -0,0 +1,10 @@ + + + + + + + + + diff --git a/ThingSpeak Chart/Opensim - ThingSpeak.url b/ThingSpeak Chart/Opensim - ThingSpeak.url new file mode 100644 index 00000000..e5b381dd --- /dev/null +++ b/ThingSpeak Chart/Opensim - ThingSpeak.url @@ -0,0 +1,2 @@ +[InternetShortcut] +URL=https://thingspeak.com/channels/47964 diff --git a/ThingSpeak Chart/ThingSpeak Chart.sol b/ThingSpeak Chart/ThingSpeak Chart.sol new file mode 100644 index 00000000..ba340ea4 --- /dev/null +++ b/ThingSpeak Chart/ThingSpeak Chart.sol @@ -0,0 +1,3 @@ + + + diff --git a/ThingSpeak Chart/ThingSpeak Chart/ThingSpeak Chart.prj b/ThingSpeak Chart/ThingSpeak Chart/ThingSpeak Chart.prj new file mode 100644 index 00000000..f31466bd --- /dev/null +++ b/ThingSpeak Chart/ThingSpeak Chart/ThingSpeak Chart.prj @@ -0,0 +1,6 @@ + + + + + diff --git a/ThingSpeak Chart/ThingSpeak Chart/Web Chart Sender/Script.lsl b/ThingSpeak Chart/ThingSpeak Chart/Web Chart Sender/Script.lsl new file mode 100644 index 00000000..0e81cc32 --- /dev/null +++ b/ThingSpeak Chart/ThingSpeak Chart/Web Chart Sender/Script.lsl @@ -0,0 +1,131 @@ + +integer osIsNpc(key UUID) { return 0; } + + +string wAPIKey = "419JVLGPPTCM5TZ1"; + +string ChannelID = "47964"; +string fieldID = "field1"; + +list exclude = []; +// You can add a list of name of people to exclude, such as your name. +// list exclude = ["Nara Malone","Ferd Frederix"]; + +integer debug = FALSE; +integer hovertext = TRUE; // shows visitor count, or not +integer ToDo; +integer timeGoneBy = 0; + +DEBUG(string msg) +{ + if (debug ) llOwnerSay(msg); +} + +key http_request_id; +list ToDoURL; + +default +{ + + state_entry() + { + llSetText("", <1,1,1>, 1); + + string url = "https://api.thingspeak.com/update?api_key=" + + wAPIKey + + "&" + + "title" + + "=" + + "Visitors"; + + http_request_id = llHTTPRequest(llList2String(ToDoURL,0), [], ""); + llSetTimerEvent(1); + } + + timer() + { + if (ToDo == 0 && timeGoneBy ) + { + DEBUG((string) timeGoneBy); + timeGoneBy--; + return; + } + + if (ToDo == 0 && !timeGoneBy ) + { + timeGoneBy = 60; + + ToDoURL = []; + + list keys = llGetAgentList(AGENT_LIST_REGION,[]); + + integer i; + for (i=0; i < llGetListLength(keys); ++i) + { + key UUID = llList2Key(keys,i); + if (!osIsNpc(UUID)) { + string name = llKey2Name(UUID); // name + DEBUG("Checking " + name); + + if (hovertext) llSetText(name, <1,1,1>, 1); + if (llListFindList(exclude,[name]) == -1) + { + list details = llGetObjectDetails(UUID, [OBJECT_POS]) ; // pos + vector pos = llList2Vector(details,0); + string x = (string) pos.x; + string y = (string) pos.y; + string z = (string) pos.z; + + // parcel + details = llGetParcelDetails(pos,[PARCEL_DETAILS_NAME]); + string parcel = llList2String(details,0); + + string url = "https://api.thingspeak.com/update?api_key=" + wAPIKey + + "&field1=" + x; + + "&field2=" + y; + + DEBUG(url); + ToDoURL += url; + } + } + } + ToDo = 1; // send them + } + else if (ToDo == 1) + { + if (llGetListLength(ToDoURL) == 0) + { + ToDo = 0; + return; + } + + DEBUG(llList2String(ToDoURL,0)); + http_request_id = llHTTPRequest(llList2String(ToDoURL,0), [], ""); + + ToDoURL = llDeleteSubList(ToDoURL,0,0); + } + } + + http_response(key request_id, integer status, list metadata, string body) + { + if (request_id == http_request_id) + { + if (hovertext) + llSetText("Click for Stats", <0,0,1>, 1); + + DEBUG(body); + } + } + + touch_start(integer p) + { + if (llDetectedKey(0) == llGetOwner()) + { + llLoadURL(llGetOwner(), "Click to view vistors", "https://thingspeak.com/channels/" + + ChannelID + + "/" + + fieldID); + } + } + +} \ No newline at end of file diff --git a/Top2000_Radio_Stations_Player/Top_Radio_Stations_Player/Object/Remote parcel script.lsl b/Top2000_Radio_Stations_Player/Top_Radio_Stations_Player/Object/Remote parcel script.lsl index 6361d2f0..5cf32a4f 100644 --- a/Top2000_Radio_Stations_Player/Top_Radio_Stations_Player/Object/Remote parcel script.lsl +++ b/Top2000_Radio_Stations_Player/Top_Radio_Stations_Player/Object/Remote parcel script.lsl @@ -4,7 +4,7 @@ // :AUTHOR:Ferd Frederix // :KEYWORDS: // :CREATED:2013-12-14 13:33:32 -// :EDITED:2015-06-11 15:29:27 +// :EDITED:2015-12-05 19:26:08 // :ID:902 // :NUM:1558 // :REV:1.2