// Myriad_Lite_Module_BAM-v0.0.6-20131026.lsl // Copyright (c) 2013 by Baroun Tardis (OSG/SL) 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/ // FIXME - needs some RPEVENTS to tell others about quester's actions? // FIXME - needs some HELP events to tell others how to use the thing // CONSTANTS - DO NOT CHANGE DURING RUN string BASENAME = "Myriad Lite Module BAM"; string VERSION = "0.0.6"; // script version string VERSIONDATE = "20131026"; // script date // Module to Module Messaging Constants integer MODULE_BAM = -4; integer LM_SENDTOATTACHMENT = 0x80000000; string API_OFFERADV = "OfferAdv"; // Offer an adventure to HUD wearer string API_ACCEPTADV = "AcceptAdv"; // Offer accepted player is on an adventure string API_INADV_QUERY = "InAdv?"; // Ask a player HUD if the player is in an adventure - InAdv? string API_INADV_RESPONSE = "InAdv"; // In Adventure Response Yes: InAdv | String AdventureName No: InAdv | NONE string API_TASKIP_QUERY = "TaskIP?"; string API_TASKIP_RESPONSE = "TaskIP"; // Task In Progress Reply current task in progress: TaskIP | AdventureGoal string API_TASKCP_QUERY = "TaskCP?"; // Task Complete Query string API_TASKCP_RESPONSE = "TaskCP"; // Task Complete Reply string API_DONETASK = "DoneTask"; // Task Done - player achieved current goal NPC sends: DoneTask | GoalText | TaskDone Text | PlayerUUID string API_DONEADV = "DoneAdv"; // Done Adventure reply string API_NONE = "NONE"; // a fixed string when not in adventure - uses variable to stay consistent everywhere string API_ADDTASK = "AddTask"; // Add a task to the Player HUD - AddTask | TaskNumber | String Describing Task string API_ADDHINT = "AddHint"; // Add a hint for a task to the Player HUD - AddHint | TaskNumber | String Hint string MSG_NO_ADVENTURE = "Looking for adventure..."; string MSG_CURRENT_ADVENTURE = "Adventure: "; string MSG_CURRENT_GOAL = "Overall Goal: "; string MSG_CURRENT_TASK = "Current Task: "; string MSG_CURRENT_HINT = "Current Hint: "; // RUNTIME GLOBALS - CAN CHANGE DURING RUN integer CHANOBJBAM; // channel of thing we're talking to string STATUSMSG; // scratch space to build status messages // Adventure-Specific Configuration // Task numbers are (AdvNum*100)+task, so they don't overlap between adventures // string ADVNAME="NONE"; // Adventure Name // string ADVTEXT; // brief description // Current Task-specific info // string ADVTASKTODO; // task name of the next task handed out // integer ADVTASKTDNUM; // task number of the next task handed out // string ADVTASKTODOHINT; // Hint text for the next task handed out // Previous Task-specific info // list ADVTCOMP; // completed task numbers (list of numbers) // // PPMA GET_VAL // string KEY_NOT_FOUND = "[KEY_NOT_FOUND]"; string GET_VAL(string dbkey,string field) { //OWNERSAY("GET_VAL KEY=["+dbkey+"] FIELD=["+field+"]"); string out = KEY_NOT_FOUND; integer i; string name; string desc; for ( i = 2; i <= llGetNumberOfPrims(); i++ ) { name = llList2String(llGetLinkPrimitiveParams(i,[PRIM_NAME]),0); desc = llList2String(llGetLinkPrimitiveParams(i,[PRIM_DESC]),0); if ( llToLower(name) == llToLower(dbkey) && llToLower(desc) == llToLower(field) ) { out = llList2String(llGetLinkPrimitiveParams(i,[PRIM_TEXT]),0); //DEBUG("GET_VAL RETURN=["+out+"]"); return out; // bail on first match? } } //OWNERSAY("GET_VAL RETURN=["+out+"]"); return out; } SET_VAL(string dbkey, string field, string val) { //OWNERSAY("SET_VAL KEY=["+dbkey+"] FIELD=["+field+"] VAL=["+val+"]"); integer i; string name; string desc; integer written = FALSE; for ( i = 2; i <= llGetNumberOfPrims(); i++ ) { name = llStringTrim(llList2String(llGetLinkPrimitiveParams(i,[PRIM_NAME]),0),STRING_TRIM); desc = llStringTrim(llList2String(llGetLinkPrimitiveParams(i,[PRIM_DESC]),0),STRING_TRIM); if ( ( llToLower(name) == llToLower(dbkey) && llToLower(desc) == llToLower(field) ) && written == FALSE ) { //OWNERSAY("SET_VAL UPDATE RECORD=["+(string)i+"] DBKEY=["+dbkey+"] DESC=["+field+"] VAL=["+val+"]"); llSetLinkPrimitiveParamsFast(i,[PRIM_NAME,dbkey,PRIM_DESC,field,PRIM_TEXT,val,ZERO_VECTOR,0.0]); written = TRUE; // we did an update, remember it } } if ( written == TRUE ) { //OWNERSAY("SET_VAL UPDATE COMPLETE."); return; } // data hasn't been written, scan for free prim for ( i = 2; i <= llGetNumberOfPrims(); i++ ) { name = llStringTrim(llList2String(llGetLinkPrimitiveParams(i,[PRIM_NAME]),0),STRING_TRIM); desc = llStringTrim(llList2String(llGetLinkPrimitiveParams(i,[PRIM_DESC]),0),STRING_TRIM); if ( ( name == "" && desc == "" ) && written == FALSE ) { //OWNERSAY("SET_VAL INSERT RECORD=["+(string)i+"] DBKEY=["+dbkey+"] DESC=["+field+"] VAL=["+val+"]"); llSetLinkPrimitiveParamsFast(i,[PRIM_NAME,dbkey,PRIM_DESC,field,PRIM_TEXT,val,ZERO_VECTOR,0.0]); written = TRUE; // we did an update, remember it } } if ( written == TRUE ) { //OWNERSAY("SET_VAL INSERT COMPLETED."); return; } //OWNERSAY("SET_VAL NO FREE RECORD FOUND TO INSERT INTO! DATA LOST"); } ////////////////////////////////////////////////////////////////////////////// // FLAG FUNCTIONS // INCAPACITATED - lost wounds // DEAD - lost critical wounds // DEBUG - show debug messages or not // IDEA: INDECISION - lost resolve? // LMIM: SET_FLAG|<name>=<TRUE|FALSE> // PPMA: FLAG,<flagname>,<TRUE|FALSE> // LMOUT: FLAG|<flagname>=<TRUE|FALSE> // LMERR: FIXME integer GET_FLAG(string flag) { string val = GET_VAL("FLAG",llToUpper(flag)); if ( val == KEY_NOT_FOUND ) { ERROR("Flag ["+flag+"] does not exist."); } if ( val != "0" && val != "1") { ERROR("Flag: "+flag+" invalid value ["+val+"]"); } integer bool = (integer)val; return bool; } // SET_FLAG // LMIM: SET_FLAG|<flagname>=<TRUE|FALSE> // PPMA: FLAG,<flagname>,<TRUE|FALSE> // LMOUT: SET_FLAG|<flagname>=<TRUE|FALSE> // LMERR: FIXME SET_FLAG(string flag,integer value) { if ( flag == "" ) return; // FIXME add error if ( value != TRUE && value != FALSE ) return; // FIXME add err SET_VAL("FLAG",llToUpper(flag),(string)value); } // Adventure-Specific Configuration // Adventure Name string GET_ADVNAME() { string advname = GET_VAL("BAM","ADVENTURENAME"); if ( advname == KEY_NOT_FOUND ) return API_NONE; return advname; } SET_ADVNAME(string name) { // FIXME RANGE CHECKS SET_VAL("BAM","ADVENTURENAME",name); } // Adventure brief description string GET_ADVTEXT() { string advtext = GET_VAL("BAM","ADVENTURETEXT"); if ( advtext == KEY_NOT_FOUND ) return ""; return advtext; } SET_ADVTEXT(string text) { // FIXME RANGE CHECKS SET_VAL("BAM","ADVENTURETEXT",text); } // task name of the next task handed out string GET_ADVTASKTODO() { string todo = GET_VAL("BAM","ADVENTURENEXT"); if ( todo == KEY_NOT_FOUND ) return ""; return todo; } SET_ADVTASKTODO(string todo) { // FIXME RANGE CHECKS SET_VAL("BAM","ADVENTURETODO",todo); } // Task numbers are (AdvNum*100)+task, so they don't overlap between adventures // task number of the next task handed out integer GET_ADVTASKTODONUMBER() { string todonum = GET_VAL("BAM","ADVENTURETODONUMBER"); if ( todonum == KEY_NOT_FOUND ) return 0; return (integer)todonum; } SET_ADVTASKTODONUMBER(integer todonumber) { // FIXME RANGE CHECKS if ( todonumber < 0 ) { ERROR("Adventure next task number less than zero - ignoring"); return; } SET_VAL("BAM","ADVENTURENEXT",(string)todonumber); } // Hint text for the next task handed out string GET_ADVTASKTODOHINT() { string hint = GET_VAL("BAM","ADVENTURETODOHINT"); if ( hint == KEY_NOT_FOUND ) hint = ""; return hint; } SET_ADVTASKTODOHINT(string hint) { // FIXME RANGE CHECKS SET_VAL("BAM","ADVENTURETODOHINT",hint); } // numerically sorted list of completed task numbers list GET_ADVTASKCOMP() { string tasksdone = GET_VAL("BAM","ADVENTURETASKSCOMPLETE"); if ( tasksdone == KEY_NOT_FOUND ) return []; return llListSort(llCSV2List(tasksdone),1,TRUE); } SET_ADVTASKCOMP(list done) { // FIXME RANGE CHECKS SET_VAL("BAM","ADVENTURETASKSCOMPLETE",llList2CSV(llListSort(done,1,TRUE))); } //============================================================================ // DEBUG - show errors on debug channel with wearer name for sorting //============================================================================ DEBUG(string debugmsg) { if ( GET_FLAG("DEBUG") == TRUE ) llMessageLinked(LINK_THIS,MODULE_BAM,"DEBUG|"+debugmsg,llGetOwner()); } // // ERROR // ERROR(string msg) { llMessageLinked(LINK_THIS,MODULE_BAM,"ERROR|"+msg,llGetOwner()); } // MEMORY GET_MEMORY() { OWNERSAY(BASENAME+" free memory: "+(string)llGetFreeMemory()); // show this module's free memory info } // GETVERSION GET_VERSION() { OWNERSAY(BASENAME+" v"+VERSION+"-"+VERSIONDATE); // show this module's version info } // // OWNERSAY // OWNERSAY(string msg) { llMessageLinked(LINK_THIS,MODULE_BAM,"OWNERSAY|"+msg,llGetOwner()); } // RESET - shut down running animations then reset the script to reload character sheet RESET() { llResetScript(); // now reset } //============================================================================ // SETUP - begin bringing the HUD online //============================================================================ SETUP() { llSetText("",<0,0,0>,0); // clear any previous hovertext if ( llGetAttached() >= 31 && llGetAttached() <= 38 ) { // are we attached to a HUD slot? SET_FLAG("BAMSTATUS",TRUE); // turn on HUD hovertext } else { SET_FLAG("BAMSTATUS",FALSE); // turn off body attached hovertext } STATUS(); OWNERSAY("Baroun's Adventure Machine module active."); } //============================================================================ // STATUS() - update adventure data on HUD //============================================================================ STATUS() { if ( GET_ADVNAME() == API_NONE ) { // is player in an adventure? STATUSMSG = MSG_NO_ADVENTURE; // nope } else { // yep, build the status STATUSMSG = MSG_CURRENT_ADVENTURE + GET_ADVNAME() + "\n" + MSG_CURRENT_GOAL + GET_ADVTEXT() + "\n" + MSG_CURRENT_TASK + GET_ADVTASKTODO() + "\n" + MSG_CURRENT_HINT + GET_ADVTASKTODOHINT(); } OWNERSAY(STATUSMSG); if ( GET_FLAG("BAMSTATUS") == TRUE ) { // if attached to body, use chat output llSetText(STATUSMSG,<1,1,1>,1.0); } } //============================================================================ // DEFAULT STATE - load character sheet //============================================================================ default { //------------------------------------------------------------------------ // LINK MESSAGE - commands to and from other prims in HUD //------------------------------------------------------------------------ link_message(integer sender,integer sending_module,string message, key speakerid) { if ( sending_module == MODULE_BAM || sending_module == LM_SENDTOATTACHMENT ) return; // ignore our own messages // break down the commands and messages into units we can work with list fields = llParseString2List(message,["|"],[]); // break into list of fields based on "|"ider string command = llToUpper(llList2String(fields,0)); // assume the first field is a Myriad Lite command if ( command == "DEBUG" || command == "ERROR" || command == "HELP" || command == "OWNERSAY" || command == "RPEVENT" ) return; if ( command == "MEMORY" ) { GET_MEMORY(); return;} // get memory info if ( command == "RESET" ) { RESET(); return;} // reset on command if ( message == "VERSION" ) { GET_VERSION(); return; } // dump the version and date on request DEBUG("EVENT link_message("+(string)sender+","+(string)sending_module+","+message+","+(string)speakerid+")"); if ( message == "BAMSTATUS" ) { STATUS(); return;} // show status when specifically requested // calculate BAM dynamic channel of item/player talking to us CHANOBJBAM = (integer)("0x" + llGetSubString((string)speakerid,-7,-1)); // process the rest of the fields for multi-field commands string data1 = llList2String(fields,1); string data2 = llList2String(fields,2); string data3 = llList2String(fields,3); // We're asked what adventure we're in if ( command == API_INADV_QUERY ) { llSay(CHANOBJBAM,API_INADV_RESPONSE + "|" + GET_ADVNAME()); // reply with In Adventure and Adventure Name STATUS(); return; // command done, return early } // We're asked our current task in progress if ( command == API_TASKIP_QUERY ) { // what task in progress? // respond with task in progress llSay(CHANOBJBAM, API_TASKIP_RESPONSE + "|" + (string)GET_ADVTASKTODONUMBER()); // Reply with current task in progress and number STATUS(); return; // command done, return early } // Get list of completed tasks if ( command == API_TASKCP_QUERY ) { // what tasks complete? llSay(CHANOBJBAM, API_TASKCP_RESPONSE + "|" + llList2CSV(GET_ADVTASKCOMP())); // reply with current task complete list as CSV STATUS(); return; // command done, return early } // player is offered an adventure if ( command == API_OFFERADV ) { // want adventure? // FIXME need to add dialog box to accept/decline later SET_ADVNAME(data1); // name of the adventure SET_ADVTEXT(data2); // description of the adventure SET_ADVTASKCOMP([]); // clear the completed task list and start new one SET_ADVTASKTODO(""); // clear the next task name SET_ADVTASKTODONUMBER(0); // clear the next task number llSay(CHANOBJBAM, API_ACCEPTADV + "|" +data1); // accept the adventure STATUS(); return; // command done, return early } // add the next task to complete if ( command == API_ADDTASK ) { // add a task SET_ADVTASKTODONUMBER((integer)data1); // next task number SET_ADVTASKTODO(data2); // next task name OWNERSAY(data2); // tell player the next task name STATUS(); return; // command done, return early } // add a hint for the next task to complete if( ( command == API_ADDHINT ) && ( (integer)data1 == GET_ADVTASKTODONUMBER()) ) { SET_ADVTASKTODOHINT(data2); // next task hint OWNERSAY(data2); // tell that player the next task hint STATUS(); return; // command done, return early } // is player done with this task of the adventure? if ( ( command == API_DONETASK ) && ( (integer)data1 == GET_ADVTASKTODONUMBER()) ) { list temp = GET_ADVTASKCOMP(); SET_ADVTASKCOMP([(integer)data1] + temp); // add this task number to completed list SET_ADVTASKTODONUMBER(0); // clear out the task number we're working on since its done now SET_ADVTASKTODO(""); // clear out the task name SET_ADVTASKTODOHINT(""); // clear out the task hint OWNERSAY(data2); // tell player task is complete if ( data3 != "" ) llPlaySound(data3,0.5); // play sound if one was defined STATUS(); return; // command done, return early } // is player done with the entire adventure? if ( ( command == API_DONEADV ) && ( data1 == GET_ADVNAME() ) ) { SET_ADVTASKCOMP([]); // clear out adventure tasks - we're done SET_ADVTASKTODONUMBER(0); // clear out next task number SET_ADVTASKTODO(""); // clear out name of next task SET_ADVTASKTODOHINT(""); // clear out next task hint SET_ADVNAME(API_NONE); // set current adventure name to none SET_ADVTEXT(MSG_NO_ADVENTURE); // set the current adventure name to OWNERSAY(data2); // tell player adventure is complete if ( data3 != "" ) llPlaySound(data3,0.5); // play sound if one was defined STATUS(); return; // command done, return early } } // end link message //------------------------------------------------------------------------ // STATE ENTRY - called on Reset //------------------------------------------------------------------------ state_entry() { SETUP(); // show credits and start character sheet load } //------------------------------------------------------------------------ // TOUCH_START - touch HUD for adventure update //------------------------------------------------------------------------ touch_start(integer total_number) { total_number = 0; // LSLINT STATUS(); } } // end state running // END