Allen Kerensky"It seems you've been living two lives ..."

Myriad Lite Module Skill Ranged Combat

// Myriad_Lite_Module_Skill_Ranged_Combat-v0.0.4-20131025.lsl
// Copyright (c) 2012 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/
 
// FIXME add more RPEVENTs to module
// FIXME add HELP messages to module
 
// CONSTANTS - DO NOT CHANGE DURING RUN
string BASENAME = "Myriad Lite Module Skill Ranged Combat";
string VERSION = "0.0.4"; // script version
string VERSIONDATE = "20131025"; // script yyyymmdd
 
// Module to Module Messaging Constants
integer MODULE_RANGED = -7;
integer LM_SENDTOATTACHMENT = 0x80000000;
 
//
// 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);
//}
 
//
// GET_MYRIAD
// Requires a MYRIAD CONSTANT NAME
// Returns the amount of that Myriad rules constant or defaultif the player does not currently have that constant
//
integer GET_MYRIAD(string setting) {
    integer retval;
    string value = GET_VAL("MYRIAD",setting);
    if ( value == KEY_NOT_FOUND ) {
        if ( setting == "MINSTAT" ) retval = 1;
        if ( setting == "MAXSTAT" ) retval = 10;
        if ( setting == "MINSKILL" ) retval = 0;
        if ( setting == "MAXSKILL" ) retval = 7;
        if ( setting == "MINEFFECT" ) retval = 0;
        if ( setting == "MAXEFFECT" ) retval = 5;
        if ( setting == "MINRESILIENCE" ) retval = 0;
        if ( setting == "MAXRESILIENCE" ) retval = 20;
        if ( setting == "MINBOON" ) retval = 0;
        if ( setting == "MAXBOON" ) retval = 5;
        if ( setting == "MINFLAW" ) retval = 0;
        if ( setting == "MAXFLAW" ) retval = 5;
        if ( setting == "MINRP" ) retval = 0;
        if ( setting == "MAXRP" ) retval = 10;
        if ( setting == "MINITEM" ) retval = 0;
        if ( setting == "MAXITEM" ) retval = 100;
        if ( setting == "MINARMOR" ) retval = 0;
        if ( setting == "MAXARMOR" ) retval = 5;
        if ( setting == "MINDAMAGE" ) retval = 1;
        if ( setting == "MAXDAMAGE" ) retval = 5;
        ERROR("Unable to locate Myriad setting "+setting+" returning "+(string)retval);
    } else {
        retval = (integer)value;
    }
    return retval;
}
 
//
// GET_SKILL
// Requires a SKILL NAME
// Returns the rank value for that skill, or zero if player doesn't have skill
//
integer GET_SKILL(string askill) {
    string val = GET_VAL("SKILL",askill);
    if ( val == KEY_NOT_FOUND ) {
        ERROR("Unable to locate skill "+askill+" returning 0");
        return 0;
    }
    integer retval = (integer)val;
    if ( retval < GET_MYRIAD("MINSKILL") || retval > GET_MYRIAD("MAXSKILL") ) {
        ERROR("Skill "+askill+" value "+val+" out of range "+(string)GET_MYRIAD("MINSKILL")+"-"+(string)GET_MYRIAD("MAXSKILL"));
        return GET_MYRIAD("MINSKILL");
    }
    return retval;
}
 
//
// GET_STAT
// Requires a STAT NAME
// Returns the rank value for that statistics, or zero if player doesn't have stat
//
integer GET_STATISTIC(string astat) {
    string val = GET_VAL("STATISTIC",astat);
    if ( val == KEY_NOT_FOUND ) {
        ERROR("Unabled to locate statistic "+astat+" returning 0");
        return 0;
    }
    integer retval = (integer)val;
    if ( retval < GET_MYRIAD("MINSTAT") || retval > GET_MYRIAD("MAXSTAT") ) {
        ERROR("Statistic "+astat+" value "+val+" out of range "+(string)GET_MYRIAD("MINSTAT")+"-"+(string)GET_MYRIAD("MAXSTAT"));
        return GET_MYRIAD("MINSTAT");
    }
    return retval;
}
 
// 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 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; // now, return the total of highest dice roll + skill value
}
 
// COMBATOFF - turn off ranged combat
COMBATOFF() {
    //llMessageLinked(LINK_THIS,MODULE_RANGED,"COMBATOFF",llGetOwner());
}
 
// COMBATON - turn on ranged combat
COMBATON() {
    //llMessageLinked(LINK_THIS,MODULE_RANGED,"COMBATON",llGetOwner());
}
 
// DEBUG - show debug chat with wearer name for sorting
DEBUG(string dmessage) {
    if ( GET_FLAG("DEBUG") == TRUE ) llMessageLinked(LINK_THIS,MODULE_RANGED,"DEBUG|"+dmessage,llGetOwner());
}
 
