User:Allen Kerensky/Myriad Lite Preview 5/BAM Adventure Giver NPC

= Myriad Lite BAM Adventure Giver NPC =

All quests start and end with an NPC. The first quest NPC would be fictional task 100, and each step of the quest would be 101, 102, etc, and when done, the player returns to the quest NPC to claim the prize.

Setup
There are a number steps and configurable items for a BAM Adventure Giver NPC:
 * 1) Create your quest NPC item.
 * 2) Drop in the BAM Adventure Giver NPC script.
 * 3) Edit the "Adventure Specific Configuration" section at the top of the script. The variables are explained next.
 * 4) Set MSG_NPCNAME to the NPC's game name
 * 5) Choose a hovertext color for the NPC - http://wiki.secondlife.com/wiki/Category:LSL_Color
 * 6) Choose an alpha/transparency - 0.0 is totally clear, 1.0 is totally visible.
 * 7) ADVNAME: Adventure Name - set a short name for this adventure. Example: "Red Salt Quest"
 * 8) ADVTEXT: Adventure Description - set a short one-line description for this adventure. Example: "Find the rest salt for the baker"
 * 9) ADV_ATTRACT: Attract Message - Set a message for the NPC to attract player help. Example: " can you help me find some red salt?"
 * 10) MSG_OWNER_STARTADV: Message to owner that player started adventure. Example: " has started Red Salt Quest"
 * 11) ADV_ALL_TASKS: list of all task numbers that MUST be completed to get the prize. Example: "101, 102" NOTE: This list is a comma-separated values list, so there should be a SPACE after each comma. If the adventure does not finish when the player has performed all tasks, check this first!
 * 12) MSG_ADV_INCOMPLETE: what NPC should say if player comes back with not all tasks complete. Example: " Not done yet?"
 * 13) MSG_OWNER_DONEADV: message to quest owner when player finishes a quest. Example: "has completed Red Salt Quest"
 * 14) ADVDONETEXT: message from NPC to player that the quest is done. Example: "Thanks for finding red salt for me!"
 * 15) ADVDONEUUID: uuid key of a sound to play when player is successful on the quest, like a fanfare.  Example: a "TA-DA" horn sound Can be the name of a sound effect file in the NPC object inventory too.
 * 16) PRIZENAME: name of a prize object in NPC object inventory to give to the player when they finish the quest. Example: Sweet roll or NONE. Put the Prize object in the NPC's object inventory. The key value NONE means do not give a prize at the end of the quest.
 * 17) ADVTASKTDNUM: the task to-do number of the NEXT TASK. Example: 101
 * 18) ADTASKTODO: a text description of the NEXT TASK the player should do.  Example: "Find the Red Salt Mine."
 * 19) ADVTASKTODOHINT: a hint for the player on how to complete the NEXT TASK they are being given. Example: "You think the Red Salt Mine is northeast."
 * 20) TRIGGERWAIT: how long in seconds before a player can re-trigger this NPC or goal. Example: 60
 * 21) PRIZEWAIT: how long in seconds between when player can re-do quest and get the prize again. Example: 3600 (= 1 hour, or 86400 = 1 day)
 * 22) EVENTTIMER: how long between timer events, the smaller the number, the more lag. Example: 15.0 The timer processes the trigger and prize given lists to expire old entries - too long a wait between timers means longer before players can retry the quests.
 * 23) Save the script and let the NPC restart.
 * 24) Move on to creating the quest goals for this NPC's quest.

BAM_Adventure_Giver_NPC-v0.0.6-20120130.lsl
// Baroun's Adventure Machine BAM_Adventure_Giver_NPC-v0.0.6-20120130.lsl // Copyright (c) 2008-2012 Baroun Tardis (SL) and Allen Kerensky (OSG/SL) // The Myriad RPG System was designed, written, and illustrated by Ashok Desai // Myriad RPG licensed under the Creative Commons Attribution 2.0 UK: England and Wales // http://creativecommons.org/licenses/by/2.0/uk/ // Myriad Lite software Copyright (c) 2011-2012 by Allen Kerensky (OSG/SL) // Baroun's Adventure Machine Copyright (c) 2008-2011 by Baroun Tardis (SL) // Myriad Lite and Baroun's Adventure Machine licensed under the // Creative Commons Attribution-Share Alike-Non-Commercial 3.0 Unported // http://creativecommons.org/licenses/by-nc-sa/3.0/ // You must agree to the terms of this license before making any use of this software. // If you do not agree to this license, simply delete these materials. // There is no warranty, express or implied, for your use of these materials.

