Baroun's Adventure Machine (BAM) Location Giver

Give something to an adventurer when he or she passes a certain spot

// Baroun's Adventure Machine (BAM) Location Goal v0.0.6 20110902
// 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
// Task numbers are (AdvNum*100)+task, so they don't overlap between adventures
//============================================================================

// NPC or object specific info
string  MSG_SETTEXT = "Processed Red Salt Cask";
vector  MSG_SETTEXT_COLOR = <0,0,1>;
float   MSG_SETTEXT_ALPHA = 1.0;
float   SCAN_RANGE = 3.0;
float   SCAN_REPEAT = 3.0;

// Adventure-specific info
string  AdvName="Red Salt"; // Adventure Name

// Current Task-specific info
integer AdvGoal=101;
string  TaskDoneText = "You've found the mines!";
string  TaskDoneUUID = "f78027c9-e8bb-38f2-9b11-1d4e89ac10a4";
string  PrizeName = "NONE";

// Next Task-specific info
integer AdvTaskTDNum=102;
string  AdvTaskToDo="Find some processed salt inside the mine";
string  AdvTaskToDoHint="Checking casks might be a good idea"; // 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
//============================================================================
// Ask a player HUD if the player is in an adventure - InAdv?

// Player responds:
// Yes in an adventure - InAdv | String AdventureName
// No, not in adventure - InAdv | NONE

// Task In Progress Query - Ask player what their current goal is
// NPC/Object Send Example: TaskIP?

// Task In Progress Response - The player Responds with current task in progress
// Player Send Example: TaskIP | AdventureGoal

// Task Done - Tell player they have achieved their current goal
// NPC Object Send Example: DoneTask | GoalText | TaskDone Text | PlayerUUID

// Add a task to the Player HUD - AddTask | TaskNumber | String Describing Task
// Add a hint for a task to the Player HUD - AddHint | TaskNumber | String Hint

//============================================================================
// GLOBAL CONSTANTS
//============================================================================
string MSG_STARTUP          = "Baroun's Adventure Machine is activating";
string CHAN_PREFIX          = "0x"; // prefix for calculating dynamic channels from UUIDs
string API_DIVIDER          = "|"; // The field divider within BAM packets
string API_INADV_QUERY      = "InAdv?"; // ask a player HUD if the player is in an adventure?
string API_INADV_RESPONSE   = "InAdv"; // player response to in adventure request
string API_TASKIP_QUERY     = "TaskIP?"; // what is player task-in-progress?
string API_TASKIP_RESPONSE  = "TaskIP"; // player says current task-in-progress
string API_DONETASK         = "DoneTask"; // tell player the task is done
string API_NONE             = "NONE"; // magic value for no current adventure, or no prize
string API_ADDTASK          = "AddTask"; // add a task to player HUD
string API_ADDHINT          = "AddHint"; // add a task hint to player HUD

//============================================================================
// GLOBAL RUNTIME
//============================================================================
float   SCAN_ARC;   // the sensor arc to scan for people in
list    Recent; // list of [UUID,unixtime] who recently collided with this goal
list    GotPrizes; // list of who got prizes [UUID,unixtime]
key     AvKey;      // key of player
string  AvName;     // name of player
integer BAM;        // Channel we listen on
integer Target;     //channel of thing we're talking to
integer HANDLE;     // hold a handle for the open listener