// ERROR - show errors on debug channel with wearer name for sorting
ERROR(string emessage) {
    llMessageLinked(LINK_THIS,MODULE_RANGED,"ERROR|"+emessage,llGetOwner());
}
 
// MEMORY
GET_MEMORY() {
    OWNERSAY(BASENAME+" free memory: "+(string)llGetFreeMemory());
}
 
// GETVERSION
GET_VERSION() {
    OWNERSAY(BASENAME+" v"+VERSION+"-"+VERSIONDATE);
}
 
// An Opposed Ability Test - Myriad PDF p. 19 Myriad Special Edition p. 25
// Requires Attacker Attribute Name, Attacker Skill Name, Defender Attribute Name, Defender Skill Name
// Returns TRUE for Success, FALSE for failure
integer OPPOSED_TEST(integer aattrib,integer askill,integer dattrib,integer dskill) {
    integer acheck = ABILITY_TEST(aattrib,askill); // calculate attacker's ability test
    integer dcheck = ABILITY_TEST(dattrib,dskill); // calculate defender's ability test
    if ( acheck >= dcheck ) return TRUE; // attacker more than defender = attacker wins
    return FALSE; // defender wins
}
 
//
// OWNERSAY
//
OWNERSAY(string msg) {
    llMessageLinked(LINK_THIS,MODULE_RANGED,"OWNERSAY|"+msg,llGetOwner());
}
 
// RESET - shut down running animations then reset the script to reload character sheet
RESET() {
    llResetScript(); // now reset
}
 
// RPEVENT
RPEVENT(string rpevent) {
    llMessageLinked(LINK_THIS,MODULE_RANGED,"RPEVENT|"+rpevent,llGetOwner());
}
 
// SETUP - begin bringing the HUD online
SETUP() {
    OWNERSAY("Ranged Combat module active.");
}
 