//============================================================================ // Adventure-Specific Configuration //============================================================================ // NPC-specific Info string MSG_NPCNAME         = "Testy Testerson";   // hovertext name for NPC - I hate hovertext vector MSG_SETTEXT_COLOR   = <1.0,1.0,1.0>;   // color to display settext in float   MSG_SETTEXT_ALPHA   = 1.0; // alpha for hovertext. 0.0 = clear, 1.0 = solid.

// Adventure-specific Info string ADVNAME             = "Testing"; // Adventure Name string ADVTEXT             = "The Quest To Test This Questy Test"; // brief description string ADV_ATTRACT            = "can you help me test a quest?"; // hook to get player to play string MSG_OWNER_STARTADV  = " has started Testing Adventure"; string ADV_ALL_TASKS       = "999"; // CSV list of task numbers that all have to be done for adventure to be complete example: "101, 102, 103" string MSG_ADV_INCOMPLETE  = ", It looks like you still have work to do."; // prefixed by AVNAME string MSG_OWNER_DONEADV   = " has completed Testing Adventure"; // message to speak when task complete string ADVDONETEXT         = "Thanks! I appreciate your help!"; string ADVDONEUUID         = "f78027c9-e8bb-38f2-9b11-1d4e89ac10a4"; //object or sounds to give as a mission complete string PRIZENAME           = "NONE"; // prize to give when adventure is complete

// Task-Specific Info // Task numbers are (AdvNum*100)+task, so they don't match up between adventures integer ADVTASKTDNUM       = 999; // task number for the task THIS node hands out string ADVTASKTODO         = "Test reaching a goal"; // task description of what to do string  ADVTASKTODOHINT     = "Test using a hint"; // string

integer TRIGGERWAIT = 60; // seconds to remember players to prevent re-triggering task? integer PRIZEWAIT = 3600; // seconds to remember players who got prize? float EVENTTIMER = 15.0; // seconds between running memory and list cleanup timed events

//============================================================================ // MESSAGE FORMAT REFERENCE //============================================================================ // -1 OUT - [YES,NO,CLOSE] // -1 IN - YES // BAMCHAN OUT - InAdv? // BAMCHAN IN - InAdv|str NONE // BAMCHAN IN - InAdv|str AdventureName // BAMCHAN OUT - OfferAdv|str AdventureName|str AdventureText // BAMCHAN OUT - TaskCP? // BAMCHAN IN - AcceptAdv|str AdvName // BAMCHAN OUT - AddTask|int TaskNumber|str TaskToDo // BAMCHAN OUT - AddHint|int TaskNumber|str TaskHint // BAMCHAN IN - TaskCP| // BAMCHAN IN - TaskCP|str 999 // BAMCHAN OUT - DoneAdv|str AdventureName|str AdventureText|key AdventureDoneUUID

//============================================================================ // Global Constants //============================================================================ integer REPLY_FLAG              = FALSE;    // flag to store if we got a reply in time or not for InAdv? string CHAN_PREFIX         = "0x";     // random chat channel prefix string CHAT_DIVIDER        = ", ";     // to comma-separate elements in emitted chat elements string MSG_STARTUP         = "Baroun's Adventure Machine is activating."; string DEBUG_LISTEN_CHANNEL= "BAM Listening on channel: "; // debug message to see picked channel string API_INADV_QUERY     = "InAdv?"; // API trigger to check if in an adventure. string API_TASKCP_QUERY    = "TaskCP?"; // task complete? string API_INADV_RESPONSE  = "InAdv";  // confirms player is already in an adventure string DIV1         = "|";      // divides fields of API messages string API_NONE            = "NONE";   // magic text if player not in an adventure string API_OFFER_ADV       = "OfferAdv"; // offer player an adventure string API_ACCEPT_ADV      = "AcceptAdv"; // player accepts adventure string API_ADD_TASK        = "AddTask"; // add a task to player string API_ADD_HINT        = "AddHint"; // add a hint for the current task string API_TASKCP_RESPONSE = "TaskCP"; // task completed string API_DONEADV         = "DoneAdv"; // adventure done string MSG_NEED_HUD        = ", you'll need Baroun's Adventure Machine HUD to join this adventure. Here's one for you to wear."; string INVENTORY_HUD       = "Baroun's Adventure Machine HUD"; // name of HUD item to give from inventory. string INVENTORY_NOTE      = "BAM Baroun's Adventure Machine Instructions"; // name of notecard to give

