Baroun's Adventure Machine (BAM) Adventure Giver NPC

The Baroun's Adventure Machine is used to build quests and adventurers for your members to enjoy.

This sets up an NPC to get the adventurers on their way.

// Baroun's Adventure Machine (BAM) Adventure Giver NPC v0.0.6 20110906
// Copyright (c) 2008-2011 Baroun Tardis (SL) and Allen Kerensky (OSG/SL)
// 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/

//============================================================================
// Adventure-Specific Configuration
//============================================================================
// NPC-specific Info
string  MSG_NPCNAME         = "BAM Adventure Giver NPC";   // 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             = "Red Salt"; // Adventure Name
string  ADVTEXT             = "Find some red salt for luck."; // brief description
string  ADV_ATTRACT            = "can you help me find some red salt?"; // hook to get player to play
string  MSG_OWNER_STARTADV  = " has started Red Salt Adventure";
string  ADV_ALL_TASKS       = "101, 102"; // 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 haven't found the red salt for me."; // prefixed by AVNAME
string  MSG_OWNER_DONEADV   = " has completed the Red Salt 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        = 101; // task number for the task THIS node hands out
string  ADVTASKTODO         = "Find the Red Salt Mines"; // task description of what to do
string  ADVTASKTODOHINT     = "Look in the other corner of the room"; // 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
float SENSOR_RANGE = 3.0; // how close do you have to be to trigger the NPC
float SENSOR_ARC = PI; // what is the sensor's sweep?
float SENSOR_REPEAT = 10; // how often to repeat the sensor

//============================================================================
// 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
//============================================================================
string  CHAN_PREFIX         = "0x";     // random chat channel prefix
string  CHAT_DIVIDER        = ", ";     // to comma-separate elements in emitted chat elements
integer DEBUG_FLAG          = FALSE;    // set to true for debug messages
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.
float   API_INADV_TIMEOUT   = 3.0;      // API timeout for an InAdv? request
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

//============================================================================
// 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

//============================================================================
// 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
        if (DEBUG_FLAG) 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
        llSensorRepeat("",NULL_KEY,AGENT,SENSOR_RANGE,SENSOR_ARC,SENSOR_REPEAT); // start NPC looking for someone to help
    }

    //------------------------------------------------------------------------
    // TOUCH_START EVENT
    //------------------------------------------------------------------------
    sensor(integer num_sensed) {
        while (num_sensed--) { // count down through all touches in this event
            AVKEY=llDetectedKey(num_sensed); // get the UUID of the toucher
            if ( llListFindList(Recent,[AVKEY]) == -1 ) { // player not in list of people recently completing the quest
                AVNAME=llDetectedName(num_sensed); // get the name of the toucher
                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
            }
        }
    }

    //------------------------------------------------------------------------
    // TIMER EVENT
    //------------------------------------------------------------------------
    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) {

        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
            if ( data == API_NONE && llListFindList(Recent,[llGetOwnerKey(id)]) == -1 ) { // 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
                Recent = [ llGetOwnerKey(id), llGetUnixTime() + TRIGGERWAIT ] + Recent; // remember this player 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
//============================================================================
Creative Commons License
This work by