// DEFAULT STATE
default {
 
    link_message(integer sender_num,integer sender,string message,key id) {
        if ( sender == MODULE_RANGED || sender == LM_SENDTOATTACHMENT ) return; // ignore our own messages
 
        list fields = llParseString2List(message,["|"],[]); // break line of text into = delimited fields
        string command = llToLower(llStringTrim(llList2String(fields,0),STRING_TRIM)); // field zero is the "command"
 
        if ( command == "debug" || command == "error" || command == "help" || command == "ownersay" || command == "rpevent" ) return; // ignore WELL messages
 
        // process the rest of the message for this module
        //string data = llStringTrim(llList2String(fields,1),STRING_TRIM); // field one is the data
        //list subfields = llParseString2List(data,["="],[]); // break data field into comma-delimited subfields if needed
 
        // only show debug after ignoring well commands and stuff above
        DEBUG("EVENT link_message sender_num=["+(string)sender_num+"] sender=["+(string)sender+"] message=["+message+"] id=["+(string)id+"]");
 
        if ( command == "combatoff" ) { COMBATOFF(); return; }
        if ( command == "combaton" ) { COMBATON(); return; }
        if ( command == "reset" ) { RESET(); return; }
        if ( command == "memory" ) { GET_MEMORY(); return; }
        if ( command == "version" ) { GET_VERSION(); return; }
 
        //
        // We're hit by a ranged combat item
        //
        if ( command == "rangedhit" ) { // mortal combat attack message?
            integer attackstat = llList2Integer(fields,1); // get attackers stat
            integer attackskill = llList2Integer(fields,2); // get attackers skill
            integer attackdice = llList2Integer(fields,3); // get attacker object's attack dice
            key owner = llList2Key(fields,4); // get attacker object's key
            string item = llList2String(fields,5); // get attacker object name
            if ( attackstat < GET_MYRIAD("MINSTAT") || attackstat > GET_MYRIAD("MAXSTAT") ) { // is the attack stat value out of allowed range?
                ERROR("Attack Statistic value "+(string)attackstat+" out of range: "+(string)GET_MYRIAD("MINSTAT")+"-"+(string)GET_MYRIAD("MAXSTAT"));
                // FIXME make a tattletale RP event?
                return;
            }
            if ( attackskill < GET_MYRIAD("MINSKILL") || attackstat > GET_MYRIAD("MAXSKILL") ) { // is the attack skill value out of allowed range?
                ERROR("Attack Skill value "+(string)attackskill+" out of range: "+(string)GET_MYRIAD("MINSKILL")+"-"+(string)GET_MYRIAD("MAXSKILL"));
                // FIXME make a tattletale RP event?
                return;
            }
            if ( attackdice < GET_MYRIAD("MINDAMAGE") || attackdice > GET_MYRIAD("MAXDAMAGE") ) { // is the attacking weapon's attack dice value out of allowed range?
                ERROR("Attack Damage Dice value out of range: "+(string)GET_MYRIAD("MINDAMAGE")+"-"+(string)GET_MYRIAD("MAXDAMAGE"));
                // FIXME make a tattletale RP event?
                return;
            }
            integer skillamount = 0; // create a place to hold the defenders mortal combat skill rank
            skillamount = GET_SKILL("Ranged Combat"); // get ranged combat skill rank
            // see if we're hit
            integer amihit = OPPOSED_TEST(attackstat,attackskill,GET_STATISTIC("Grace"),skillamount); // attacker power+skill vs. defender grace+skill
            if ( amihit == TRUE ) { // we're hit!
                string ownername = llList2String(llParseString2List(llKey2Name(owner),["@"],[]),0); // strip @where from HG names
                OWNERSAY("You've been hit in ranged combat by "+ownername+"'s "+item+"!");
                RPEVENT("struck by "+ownername+"'s "+item+" in ranged combat!");
                llMessageLinked(LINK_THIS,MODULE_RANGED,"HIT|"+(string)attackdice,owner); // apply the hit
            }
            return;
        }
        //
        // Actions NOT Allowed When Dead/Incapacitated go below here
        //
        if ( GET_FLAG("DEAD") == TRUE || GET_FLAG("INCAPACITATED") == TRUE ) return;
        //
        if ( command == "attachranged" ) { // holding a ranged weapon rather than using fists
            // FIXME state with close combat, social combat, etc
            return;
        }
        //
        if ( command == "detachranged" ) { // are we going back to fists?
            // FIXME state with close combat, social combat, etc
            return;
        }
        //
        // We've hit someone with a ranged attack object
        // If Your Bullet has hit, fire a hitcheck regionwide at target player's dynamic channel
        //
        if ( command == "rangedcombat" ) {
            integer attdice = llList2Integer(fields,1); // get attack dice of weapon used
            string hitwho = llList2String(fields,2); // get UUID of who we hit
            string bywho = llList2String(fields,3); // should be our own UUID
            string bywhat = llList2String(fields,4); // name of item we hit with (good for bullets/missiles)
 
            integer victimchan = (integer)("0x"+llGetSubString(hitwho,0,6)); // calculate victim's dynamic channel
            integer attstat = GET_STATISTIC("Power"); // get ranged combat attack stat level
            integer attskill = GET_SKILL("Ranged Combat"); // get ranged combat skill level
            // Message format: RANGEDHIT | <value of attack stat> | <value of attack skill> | <number of attack damage dice> | <uuid of who made attack> | <string name of what they attacked with>
            llRegionSay(victimchan,"RANGEDHIT"+"|"+(string)attstat+"|"+(string)attskill+"|"+(string)attdice+"|"+bywho+"|"+bywhat); // attack!
            return;
        } // end if RANGEDCOMBAT/TOHIT
    } // end of link_message event
 
    // STATE ENTRY - called on Reset
    state_entry() {
 
        // Check required character sheet data is in PPMA, if not, disable module until HUD reset
        // have to do this here, since state command cannot be called from global functions
        // See http://lslwiki.net/lslwiki/wakka.php?wakka=FunctionStateChangeHack
        // See also "Caveats" in http://wiki.secondlife.com/wiki/State
        list requirements = [ "STATISTIC", "Power", "STATISTIC", "Grace", "SKILL", "Ranged Combat", "RESILIENCE", "Wounds", "RESILIENCE", "CurrentWounds", "RESILIENCE", "CriticalWounds", "RESILIENCE", "CurrentCriticalWounds", "MYRIAD", "MINSTAT", "MYRIAD", "MAXSTAT", "MYRIAD", "MINSKILL", "MYRIAD", "MAXSKILL", "MYRIAD", "MINRESILIENCE", "MYRIAD", "MAXRESILIENCE", "MYRIAD", "MINDAMAGE", "MYRIAD", "MAXDAMAGE", "FLAG", "INCAPACITATED", "FLAG", "DEAD" ];
        integer i;
        for ( i = 0; i < llGetListLength(requirements); i += 2 ) {
            string category = llList2String(requirements, i);
            string aspect = llList2String(requirements, i + 1);
            if ( GET_VAL(category,aspect) == KEY_NOT_FOUND ) {
                OWNERSAY("Required "+llToLower(category)+" \""+aspect+"\" not found. Ranged Combat module will not work correctly. Disabling...");
                state disabled;
            }
        }
 
        SETUP();
    }
} // end state
 
state disabled {
    link_message(integer sender_num,integer sender,string message,key id) {
        if ( sender == MODULE_RANGED || sender == LM_SENDTOATTACHMENT ) return; // ignore our own messages
 
        list fields = llParseString2List(message,["|"],[]); // break line of text into = delimited fields
        string command = llToLower(llStringTrim(llList2String(fields,0),STRING_TRIM)); // field zero is the "command"
 
        if ( command == "debug" || command == "error" || command == "help" || command == "ownersay" || command == "rpevent" ) return; // ignore WELL commands
 
        if ( command == "reset" ) { RESET(); return; }    }
}
// END
This website uses cookies. By using the website, you agree with storing cookies on your computer. Also you acknowledge that you have read and understand our Privacy Policy. If you do not agree leave the website.More information about cookies
DokuWiki