====== Myriad Lite ====== // Myriad_Lite-v0.1.14-20131014.lsl // Copyright (c) 2013 by 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/ // CONSTANTS - DO NOT CHANGE DURING RUN string BASENAME = "Myriad Lite"; // base name of this script without version or date string VERSION = "0.1.14"; // Allen Kerensky's script version string VERSIONDATE = "20131014"; // Allen Kerensky's script yyyymmdd // Module to Module Messaging Constants integer MODULE_HUD = -1; integer LM_SENDTOATTACHMENT = 0x80000000; integer RENDEZVOUS1 = -999; // OLD "region broadcast" channel - chat sent to ALL Myriad players in region integer RENDEZVOUS2; // NEW "region broadcast" channel // Configuration Items loaded in SETUP() integer CHANCOMMAND; // chat sent by player to their meter? see OOMA CONFIG,CHANCOMMAND, default 5 integer CHANNEL_OOCCHAT; // what channel for OOC chat? see PPMA CONFIG,CHANOOCCHAT, default 22 integer CHANNEL_OOCEMOTE; // what channel for OOC emote? see PPMA CONFIG,CHANOOCEMOTE, default 23 integer CHANNEL_ICCHAT; // what channel for IC chat? see PPMA CONFIG,CHANICCHAT, default 44 integer CHANNEL_ICTHINK; // what channel for IC thinking out loud? see PPMA CONFIG,CHANICTHINK, default 45 integer CHANNEL_ICEMOTE; // what channel for IC emotes? see PPMA CONFIG,CHANICEMOTE, default 66 integer CHANNEL_NARRATE; // what channel for narrations? see PPMA CONFIG,CHANNARRATE, default 88 string OOCPREFIX; // what to put before OOC messages? see PPMA CONFIG,OOCPREFIX, default (( string OOCSUFFIX; // what to put after OOC messages? see PPMA CONFIG,OOCSUFFIX, default )) // FIXME MOVE TO RANGED COMBAT integer ALLOW_PHYS_BULLETS; // collision or Myriad Bullets? FALSE = Myriad Skill-based bullets. see PPMA CONFIG,PHYSBULLET, // RUNTIME GLOBALS - CAN CHANGE DURING RUN integer CHANPLAYER; // dynamic channel to one player's UUID integer CHANOBJECT; // dynamic channel to one object's UUID integer CHANHUD; // dyname channel to HUD object UUID integer CHANATTACH; // dynamic channel for attachments integer CHANBAM; // dynamic channel for BAM quests integer HANDRENDV1; // Myriad Rendezvous1 channel handle integer HANDRENDV2; // Myriad Rendezvous2 channel handle integer HANDCOMMAND; // command channel handle integer HANDPLAYER; // player channel handle integer HANDHUD; // HUD channel handle integer HANDATTACH; // attachment channel handle integer HANDBAM; // BAM channel update integer HANDLE_OOCCHAT; // callback handle for llListen integer HANDLE_OOCEMOTE; // callback handle for llListen integer HANDLE_ICCHAT; // callback handle for llListen integer HANDLE_ICTHINK; // callback handle for llListen integer HANDLE_ICEMOTE; // callback handle for llListen integer HANDLE_NARRATE; // callback handle for llListen string TRUENAME; // name to reset item to after talker/emoter use // Texture menu handling // float bottom left X, bottom left y, top right X, top right Y, command // coords are float 0.0 - 1.0 for X and Y - command is a string passed to PARSE() function list TEXMENU; // // 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|= // PPMA: FLAG,, // LMOUT: FLAG|= // 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|= // PPMA: FLAG,, // LMOUT: SET_FLAG|= // 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); } // // PPMA GET_CONFIG // string GET_CONFIG(string config) { config = llToUpper(config); string out = GET_VAL("CONFIG",config); if ( out == KEY_NOT_FOUND ) { if ( config == "CHANCOMMAND" ) { out = "5"; SET_VAL("CONFIG",config,out);} if ( config == "CHANOOCCHAT" ) { out = "22"; SET_VAL("CONFIG",config,out);} if ( config == "CHANOOCEMOTE" ) { out = "23"; SET_VAL("CONFIG",config,out);} if ( config == "CHANICCHAT" ) { out = "44"; SET_VAL("CONFIG",config,out);} if ( config == "CHANICTHINK" ) { out = "45"; SET_VAL("CONFIG",config,out);} if ( config == "CHANICEMOTE" ) { out = "66"; SET_VAL("CONFIG",config,out);} if ( config == "CHANNARRATE" ) { out = "88"; SET_VAL("CONFIG",config,out);} if ( config == "OOCPREFIX" ) { out = "(("; SET_VAL("CONFIG",config,out);} if ( config == "OOCSUFFIX" ) { out = "))"; SET_VAL("CONFIG",config,out);} if ( config == "PHYSBULLETS" ) { out = "0"; SET_VAL("CONFIG",config,out);} } return out; } // // PPMA GET_NAME - returns player firstname or nickname for Talker/Emoter // string GET_NAME() { string defaultname = "Myriad Player"; string name = GET_VAL("CHARACTER","NAME"); list tokens = llParseString2List(name,[" ",".","_"],[]); string firstname = llList2String(tokens,0); string lastname = llList2String(tokens,1); string nickname = GET_VAL("CHARACTER","NICKNAME"); if ( name == KEY_NOT_FOUND && nickname == KEY_NOT_FOUND ) return defaultname; if ( name != KEY_NOT_FOUND && nickname == KEY_NOT_FOUND ) return name; if ( name == KEY_NOT_FOUND && nickname != KEY_NOT_FOUND ) return nickname; if ( name != KEY_NOT_FOUND && nickname != KEY_NOT_FOUND ) return firstname+" \""+nickname+"\" "+lastname; return defaultname; } // ABILITY TEST // Requires ATTRIBUTE NAME, SKILL NAME // Returns the ability test score for use by success fail, opposed rolls, etc // See Myriad PDF page 18, Myriad Special Edition page 24 integer ABILITY_TEST(integer attribute,integer skill,integer modifiers) { integer highroll = 0; // clear out the highest roll while( attribute-- ) { // roll a dice for each point of the attribute integer roll = 1+(integer)llFrand(6.0); // roll this d6 if ( roll > highroll) highroll = roll; // if this is highest roll so far, remember it } // finished rolling a dice for each point of the base attribute return highroll + skill + modifiers; // now, return the total of highest dice roll + skill value + modifiers } // CHECK AMMO CHECKAMMO() { llWhisper(CHANATTACH,"CHECKAMMO"); } // COMBATOFF - turn off fist fighter COMBATOFF() { llMessageLinked(LINK_THIS,MODULE_HUD,"COMBATOFF",llGetOwner()); } // COMBATON - turn on fist fighter COMBATON() { llMessageLinked(LINK_THIS,MODULE_HUD,"COMBATON",llGetOwner()); } // COMMAND - process chat and link message commands together COMMAND(string msg,key id) { // break down the commands and messages into units we can work with list fields = llParseString2List(msg,["|"," "],[]); // break into list of fields based on "|"ider string command = llToLower(llStringTrim(llList2String(fields,0),STRING_TRIM)); // assume the first field is a Myriad Lite command if ( command == "debug" || command == "error" || command == "help" || command == "ownersay" || command == "rpevent" ) return; // ignore WELL commands FIXME duped from link messages - cleanup entire chat/lm message routing if ( command == "checkammo" ) { CHECKAMMO(); return;} // ATTACH_FIREARM check ammo in weapons if ( command == "combatoff") { COMBATOFF(); return; } // MODULE_SKILL_CLOSE turn off fist fighter if ( command == "combaton" ) { COMBATON(); return; } // MODULE_SKILL_CLOSE turn on the fist fighter if ( command == "credits" ) { CREDITS(); return;} // ALL MODULES show the credits including version number if ( command == "debugoff" ) { DEBUGOFF(); return; } // ALL MODULES player turn off debugging if ( command == "debugon" ) { DEBUGON(); return;} // ALL MODULES player turn on debugging if ( command == "deceive" ) { DECEIVE(msg); return;} // MODULE_SOCIAL player starts DECEIT-based social attack if ( command == "drawboth" ) { DRAW("both"); return; } // ATTACH_FIREARM ATTACH_MELEE draw both weapons if ( command == "drawleft" ) { DRAW("left"); return; } // ATTACH_FIREARM ATTACH_MELEE draw weapon in left hand if ( command == "drawright" ) { DRAW("right"); return; } // ATTACH_FIREARM ATTACH_MELEE draw weapon using right hand if ( command == "dump_records" ) { SENDTOMODULE(msg,id); return; } // MODULE_CHARSHEET if ( command == "format_db" ) { SENDTOMODULE(msg,id); return; } // MODULE_CHARSHEET if ( command == "holsterboth" ) { HOLSTER("both"); return; } // ATTACH_HOLSTER holster both weapons if ( command == "holsterleft" ) { HOLSTER("left"); return; } // ATTACH_HOLSTER holster weapon in left hand if ( command == "holsterright" ) { HOLSTER("right"); return; } // ATTACH_HOLSTER holster weapon in right hand if ( command == "memory" ) { GET_MEMORY(); return;} // return script memory information if ( command == "persuade" ) { PERSUADE(msg); return;} // MODULE_SOCIAL social attack persuasion if ( command == "quest" ) { QUEST(); return; } // MODULE_BAM check our current quest status if ( command == "reload" ) { RELOAD(); return;} // ATTACH_FIREARM reload weapons if ( command == "reset" ) { RESET(); return;} // ALL MODULES reset HUD and all modules if ( command == "safetyoff" ) { SAFETYOFF(); return;} // ATTACH_FIREARM unsafe the weapons if ( command == "safetyon" ) { SAFETYON(); return;} // ATTACH_FIREARM safe the weapons if ( command == "setup_hud" ) { SETUP_HUD(llToLower(llStringTrim(llList2String(fields,0),STRING_TRIM)),id); return; } if ( command == "sheatheboth" ) { SHEATHE("both"); return; } // ATTACH_HOLSTER sheathe both weapons if ( command == "sheatheleft" ) { SHEATHE("left"); return; } // ATTACH_HOLSTER sheathe weapon in left hand if ( command == "sheatheright" ) { SHEATHE("right"); return; } // ATTACH_HOLSTER sheathe weapon in right hand if ( command == "socialoff" ) { SOCIALOFF(); return;} // MODULE_SOCIAL if ( command == "socialon" ) { SOCIALON(); return;} // MODULE_SOCIAL if ( command == "version" ) { GET_VERSION(); return;} // ALL MODULES show the version internals if ( llGetSubString(llStringTrim(llToLower(command),STRING_TRIM),0,4) == "armor" ) { SENDTOMODULE(msg,id); return; } // MODULE_ARMOR if ( llGetSubString(llStringTrim(llToLower(command),STRING_TRIM),0,3) == "get_" ) { SENDTOMODULE(msg,id); return; } // MODULE_CHARSHEET GET_* if ( llGetSubString(llStringTrim(llToLower(command),STRING_TRIM),0,4) == "list_" ) { SENDTOMODULE(msg,id); return; } // MODULE_CHARSHEET LIST_* if ( llGetSubString(llStringTrim(llToLower(command),STRING_TRIM),0,3) == "roll" ) { ROLL(msg); return; } // roll dice if ( llGetSubString(llStringTrim(llToLower(command),STRING_TRIM),0,3) == "rlv_" ) { SENDTOMODULE(msg,id); return; } // MODULE_RLV if ( llGetSubString(llStringTrim(llToLower(command),STRING_TRIM),0,4) == "rumor" ) { RUMOR(msg); return; } // MODULE_RUMOR if ( llGetSubString(llStringTrim(llToLower(command),STRING_TRIM),0,3) == "set_" ) { SENDTOMODULE(msg,id); return; } // MODULE_CHARSHEET SET_* } // CREDITS comply with Myriad RPG Creative Common-Attribution legal requirement CREDITS() { OWNERSAY("The Myriad RPG System was designed, written, and illustrated by Ashok Desai."); OWNERSAY("RPG System licensed under the Creative Commons Attribution 2.0 UK: England and Wales."); OWNERSAY("Myriad Lite v"+VERSION+" "+VERSIONDATE+" Copyright (c) 2011-2014 by Allen Kerensky (OSG/SL)"); OWNERSAY("Licensed under Creative Commons Attribution (CC-BY) 3.0 Unported and BSD 3-clause."); // llMessageLinked(LINK_THIS,MODULE_HUD,"VERSION",llGetOwner()); } // DEBUG - show debug chat with wearer name for sorting DEBUG(string dmessage) { if ( GET_FLAG("DEBUG") == TRUE ) llMessageLinked(LINK_THIS,MODULE_HUD,"DEBUG|"+dmessage,llGetOwner()); } // DEBUGOFF - turn off the DEBUG flag DEBUGOFF() { SET_FLAG("DEBUG",FALSE); // set debug flag to FALSE OWNERSAY("Debug Mode Deactivated"); } // DEBUGON - turn on the DEBUG flag DEBUGON() { SET_FLAG("DEBUG",TRUE); // set debug flag TRUE OWNERSAY("Debug Mode Activated"); } // DECEIT SOCIAL COMBAT SKILL DECEIVE(string msg) { string message = llDumpList2String( llList2List( llParseString2List(msg,[" "],[]) , 1, -1 ) , " " ); if ( llStringLength( message ) > 3 && message != "deceive" ) { llSetObjectName(GET_NAME()); llSay(PUBLIC_CHANNEL,"/me says, \""+message+"\""); llSetObjectName(TRUENAME); } // FIXME - if in mouselook and cursor pointing at 1 avatar, make TARGETED SOCIAL ATTACK else make AREA SOCIAL ATTACK // FIXME if in mouselook, focus the attack on that one person, with a bonus Gotta look them in the eye when you're lying LOL llMessageLinked(LINK_THIS,MODULE_HUD,"DECEIVE",llGetOwner()); } // DRAW weapons DRAW(string hand) { if ( hand == "left" ) { llWhisper(CHANATTACH,"DRAWLEFT"); return; } // draw left-hand weapon if ( hand == "right" ) { llWhisper(CHANATTACH,"DRAWRIGHT"); return; } // draw right-hand weapon if ( hand == "both" ) { llWhisper(CHANATTACH,"DRAWBOTH"); return; } // draw both weapons } // ERROR - show errors on debug channel with wearer name for sorting ERROR(string emessage) { llMessageLinked(LINK_THIS,MODULE_HUD,"ERROR|"+emessage,llGetOwner()); } // MEMORY GET_MEMORY() { OWNERSAY(BASENAME+" free memory: "+(string)llGetFreeMemory()); // show this module's free memory info llMessageLinked(LINK_THIS,MODULE_HUD,"MEMORY",llGetOwner()); // trigger the rest of the modules too } // GETVERSION GET_VERSION() { OWNERSAY(BASENAME+" v"+VERSION+"-"+VERSIONDATE); // show this module's version info llMessageLinked(LINK_THIS,MODULE_HUD,"VERSION",llGetOwner()); // trigger the rest of the modules too } // HELP HELP(string help) { llMessageLinked(LINK_THIS,MODULE_HUD,"HELP|"+help,llGetOwner()); } // HOLSTER weapons HOLSTER(string hand) { if ( hand == "left" ) { llWhisper(CHANATTACH,"HOLSTERLEFT"); return; } // holster left-hand weapon if ( hand == "right" ) { llWhisper(CHANATTACH,"HOLSTERRIGHT"); return; } // holster right-hand weapon if ( hand == "both" ) { llWhisper(CHANATTACH,"HOLSTERBOTH"); return; } // holster both weapons } // METER - update a hovertext health meter or HUD bar graph METER() { llMessageLinked(LINK_THIS,MODULE_HUD,"METER",llGetOwner()); } // OWNERSAY - sent messages to player only OWNERSAY(string message) { llMessageLinked(LINK_THIS,MODULE_HUD,"OWNERSAY|"+message,llGetOwner()); } // PERSUASION SOCIAL COMBAT SKILL PERSUADE(string msg) { string message = llDumpList2String( llList2List( llParseString2List(msg,[" "],[]) , 1, -1 ) , " " ); if ( llStringLength( message ) > 3 && message != "persuade" ) { llSetObjectName(GET_NAME()); llSay(PUBLIC_CHANNEL,"/me says, \""+message+"\""); llSetObjectName(TRUENAME); } // FIXME - if in mouselook and cursor pointing at 1 avatar, make TARGETED SOCIAL ATTACK else make AREA SOCIAL ATTACK // FIXME if in mouselook, focus the attack on that one person, with a bonus Gotta look them in the eye when you're lying LOL llMessageLinked(LINK_THIS,MODULE_HUD,"PERSUADE",llGetOwner()); } // QUEST STATUS QUEST() { llMessageLinked(LINK_THIS,MODULE_HUD,"BAMSTATUS",llGetOwner()); // send a status request to BAM Modules } // RELOAD RELOAD() { llWhisper(CHANATTACH,"RELOAD"); } // RESET - shut down running animations then reset the script to reload character sheet RESET() { if ( GET_FLAG("DEAD") == TRUE || GET_FLAG("INCAPACITATED") == TRUE ) { // don't allow reset if already on respawn timer OWNERSAY("Cannot reset while incapacitated or dead. You will respawn in a few moments."); return; } OWNERSAY("Resetting Myriad Lite. Please wait..."); llMessageLinked(LINK_THIS,MODULE_HUD,"RESET",llGetOwner()); // send reset to modules llResetScript(); // now reset this script } // ROLL DICE ROLL(string message) { list input = llParseString2List(message, [" ","d"],[] ); string command = llList2String(input,0); integer numberOfDice = llList2Integer(input,1); integer numberOfSides = llList2Integer(input,2); string output = ""; integer total = 0; if ( command == "roll" ) { if ( numberOfDice >= 1 && numberOfDice <= 20 ) { if ( numberOfSides > 1 && numberOfSides <= 100 ) { integer index; for( index = 1; index <= numberOfDice; index++) { integer roll = (integer)((llFrand(1.0) * numberOfSides)+1); output += (string)roll; output += ", "; total += roll; } string ownername = llList2String(llParseString2List(llKey2Name(llGetOwner()),["@"],[]),0); // strip @where from HG names llSetObjectName("⚅ "+ownername); llWhisper(PUBLIC_CHANNEL,"/me rolls "+(string)numberOfDice+"d"+(string)numberOfSides+" resulting in "+output+" totalling "+(string)total+"."); llSetObjectName(TRUENAME); return; } } HELP("To roll, say /5 roll #d# or /5 roll #d#. For example, saying /5 roll 1d20 in chat rolls one 20-sided dice.\""); } } // RPEVENT //RPEVENT(string rpevent) { // llMessageLinked(LINK_THIS,MODULE_HUD,"RPEVENT|"+rpevent,llGetOwner()); //} // // RPEVENTIN - handle incoming RPevents here to prevent link message loops // string LAST_RP_MSG; RPEVENTIN(string rpevent) { if ( rpevent == LAST_RP_MSG ) return; // ignore duplicates LAST_RP_MSG = rpevent; // remember this one string objname = llGetObjectName(); llSetObjectName("★"); llOwnerSay("/me "+rpevent); // now tell the owner the rest of the RPEVENT| message llSetObjectName(objname); } // RUMOR CONTROL RUMOR(string cmdrumor) { DEBUG("Sending to rumor module: "+cmdrumor); llMessageLinked(LINK_THIS,MODULE_HUD,cmdrumor,llGetOwner()); // relay rumor commands to module } // SAFETY OFF SAFETYOFF() { llWhisper(CHANATTACH, "SAFETYOFF"); } // SAFETY ON SAFETYON() { llWhisper(CHANATTACH, "SAFETYON"); } // SENDTOATTACHMENT SENDTOATTACHMENT(string msg) { DEBUG("SENDTOATTACHMENT("+msg+")"); llWhisper(CHANATTACH,msg); } // SENDTOMODULE SENDTOMODULE(string msg,key speaker) { if ( llGetSubString(llStringTrim(llToLower(msg),STRING_TRIM),0,3) == "set_" ) { // is this a privileged SET_* command? if ( llList2Key(llGetParcelDetails(<0,0,0>,[PARCEL_DETAILS_OWNER]),0) != speaker ) { // if so, check speaker is region owner or object owned by region owner ERROR("Ignoring request ["+msg+"] from unprivileged speaker ["+llKey2Name(llGetOwnerKey(speaker))+"]"); return; } } // allow safer GET_* and LIST_* commands to pass through llMessageLinked(LINK_THIS,MODULE_HUD,msg,speaker); } // SETUP - begin bringing the HUD online SETUP() { SET_FLAG("DEBUG",FALSE); CREDITS(); // show Myriad credits as required by the Creative Commons - Attribution license llSetText("",<0,0,0>,0); // clear any previous hovertext // Configuration Items CHANCOMMAND = (integer)GET_CONFIG("CHANCOMMAND"); CHANNEL_OOCCHAT = (integer)GET_CONFIG("CHANOOCCHAT"); CHANNEL_OOCEMOTE = (integer)GET_CONFIG("CHANOOCEMOTE"); CHANNEL_ICCHAT = (integer)GET_CONFIG("CHANICCHAT"); CHANNEL_ICTHINK = (integer)GET_CONFIG("CHANICTHINK"); CHANNEL_ICEMOTE = (integer)GET_CONFIG("CHANICEMOTE"); CHANNEL_NARRATE = (integer)GET_CONFIG("CHANNARRATE"); OOCPREFIX = GET_CONFIG("OOCPREFIX"); OOCSUFFIX = GET_CONFIG("OOCSUFFIX"); ALLOW_PHYS_BULLETS = (integer)GET_CONFIG("PHYSBULLETS"); // Talker/Emoter setup TRUENAME = BASENAME + " " + VERSION + " " + VERSIONDATE; // put together full item name for talker/emoter llSetObjectName(TRUENAME); // force object title back if the talker/emoter messed it up //OWNERSAY("Character Sheet loaded. You are now ready to roleplay."); if ( HANDRENDV1 != 0 ) llListenRemove(HANDRENDV1); HANDRENDV1 = llListen(RENDEZVOUS1,"",NULL_KEY,""); // setup listener for Myriad RP events if ( HANDRENDV2 != 0 ) llListenRemove(HANDRENDV2); list details = llGetParcelDetails(<0,0,0>,[PARCEL_DETAILS_ID]); string parcelid = llList2String(details,0); RENDEZVOUS2 = (integer)("0x"+llGetSubString(parcelid,0,7)); HANDRENDV2 = llListen(RENDEZVOUS2,"",NULL_KEY,""); if ( HANDCOMMAND != 0 ) llListenRemove(HANDCOMMAND); HANDCOMMAND = llListen(CHANCOMMAND,"",llGetOwner(),""); // listen to chat commands from owner CHANHUD = (integer)("0x"+llGetSubString((string)llGetKey(),0,6)); // calculate a player-specfic dynamic chat channel if ( HANDHUD != 0 ) llListenRemove(HANDHUD); HANDHUD = llListen(CHANHUD,"",NULL_KEY,""); // listen on the HUD object dynamic chat channel CHANPLAYER = (integer)("0x"+llGetSubString((string)llGetOwner(),0,6)); // calculate a player-specfic dynamic chat channel if ( HANDPLAYER != 0 ) llListenRemove(HANDPLAYER); HANDPLAYER = llListen(CHANPLAYER,"",NULL_KEY,""); // listen on the player dynamic chat channel CHANATTACH = (integer)("0x"+llGetSubString((string)llGetOwner(),1,7)); // attachment-specific channel if ( HANDATTACH != 0 ) llListenRemove(HANDATTACH); HANDATTACH = llListen(CHANATTACH,"",NULL_KEY,""); // listen for messages from attachments CHANBAM = (integer)("0x" + llGetSubString((string)llGetOwner(),-7,-1)); if ( HANDBAM != 0 ) llListenRemove(HANDBAM); HANDBAM = llListen(CHANBAM,"",NULL_KEY,""); // start listener with listenremove handle // Talker/Emoter if ( HANDLE_OOCCHAT != 0 ) llListenRemove(HANDLE_OOCCHAT); HANDLE_OOCCHAT = llListen(CHANNEL_OOCCHAT,"",llGetOwner(),""); if ( HANDLE_OOCEMOTE != 0 ) llListenRemove(HANDLE_OOCEMOTE); HANDLE_OOCEMOTE = llListen(CHANNEL_OOCEMOTE,"",llGetOwner(),""); if ( HANDLE_ICCHAT != 0 ) llListenRemove(HANDLE_ICCHAT); HANDLE_ICCHAT = llListen(CHANNEL_ICCHAT,"",llGetOwner(),""); if ( HANDLE_ICTHINK != 0 ) llListenRemove(HANDLE_ICTHINK); HANDLE_ICTHINK = llListen(CHANNEL_ICTHINK,"",llGetOwner(),""); if ( HANDLE_ICEMOTE != 0 ) llListenRemove(HANDLE_ICEMOTE); HANDLE_ICEMOTE = llListen(CHANNEL_ICEMOTE,"",llGetOwner(),""); if ( HANDLE_NARRATE != 0 ) llListenRemove(HANDLE_NARRATE); HANDLE_NARRATE = llListen(CHANNEL_NARRATE,"",llGetOwner(),""); OWNERSAY("Registering any Myriad Lite-compatible attachments..."); llWhisper(CHANATTACH,"REGISTERATTACHMENTS"); // ask for attachments on their dynamic channel METER(); // update hovertext QUEST(); // update the BAM Module OWNERSAY("Core module active."); } SETUP_HUD(string texmenu,key id) { if ( llList2Key(llGetParcelDetails(<0,0,0>,[PARCEL_DETAILS_OWNER]),0) != id ) { // if so, check speaker is region owner or object owned by region owner ERROR("Ignoring SETUP_HUD command ["+texmenu+"] from unprivileged speaker ["+llKey2Name(llGetOwnerKey(id))+"]"); return; } list tokens = llCSV2List(texmenu); // convert CSV in fromtexturename, and coord/command lists to list key texid = llGetInventoryKey(llList2String(tokens,0)); // first item is texture to show if ( texid != NULL_KEY ) { llSetLinkPrimitiveParamsFast(1,[PRIM_TEXTURE,ALL_SIDES,texid,<1,1,0>,<0,0,0>,0.0]); // FIXME make sure the BLX,BLY,TRX,TRY,COMMAND format is held to TEXMENU = llList2List(tokens,1,-1); // put the rest in to menu list } } // SHEATHE weapons SHEATHE(string hand) { if ( hand == "left" ) { llWhisper(CHANATTACH,"SHEATHELEFT"); return; } // sheathe left-hand weapon if ( hand == "right" ) { llWhisper(CHANATTACH,"SHEATHERIGHT"); return; } // sheathe right-hand weapon if ( hand == "both" ) { llWhisper(CHANATTACH,"SHEATHEBOTH"); return; } // sheathe both weapons } // SOCIALOFF - turn off social combat module SOCIALOFF() { llMessageLinked(LINK_THIS,MODULE_HUD,"SOCIALOFF",llGetOwner()); } // SOCIALON - turn on social combat module SOCIALON() { llMessageLinked(LINK_THIS,MODULE_HUD,"SOCIALON",llGetOwner()); } // FIXME MOVE TO GENERIC SKILL MODULE? // An Unopposed Ability Test - Myriad PDF p. 19, Myriad Special Edition p. 25 // Requires TargetNumber, Stat Name, Skill Name, one or more things to do on success, one or more things to do on fail // UNOPPOSED_TEST||||| // CSV success and fail actions are CSV lists action1[,action2[,...]] // Each action is an embedded link message which uses ^ in place of the usual | used to separate fields // Example Actions: SAY^^ , SHOUT^, // See the WELL module for a preset library of actions you can stack from, but any valid link message is possible // FIXME This is a huge cheat and security hole as is without testing for valid actions by players vs. region owners // Returns TRUE for Success and False for Fail string valid = " abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.?!,^"; integer UNOPPOSED_TEST1(integer targetnum,string statname, string skillname,string onsuccess,string onfail) { DEBUG("CORE UNOPPOSED_TEST targetnum=["+(string)targetnum+"] statname=["+statname+"] skillname=["+skillname+"] onsuccess=["+onsuccess+"] onfail=["+onfail+"]"); // FIXME check targetnum - replace constants with MIN_TNUM/MAX_TNUM if ( targetnum < 4 || targetnum > 17 ) { ERROR("Invalid target number "+(string)targetnum+" in unopposed test. Ending unopposed test."); return FALSE; } // FIXME check stat range - replace constants with MIN_STAT/MAX_STAT integer tstat = (integer)GET_VAL("STATISTIC",statname); if ( tstat < 0 || tstat > 10 ) { ERROR("Invalid stat amount "+(string)tstat+" in unopposed test. Ending unopposed test."); return FALSE;} // FIXME check skill range - replace constants with MIN_SKILL/MAX_SKILL integer tskill = (integer)GET_VAL("SKILL",skillname); if ( tskill < 0 || tskill > 7 ) { ERROR("Invalid skill amount "+(string)tskill+" in unopposed test. Ending unopposed test."); return FALSE;} // FIXME modifiers integer tmods = 0; integer check = ABILITY_TEST(tstat,tskill,tmods); // calculate the player's ability test value (stat, skill, modifiers integer result; string acts; if ( check >= targetnum ) { acts = onsuccess; result = TRUE; } else { // player lost the ability test acts = onfail; result = FALSE; } // Do onsuccess or onfail actions // ugly string filter and conversion of ^ to | for passing to link message list actions = llCSV2List(acts); // split onsuccess or onfail into list of actions separated by commas integer count = llGetListLength(actions); // how many actions in list integer i; // temp variable for action by action loop for ( i = 0; i < count; i++ ) { // step through each action in comma list of actions string well = llList2String(actions,i); // get this specific action string out = ""; // start a new empty output string integer len = llStringLength(well); // get the length of this action string integer j; // temp variable for character by character loop for ( j = 0; j < len; j++) { // step through each character of current action string char = llGetSubString(well,j,j); // get the current character if ( char != "^" && llSubStringIndex(valid,char) >= 0 ) { // if the character is not a ^ and is in the list of VALID string characters out = out + char; // add it to the current output string } else { // otherwise its invalid out = out + " "; // and add a placeholder space instead } // done with valid character scan, excepting the ^ if ( char == "^" ) out = out + "|"; // replace the ^ with a | divider used in link messages to WELL } // done with all actions in this list llMessageLinked(LINK_THIS,MODULE_HUD,out,llGetOwner()); // fire the resulting cleaned/converted action over to WELL or other hud module to act upon // and go on to next action in list if any } // all done with actions return result; } UNOPPOSED_TEST2(key id,string task, string stat,string skill) { integer chan = (integer)("0x"+llGetSubString(id,0,6)); // calculate dynamic channel of object that triggered this test OWNERSAY("Unopposed Ability Test: "+task); // tell the player the task we're starting integer statamount = (integer)GET_VAL("STATISTIC",stat); if ( statamount == 0 ) return; // no such stat, can't really test or succeed can we if ( statamount < 0 || statamount > 10 ) return; // bogus info returned - FIXME MINSTAT/MAXSTAT integer skillamount = (integer)GET_VAL("SKILL",skill); // note skillamount = 0 is still a valid return - unskilled tests just have harder time to succeed, based on raw stat only if ( skillamount < 0 || skillamount > 7 ) return; // bogus info returned - FIXME MINSKILL/MAXSKILL integer modifiers = 0; // FIXME calculate applicable modifiers llRegionSay(chan,"UNOPPOSED_TEST2|"+(string)statamount+"|"+(string)skillamount+"|"+(string)modifiers); } // DEFAULT STATE - load character sheet default { // ATTACH - logged in with meter or worn from inventory/ground while running attach(key id) { if ( id != NULL_KEY ) { RESET(); } } // COLLISION_START -- FIXME move to Ranged COmbat module? collision_start(integer num_detected) { if ( ALLOW_PHYS_BULLETS == FALSE ) { return;} // skip phys bullet code if ( GET_FLAG("DEAD") == TRUE ) { return; } // can't take more damage when dead, but you can when incapacitated while ( num_detected-- ) { // loop through all hits this server frame float impactspeed = llVecMag(llDetectedVel(num_detected)); if ( impactspeed > 15.0 && impactspeed <= 255.0 ) { // hit by something moving between 15.0 and 255.0 m/s integer impact = llRound( (impactspeed - 15.0) / 5.0 ) + 1; // calculate 1-5 range bands of damage from 0.0 to 240.0 speed // RANGEDCOMBAT|attackdice (1-5)|hitwho (my UUID here)|hitbywho (shooter UUID)|hitbywhat (objectname) string impactmsg = "RANGEDCOMBAT"+"|"+(string)impact+"|"+(string)llGetOwner()+"|"+(string)llGetOwnerKey(llDetectedKey(num_detected))+"|"+llDetectedName(num_detected); llMessageLinked(LINK_THIS,MODULE_HUD,impactmsg,llDetectedKey(num_detected)); } } } // CHANGED - triggered for many changes to the avatar // TODO reload sim-specific settings on region change changed(integer changes) { if ( changes & CHANGED_INVENTORY ) { // inventory changed somehow? //OWNERSAY("Inventory changed. Reloading character sheet."); // FIXME do we still need this? RESET(); // saved a new character sheet? - reset and re-read it. } } // LINK MESSAGE - commands to and from other prims in HUD link_message(integer sender,integer sending_module,string str, key id) { if ( sending_module == MODULE_HUD ) return; // ignore our own link messages list fields = llParseString2List(str,["|"],[]); // break into list of fields based on "|"ider string command = llToLower(llStringTrim(llList2String(fields,0),STRING_TRIM)); // assume the first field is a Myriad Lite command if ( command == "debug" || command == "error" || command == "help" || command == "ownersay" || command == "rpevent" ) return; // ignore WELL commands - region RPevents come in through LISTEN on RENDEZVOUS1 // show debug only when not ignoring other commands DEBUG("EVENT: link_message("+(string)sender+","+(string)sending_module+","+str+","+(string)id+")"); if ( llGetSubString(command,0,4) == "armor" ) { SENDTOATTACHMENT(str); return; } // process armor messages if ( sending_module == LM_SENDTOATTACHMENT ) { SENDTOATTACHMENT(str); return; } // send module messages to attachments COMMAND(str,id); // send to shared command processor for chat and link messages return; } // LISTEN - the main Myriad Lite message processor for RP events and player commands listen(integer channel, string speakername, key speakerid, string message) { DEBUG("HUD Listen: channel=["+(string)channel+"] name=["+speakername+"] id=["+(string)speakerid+"] message=["+message+"]"); // calculate the dynamic channel of who is speaking in case we need to return commands CHANOBJECT = (integer)("0x"+llGetSubString((string)speakerid,0,6)); // 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 = llList2String(fields,0); // assume the first field is a Myriad Lite command // --- PLAYER COMMAND CHANNEL if ( channel == CHANCOMMAND ) { // handle player chat commands COMMAND(message,speakerid); // send to shared command processor for chat and link messages return; } // end of if channel == player commands // --- BAM CHANNEL if ( channel == CHANBAM ) { SENDTOMODULE(message,speakerid); // send BAM to Module return; } // end if channel BAMCHAN // --- Myriad Lite regionwide messages if ( channel == RENDEZVOUS1 || channel == RENDEZVOUS2 ) { // handle Myriad system messages if ( command == "RPEVENT" ) { // Myriad Lite RPEVENT - roleplay events everyone might find interesting RPEVENTIN(llList2String(fields,1)); return; } // end if RPEVENT return; } // end if channel == RENDEZVOUSx // --- ATTACHMENT CHANNEL if ( channel == CHANATTACH ) { // handle the attachment commands if ( GET_FLAG("DEAD") == TRUE || GET_FLAG("INCAPACITATED") == TRUE ) return; // can't mess with attachments while down if ( llToLower(llGetSubString(llStringTrim(command,STRING_TRIM),0,4)) == "armor" ) { SENDTOMODULE(message,llGetOwner()); return; } // process armor messages if ( command == "ATTACHMELEE" || command == "ATTACHRANGED" ) { // holding a weapon rather than using fists? llMessageLinked(LINK_THIS,MODULE_HUD,message,llGetOwner()); return; } if ( command == "DETACHMELEE" || command == "DETACHRANGED" ) { // are we going back to fists? llMessageLinked(LINK_THIS,MODULE_HUD,message,llGetOwner()); return; } if ( command == "ATTACHMETER" || command == "DETACHMETER" ) { llMessageLinked(LINK_THIS,MODULE_HUD,message,llGetOwner()); //METERWORN = TRUE; // we need to send meter events //METER(); // send update return; } } // --- CHANHUD - get region settings messages if ( channel == CHANHUD ) { llMessageLinked(LINK_THIS,MODULE_HUD,message,speakerid); return; // done with the HUD dynamic channel message processing, return } // --- CHANPLAYER if ( channel == CHANPLAYER ) { // handle player dynamic commands if ( command == "RPEVENT" ) { // Myriad Lite RPEVENT - roleplay events everyone might find interesting RPEVENTIN(llList2String(fields,1)); return; } // end if RPEVENT // incoming message from rumor server? if ( llGetSubString(llToLower(llStringTrim(command,STRING_TRIM)),0,4) == "rumor" ) { llMessageLinked(LINK_THIS,MODULE_HUD,message,speakerid); // send message and key of speaker to rumors return; } if ( command == "UNOPPOSED_TEST1" ) { // object in sim wants a simple skill check integer targetnum = llList2Integer(fields,1); // what is unopposed check target num? string tattrib = llList2String(fields,2); // target attribute string tskill = llList2String(fields,3); // target skill string onsuccess = llList2String(fields,4); // what to do on success string onfail = llList2String(fields,5); // what to do on fail UNOPPOSED_TEST1(targetnum,tattrib,tskill,onsuccess,onfail); return; } if ( command == "UNOPPOSED_TEST2" ) { string testtask = llList2String(fields,1); // target attribute string teststat = llList2String(fields,2); // target skill string testskill = llList2String(fields,3); // what to do on success UNOPPOSED_TEST2(speakerid,testtask,teststat,testskill); return; } // Check social combat before physical - can still talk your way out of trouble when physically incapacitated if ( (command == "DECEIVE" || command == "PERSUADE") && GET_FLAG("DEAD") == TRUE ) return; // can't argue when dead, but you can when incapacitated if ( (command == "CLOSECOMBAT" || command == "RANGEDCOMBAT") && ( GET_FLAG("DEAD") == TRUE || GET_FLAG("INCAPACITATED") == TRUE ) ) return; // can't fight when dead or down // The last action of the PLAYER CHAN processing is to put the message on the link-message bus // the speakerid is sent as the uuid field in case the modules need to send a message back out to another dynamic channel object // SOCIAL COMBAT: ATTACKER: DECEIVE, PERSUADE - DEFENDER: DECEITATTACK, PERSUASIONATTACK llMessageLinked(LINK_THIS,MODULE_HUD,message,speakerid); // passthru incoming messages including Myriad Bullets return; // done with the player channel message processing, return } // end of if channel CHANPLAYER // Handle Out-Of-Character speaking if ( channel == CHANNEL_OOCCHAT ) { string ownername = llList2String(llParseString2List(llKey2Name(llGetOwner()),["@"],[]),0); // strip @where from HG names llSetObjectName(OOCPREFIX+ownername); llSay(PUBLIC_CHANNEL,"/me says, \""+message+"\""+OOCSUFFIX); llSetObjectName(TRUENAME); return; } // Handle Out-Of-Character emotes if ( channel == CHANNEL_OOCEMOTE ) { string ownername = llList2String(llParseString2List(llKey2Name(llGetOwner()),["@"],[]),0); // strip @where from HG names llSetObjectName(OOCPREFIX+ownername); llSay(PUBLIC_CHANNEL,"/me "+message+OOCSUFFIX); llSetObjectName(TRUENAME); return; } // Handle In-Character speaking if ( channel == CHANNEL_ICCHAT ) { llSetObjectName(GET_NAME()); llSay(PUBLIC_CHANNEL,"/me says, \""+message+"\""); llSetObjectName(TRUENAME); return; } // Handle In-Character thinking if ( channel == CHANNEL_ICTHINK ) { llSetObjectName(GET_NAME()); llSay(PUBLIC_CHANNEL,"/me thinks, \'"+message+"\'"); llSetObjectName(TRUENAME); return; } // Handle In-Character emotes if ( channel == CHANNEL_ICEMOTE ) { llSetObjectName(GET_NAME()); llSay(PUBLIC_CHANNEL,"/me "+message); llSetObjectName(TRUENAME); return; } // Handle Narration if ( channel == CHANNEL_NARRATE ) { llSetObjectName("❖"); llSay(PUBLIC_CHANNEL,"/me "+message); llSetObjectName(TRUENAME); return; } } // end listen // ON_REZ - logged in with meter, or worn from inventory while running on_rez(integer param) { param = 0; // LSLINT RESET(); // a reset to reload character } // STATE ENTRY state_entry() { SETUP(); SETUP_HUD("MyriadLogoWhiteTransparent512,0.0,0.0,1.0,1.0,dump_records",llGetOwner()); } // TOUCH_START - touch HUD for adventure update touch_start(integer total_number) { integer primnum = llDetectedLinkNumber(0); string action = llGetLinkName(primnum); // get name of prim clicked in link set if ( llGetListLength(TEXMENU) > 0 && action == llGetObjectName() ) { // click rootprim, use texture button coords vector coord = llDetectedTouchST(0); integer i; for (i = 0; i < llGetListLength(TEXMENU); i+=5 ) { // strided list - X1,Y1,X2,Y2,Command float x1 = llList2Float(TEXMENU,i); float y1 = llList2Float(TEXMENU,i + 1 ); float x2 = llList2Float(TEXMENU,i + 2 ); float y2 = llList2Float(TEXMENU,i + 3 ); string button = llList2String(TEXMENU,i + 4); if ( coord.x >= x1 && coord.x <= x2 && coord.y >= y1 && coord.y <= y2 ) { COMMAND(button,llDetectedKey(0)); } } } if ( action != "" && action != llGetObjectName() ) { // someone clicked a named button prim on this linkset COMMAND(action,llDetectedKey(0)); // try that prim name as a command return; } METER(); QUEST(); } } // end state running // END