//============================================================================ // Runtime Globals //============================================================================ list   Recent; // list of [UUID,unixtime] who recently collided with this goal list   GotPrizes; // list of who got prizes [UUID,unixtime] key    AVKEY;      // UUID of the avatar we are interacting with string AVNAME;     // String name of the avatar we are interacting with integer CHANBAM;   // Channel we listen on integer CHANTARGET; // channel of thing we're talking to integer MENUHAND;   // llListenRemove handle for Menu chat channel

//============================================================================ // DEFAULT STATE //============================================================================ default { //   // STATE_ENTRY EVENT //   state_entry { llParticleSystem([]); // shut off any running particles llSetText(MSG_NPCNAME,MSG_SETTEXT_COLOR,MSG_SETTEXT_ALPHA); llOwnerSay(MSG_STARTUP); // tell the owner we are initializing CHANBAM = (integer)(CHAN_PREFIX + llGetSubString((string)llGetKey,-7,-1)); // calculate channel to listen on       llOwnerSay(DEBUG_LISTEN_CHANNEL+(string)CHANBAM); llListen(CHANBAM,"",NULL_KEY,""); // listen on channel for all messages from any name, name UUID, and any message llSetTimerEvent(EVENTTIMER); // fire off a timer event once an hour }

//   // TOUCH_START EVENT //   touch_start(integer times_touched) { while (times_touched--) { // count down through all touches in this event AVKEY=llDetectedKey(times_touched); // get the UUID of the toucher AVNAME=llDetectedName(times_touched); // get the name of the toucher if ( llListFindList(Recent,[AVKEY]) == -1 ) { // is player in recent list? Recent = [AVKEY,llGetUnixTime+TRIGGERWAIT] + Recent; // no, add player to list // calculate player-specific BAM dynamic channel CHANTARGET = (integer)(CHAN_PREFIX + llGetSubString((string)AVKEY,-7,-1)); llSay(CHANTARGET, API_INADV_QUERY); // ask player if they are in adventure, must be within 10m for effect }       }        REPLY_FLAG = FALSE; // set a flag to track if we get a reply or not... }

//   // TIMER EVENT //   timer { if ( REPLY_FLAG == FALSE) { // no response to test if player already wearing HUD, so ask them if they want one MENUHAND = llListen(-1,"",NULL_KEY,""); // set up a listen channel for the menu to reply on           // Send them the menu llDialog(AVKEY,"Would you like a copy of Barouns Adventure Machine to join into adventures?",["YES","NO","CLOSE"],-1); llSay(PUBLIC_CHANNEL,AVNAME+MSG_NEED_HUD); // tell player they need a HUD to play REPLY_FLAG = TRUE; // set a reply flag so we don't keep sending more HUDs }       llSetTimerEvent(0.0); // remove the timer // on timer, check memory left and clear recent list if needed integer freemem = llGetFreeMemory; // how much memory free? if ( freemem < 1024 ) { // is it too little? llInstantMessage(llGetOwnerKey(llGetKey),"Memory low for "+llGetObjectName+" in "+llGetRegionName+". Resetting RECENT list."); Recent=[]; // clear the recent list GotPrizes=[]; // clear the gotPrizes list return; // exit timer event, no sense in processing lists further since we just emptied them }       // check to see if entries in Recent list have expired integer i; // temporary index number into list list temprecent = []; // temporary list to hold entries we want to keep key who; // temporary place to keep the keys we process in the lists integer time; // temporary place to keep the time we process in the lists for (i = 0; i < llGetListLength(Recent); i += 2) { // step through strided list from begin to end who = llList2Key(Recent,i); // get the UUID for this list stride time = llList2Integer(Recent,i+1); // get the integer time for this list stride if ( llGetUnixTime < time ) temprecent = [who,time] + temprecent; // non expired, keep this entry }       Recent = temprecent; // now, replace the Recent list with the pruned version // check to see if entries in GotPrizes list have expired temprecent = []; // clear the temp list again for (i = 0; i < llGetListLength(GotPrizes); i += 2) { // step through next strided list who = llList2Key(GotPrizes,i); // get the uuid for this list stride time = llList2Integer(GotPrizes,i+1); // get the integer time for this list stride if ( llGetUnixTime < time ) temprecent = [who,time] + temprecent; // non expired, keep this entry }       GotPrizes = temprecent; // replace the gotprizes list with the pruned one }

