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

From OpenSimulator

Jump to: navigation, search

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.
    1. Set MSG_NPCNAME to the NPC's game name
    2. Choose a hovertext color for the NPC - http://wiki.secondlife.com/wiki/Category:LSL_Color
    3. Choose an alpha/transparency - 0.0 is totally clear, 1.0 is totally visible.
    4. ADVNAME: Adventure Name - set a short name for this adventure. Example: "Red Salt Quest"
    5. ADVTEXT: Adventure Description - set a short one-line description for this adventure. Example: "Find the rest salt for the baker"
    6. ADV_ATTRACT: Attract Message - Set a message for the NPC to attract player help. Example: " can you help me find some red salt?"
    7. MSG_OWNER_STARTADV: Message to owner that player started adventure. Example: " has started Red Salt Quest"
    8. 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!
    9. MSG_ADV_INCOMPLETE: what NPC should say if player comes back with not all tasks complete. Example: " Not done yet?"
    10. MSG_OWNER_DONEADV: message to quest owner when player finishes a quest. Example: "has completed Red Salt Quest"
    11. ADVDONETEXT: message from NPC to player that the quest is done. Example: "Thanks for finding red salt for me!"
    12. 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.
    13. 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.
    14. ADVTASKTDNUM: the task to-do number of the NEXT TASK. Example: 101
    15. ADTASKTODO: a text description of the NEXT TASK the player should do. Example: "Find the Red Salt Mine."
    16. 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."
    17. TRIGGERWAIT: how long in seconds before a player can re-trigger this NPC or goal. Example: 60
    18. 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)
    19. 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.
  4. Save the script and let the NPC restart.
  5. 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
//============================================================================

Personal tools
General
About This Wiki