User:Allen Kerensky/Myriad Lite/BAM Adventure Giver NPC-Preview6.lsl

From OpenSimulator

Jump to: navigation, search

BAM_Adventure_Giver_NPC-Preview6.lsl

// Baroun's Adventure Machine BAM_Adventure_Giver_NPC-v0.0.7-20120704.lsl
// Copyright (c) 2012 by Baroun Tardis and Allen Kerensky (OSG/SL) All Rights Reserved.
// This work is dual-licensed under
// Creative Commons Attribution (CC BY) 3.0 Unported
// http://creativecommons.org/licenses/by/3.0/
// - or -
// Modified BSD License (3-clause)
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright notice, 
//   this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
//   this list of conditions and the following disclaimer in the documentation
//   and/or other materials provided with the distribution.
// * Neither the name of Myriad Lite nor the names of its contributors may be
//   used to endorse or promote products derived from this software without
//   specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY EXPRESS OR
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
// NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
// NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// The Myriad RPG System was designed, written, and illustrated by Ashok Desai
// Myriad RPG System licensed under:
// Creative Commons Attribution (CC BY) 2.0 UK: England and Wales
// http://creativecommons.org/licenses/by/2.0/uk/
 
string  VERSION = "0.0.7"; // version number
string  VERDATE = "20120704"; // version date
 
//============================================================================
// 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 for luck?"; // hook to get player to play
string  MSG_OWNER_STARTADV  = " has started the 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 have work to do."; // prefixed by AVNAME
string  MSG_OWNER_DONEADV   = " has completed 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 corners of the platform"; // string
 
//integer TRIGGERWAIT = 60; // seconds to remember players to prevent re-triggering task?
float TOUCH_RANGE = 1.5; // in meters, how close to be for touch to work arms & legs = 1.5m
integer PRIZEWAIT = 3600; // seconds to remember players who got prize?
float EVENTTIMER = 15.0; // seconds between running memory and list cleanup timed events
integer FLAG_USESENSOR; // set to TRUE to use sensor alerts - set in state_entry
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
//============================================================================
integer FLAG_DEBUG;
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
string  MSG_HUDMENU         = "Would you like a copy of Barouns Adventure Machine to join into adventures?";
list    MENU                = ["YES","NO","CLOSE"];
string  API_RUMOR_FIND      = "RUMOR_SERVER_FIND";
string  API_RUMOR_FOUND     = "RUMOR_SERVER_FOUND";
string  API_RUMOR_PUT       = "RUMOR_PUT";
integer CHAN_REGION         = -999; // Myriad Region Server
integer HAND_OBJECT; // object channel handle for rumors
integer CHAN_OBJECT; // object channel for rumors
 
//============================================================================
// 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
integer CHAN_RUMOR; // what channel is the rumor server listening on?
 
//============================================================================
// Runtime Globals
//============================================================================
DEBUG(string debugmsg) {
    if ( FLAG_DEBUG == TRUE ) {
        llInstantMessage(llGetOwnerKey(llGetKey()),"DEBUG: "+debugmsg);
    }
}
 
//============================================================================
// DEFAULT STATE
//============================================================================
default {
    //------------------------------------------------------------------------
    // 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
        }
 
        if ( msg == API_RUMOR_FOUND ) {
            CHAN_RUMOR = (integer)("0x" + llGetSubString((string)id,0,6));
            llOwnerSay("Rumor server found on channel "+(string)CHAN_RUMOR);
            if ( CHAN_OBJECT != 0 ) llListenRemove(CHAN_OBJECT); // remove the unneeded object channel listener
            return;
        }
 
        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
                DEBUG(llKey2Name(llGetOwnerKey(id))+MSG_OWNER_STARTADV); // tell owner adventure begins
                if ( CHAN_RUMOR != 0 ) {
                    string who = llKey2Name(llGetOwnerKey(id));
                    llRegionSay(CHAN_RUMOR,API_RUMOR_PUT+DIV1+who+DIV1+who+MSG_OWNER_STARTADV);
                }
                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
                DEBUG(llKey2Name(llGetOwnerKey(id))+MSG_OWNER_DONEADV);
                if ( CHAN_RUMOR != 0 ) {
                    string who = llKey2Name(llGetOwnerKey(id));
                    llRegionSay(CHAN_RUMOR,API_RUMOR_PUT+DIV1+who+DIV1+who+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
        }
    }
 
    //------------------------------------------------------------------------
    // SENSOR
    //------------------------------------------------------------------------
    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
            }
        }
    }
 
    //------------------------------------------------------------------------
    // STATE_ENTRY EVENT
    //------------------------------------------------------------------------
    state_entry() {
        FLAG_DEBUG = FALSE; // do we want debug messages
        FLAG_USESENSOR = FALSE; // use the sensor or not?
        llParticleSystem([]); // shut off any running particles
        llSetText(MSG_NPCNAME,MSG_SETTEXT_COLOR,MSG_SETTEXT_ALPHA);
        DEBUG(MSG_STARTUP); // tell the owner we are initializing
        CHANBAM = (integer)(CHAN_PREFIX + llGetSubString((string)llGetKey(),-7,-1)); // calculate channel to listen on
        DEBUG(DEBUG_LISTEN_CHANNEL+(string)CHANBAM);
        llListen(CHANBAM,"",NULL_KEY,""); // listen on channel for all messages from any name, name UUID, and any message
        if ( CHAN_OBJECT != 0 ) llListenRemove(CHAN_OBJECT); // remove the unneeded object channel listener
        CHAN_OBJECT = (integer)(CHAN_PREFIX + llGetSubString((string)llGetKey(),0,6)); // calculate channel to listen on
        HAND_OBJECT = llListen(CHAN_OBJECT,"",NULL_KEY,""); // listen for rumor server
        llSetTimerEvent(EVENTTIMER); // fire off a timer event once an hour
        llRegionSay(CHAN_REGION,API_RUMOR_FIND); // send a region message to see if there is a rumor server
        if ( FLAG_USESENSOR == TRUE ) {
            llSensorRepeat("",NULL_KEY,AGENT,SENSOR_RANGE,SENSOR_ARC,SENSOR_REPEAT); // start NPC looking for someone to help
        }
    }
 
    //------------------------------------------------------------------------
    // TIMER EVENT
    //------------------------------------------------------------------------
    timer() {
        if ( REPLY_FLAG == FALSE && FLAG_USESENSOR == TRUE) {
            // 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,MSG_HUDMENU,MENU,-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?
            DEBUG("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           
    }
 
    //------------------------------------------------------------------------
    // 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
            float dist = llVecDist(llGetPos(),llList2Vector(llGetObjectDetails(AVKEY,[OBJECT_POS]),0)); // find distance between item and toucher
            if ( dist <= TOUCH_RANGE ) { // is toucher within arms reach?
 
                //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...
    }
}
//============================================================================
// END
//============================================================================
Personal tools
General
About This Wiki