//   // LISTEN EVENT //   listen(integer chan, string name, key id, string msg) { name = ""; // LSLINT if ( chan == -1 ) { if ( msg == "YES" ) { // player responded yes to menu asking if they want a HUD llGiveInventory(id,INVENTORY_HUD); // send them the HUD llGiveInventory(id,INVENTORY_NOTE); // send them instructions return; // exit early in case we decide to add more commands, and we're done with this one }           llListenRemove(MENUHAND); // done with stuff on menu channel, remove the listener llSetTimerEvent(0.0); // stop a running timer REPLY_FLAG = FALSE; // reset the reply flag return; // exit early since we're done with menu channel }

list  tokens  = llParseString2List(msg, [DIV1], []); // split incoming message apart using | string command = llList2String(tokens, 0); // the first part of the message is a command string data   = llList2String(tokens, 1); // the second part is command-specific data

// if they answer they are in an adventure, react accordingly if ( command == API_INADV_RESPONSE ) { // In An Adventure Response REPLY_FLAG = TRUE; // we got a response from a HUD, remember it           if ( data == API_NONE ) { // responded with no current adventure llSay(PUBLIC_CHANNEL,AVNAME+CHAT_DIVIDER+ADV_ATTRACT); llSay(CHANTARGET,API_OFFER_ADV+DIV1+ADVNAME+DIV1+ADVTEXT); // offer one return; }           if ( data == ADVNAME ) { // already in the current adventure llSay(CHANTARGET,API_TASKCP_QUERY); // so let's check if they have completed adventure? return; }       }

// If they are accepting the adventure, hand them first task if ( command == API_ACCEPT_ADV ) { // accepting an adventure if ( data == ADVNAME ) { // accepting this nodes adventure llSay(PUBLIC_CHANNEL,ADVTEXT); llSay(CHANTARGET,API_ADD_TASK+DIV1+(string)ADVTASKTDNUM+DIV1+ADVTASKTODO); // give them first task llSay(CHANTARGET,API_ADD_HINT+DIV1+(string)ADVTASKTDNUM+DIV1+ADVTASKTODOHINT); // give them first task hint llInstantMessage(llGetOwner,llKey2Name(llGetOwnerKey(id))+MSG_OWNER_STARTADV); // tell owner adventure begins return; // done with this chat command, exit early }       }

if ( command == API_TASKCP_RESPONSE ) { // player sends TaskCP|task# to NPC string playertasks = llList2CSV(llListSort(llCSV2List(data),1,TRUE)); string alltasks = llList2CSV(llListSort(llCSV2List(ADV_ALL_TASKS),1,TRUE)); if ( playertasks == alltasks ) { // if all tasks are in done list, then adventure is done // tell player HUD that the adventure is done llSay(CHANTARGET,API_DONEADV+DIV1+ADVNAME+DIV1+ADVDONETEXT+DIV1+ADVDONEUUID); // tell player and public that adventure is done llSay(PUBLIC_CHANNEL,llKey2Name(llGetOwnerKey(id))+CHAT_DIVIDER+ADVDONETEXT); // give player the prize if one is defined if (PRIZENAME!="NONE" && llListFindList(GotPrizes,[llGetOwnerKey(id)]) == -1 ) { llGiveInventory(llGetOwnerKey(id),PRIZENAME); GotPrizes = [ llGetOwnerKey(id), (llGetUnixTime + PRIZEWAIT) ] + GotPrizes; // remember who and when }               // send message to quest OWNER that a player finished and got the prize llInstantMessage(llGetOwner,llKey2Name(llGetOwnerKey(id))+MSG_OWNER_DONEADV); return; // exit early }           // player has not completed last quest task, so tell them there is more to do            llSay(PUBLIC_CHANNEL,llKey2Name(llList2String(llGetObjectDetails(id,[OBJECT_OWNER]),0))+MSG_ADV_INCOMPLETE); return; // exit early in case we add more chat commands }   } } //============================================================================ // END //============================================================================