User:Allen Kerensky/Myriad Lite Dev/BAM Adventure Giver NPC-v0.0.7-20120704.lsl
From OpenSimulator
< User:Allen Kerensky | Myriad Lite Dev
Revision as of 18:49, 4 July 2012 by Allen Kerensky (Talk | contribs)
BAM_Adventure_Giver_NPC-v0.0.7-20120704.lsl
// Baroun's Adventure Machine BAM_Adventure_Giver_NPC-v0.0.7-20120704.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 = "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 //============================================================================