//============================================================================
// DEFAULT STATE
//============================================================================
default {
    //------------------------------------------------------------------------
    // STATE_ENTRY EVENT
    //------------------------------------------------------------------------
    state_entry() {
        llOwnerSay(MSG_STARTUP);
        // calculate our dynamic channel
        BAM= (integer)(CHAN_PREFIX + llGetSubString((string)llGetKey(),-7,-1));
        HANDLE = llListen(BAM,"",NULL_KEY,""); // start a listener with llremove handle
        SCAN_ARC = 2 * PI; // setup sensor to scan full sphere
        llSensorRepeat("",NULL_KEY,AGENT,SCAN_RANGE,SCAN_ARC,SCAN_REPEAT); // start repeating sensor
        llSetTimerEvent(EVENTTIMER); // set a timer to manage the recent list
    }

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

    //------------------------------------------------------------------------
    // SENSOR EVENT
    //------------------------------------------------------------------------
    sensor (integer things_sensed) {
        while (things_sensed--) { // count down through all things sensed
            AvKey = llDetectedKey(things_sensed); // get UUID of thing
            AvName = llDetectedName(things_sensed); // get name of thing
            if ( llListFindList(Recent,[AvKey]) == -1 ) { // is UUID in recent list?
                Recent = [AvKey] + Recent; // no, so add it to recent list
            }
            // calculate BAM dynamic channel for sensed player
            Target= (integer)(CHAN_PREFIX + llGetSubString((string)AvKey,-7,-1));
            llSay(Target,API_INADV_QUERY); // ask player if they are in an adventure
        }
    }
    //------------------------------------------------------------------------
    // LISTEN EVENT
    //------------------------------------------------------------------------
    listen(integer chan, string name, key id, string msg) {
        // calculate the BAM dynamic channel of the person interacting with us
        Target= (integer)(CHAN_PREFIX + llGetSubString((string)llGetOwnerKey(id),-7,-1));

        list tokens = llParseString2List(msg, [API_DIVIDER], []); // split message apart around | symbols
        string command = llList2String(tokens, 0); // assign first item in list as BAM command
        string data    = llList2String(tokens, 1); // assign second item in list as BAM data for command

        // if they answer with the current adventure, then react accordingly
        if ( command == API_INADV_RESPONSE ) { // player responded they are in an adventure
            if ( data == AdvName ) { // are they in THIS adventure?
                llSay(Target,API_TASKIP_QUERY); // if so, ask which task in this adventure they are working on
                return; // exit early to save processing
            }
            return; // done processing "in adventure" responses, exit listen early
        }

        if ( command == API_TASKIP_RESPONSE ) { // player responded with their task in progress
            if ( data == (string)AdvGoal ) { // its the task for THIS object - player has found the goal

                // tell player HUD the task is done! - DoneTask|(num)|(text)|UUID
                llSay(Target, API_DONETASK + API_DIVIDER + (string)AdvGoal+ API_DIVIDER +TaskDoneText+ API_DIVIDER+ TaskDoneUUID);
                // now tell GM the task is done
                llInstantMessage(llGetOwnerKey(llGetKey()),llKey2Name(llGetOwnerKey(id))+" (adventure "+AdvName+"): finished task "+(string)AdvGoal+" ("+TaskDoneText+").");

                // The Task is Done, Distribute the Prize, if any
                if ( PrizeName != API_NONE && llListFindList(GotPrizes,[llGetOwnerKey(id)]) == -1 ) { // is there a prize at this step?
                    llGiveInventory(llGetOwnerKey(id),PrizeName); // give it over
                    GotPrizes = [ llGetOwnerKey(id), (llGetUnixTime() + PRIZEWAIT) ] + GotPrizes; // remember who and when
                    // tell the GM
                    llInstantMessage(llGetOwnerKey(llGetKey()),llKey2Name(llGetOwnerKey(id))+" (adventure "+AdvName+") prize given: "+PrizeName);
                }
                // Does finishing this task trigger a new task? If so, add it and a hint to the HUD
                if ( AdvTaskTDNum != 0 ) { // there is a "TODO" task too
                    // assign the next task to the player HUD
                    llSay(Target, API_ADDTASK + API_DIVIDER +(string)AdvTaskTDNum + API_DIVIDER + AdvTaskToDo);
                    // assign the next task HINT to the player HUD
                    llSay(Target, API_ADDHINT + API_DIVIDER +(string)AdvTaskTDNum + API_DIVIDER + AdvTaskToDoHint);
                    // tell the GM the player is on the next task
                    llInstantMessage(llGetOwnerKey(llGetKey()),llKey2Name(llGetOwnerKey(id))+" (adventure "+AdvName+"): Assigning next task "+(string)AdvTaskTDNum+" ("+AdvTaskToDo+")");
                }
                return; // we're done with API_TASKIP_RESPONSE, so exit listen early in case we add more commands later
            } // if data = AdvGoal
            return; // return early since we are done with TASKIP responses, in case we add more later
        } // end if command equal TASKIP reponse
    }
}
//============================================================================
// END
//============================================================================
Creative Commons License
This work by