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

Myriad Lite Module Skill Close Combat

// Myriad_Lite_Module_Skill_Close_Combat-v0.0.3-20131026.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/
 
// FIXME ADD RPEVENTS TO MODULE
// FIXME ADD HELP EVENTS TO MODULE
 
// CONSTANTS - DO NOT CHANGE DURING RUN
string BASENAME = "Myriad Lite Module Skill Close"; // base name for the script
string VERSION = "0.0.3"; // Allen Kerensky's script version
string VERSIONDATE = "20131026"; // Allen Kerensky's script yyyymmdd
 
// Module to Module Messaging Constants
integer MODULE_CLOSE = -6;
integer LM_SENDTOATTACHMENT = 0x80000000;
 
// CONFIG items
float ARM_LENGTH; // arm is 1m long PPMA CONFIG,ARMLENGTH,<value> default 1.0 (meters)
float LEG_LENGTH; // leg is 1.5m long PPMA CONFIG,LEGLENGTH,<value> default 1.5 (meters)
integer MELEEATTACKDICE; // 1 attack dice for fists and feet PPMA CONFIG,MELEEATTACKDICE,<value> default 1
string ANIM_PUNCH_LEFT; // anim for left punch PPMA CONFIG,ANIMPUNCHLEFT,<value> default punch_l
string ANIM_PUNCH_RIGHT; // anim for right punch PPMA CONFIG,ANIMPUNCHRIGHT,<value> default punch_r
string ANIM_PUNCH_ONETWO; // anim for 1-2 punch PPMA CONFIG,ANIMPUNCHONETWO,<value> default punch_onetwo
string ANIM_KICK; // anim for kick PPMA CONFIG,ANIMKICK,<value> default kick_roundhouse_r
integer SINGLE_PUNCH_DELAY; // time between a punch TODO fix to Myriad rules times PPMA CONFIG,SINGLEPUNCHDELAY,<value> default 1
integer DOUBLE_PUNCH_DELAY; // time between one-two punch TODO fix to Myriad rules times PPMA CONFIG,DOUBLEPUNCHDELAY,<value> default 2
integer KICK_DELAY; // recovery time between kicks TODO fix to Myriad rules times PPMA CONFIG,KICKDELAY,<value> default 3
float FIELD_OF_ATTACK; // controls field of attack. PI/6 = 60 degree field of attack PPPMA CONFIG,FIELDOFATTACK,<value> default PI
 
// RUNTIME GLOBALS - CAN CHANGE DURING RUN
integer TIME_NEXT_ATTACK; // time of last attack
integer CONTROLS; // bitfield of controls to monitor
float WEAPON_LENGTH; // weapon length in last attack
 
//
// 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);
}
 
//
// PPMA GET_CONFIG
//
string GET_CONFIG(string config) {
    config = llToUpper(config);
    string out = GET_VAL("CONFIG",config);
    if ( out == KEY_NOT_FOUND ) {
        if ( config == "ARMLENGTH" ) out = "1.0";
        if ( config == "LEGLENGTH" ) out = "1.5";
        if ( config == "MELEEATTACKDICE" ) out = "1";
        if ( config == "ANIMPUNCHLEFT" ) out = "punch_l";
        if ( config == "ANIMPUNCHRIGHT" ) out = "punch_r";
        if ( config == "ANIMPUNCHONETWO" ) out = "punch_onetwo";
        if ( config == "ANIMKICK" ) out = "kick_roundhouse_r";
        if ( config == "SINGLEPUNCHDELAY" ) out = "1";
        if ( config == "DOUBLEPUNCHDELAY" ) out = "2";
        if ( config == "KICKDELAY" ) out = "3";
        if ( config == "FIELDOFATTACK") out = (string)PI;
        SET_VAL("CONFIG",config,out);
    }
    return out;
}
 
//
// 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 fist fighter
COMBATOFF() {
    SET_FLAG("FISTS",FALSE); // disable flag to exit/ignore any more control events
    if ( GET_FLAG("CONTROLS") == TRUE ) { // do we have control permission?
        llReleaseControls(); // release the controls
        SET_FLAG("CONTROLS",FALSE); // remember that we released controls
    }
    OWNERSAY("Close Combat Deactivated");
}
 
// COMBATON - turn on fist fighter
COMBATON() {
    SET_FLAG("FISTS",TRUE); // yep, using fist fighter, for control events
    if ( GET_FLAG("CONTROLS") == FALSE ) { // do we have permission to read controls? No? we need it.
        llReleaseControls(); // release any previous controls on avatar
        llRequestPermissions(llGetOwner(),PERMISSION_TAKE_CONTROLS|PERMISSION_TRIGGER_ANIMATION); // request permissions needed for fist fighter
    }
    OWNERSAY("Close Combat Activated");
}
 
// DEBUG - show debug chat with wearer name for sorting
DEBUG(string dmessage) {
    if ( GET_FLAG("DEBUG") == TRUE ) llMessageLinked(LINK_THIS,MODULE_CLOSE,"DEBUG|"+dmessage,llGetOwner());
}
 
// ERROR - show errors on debug channel with wearer name for sorting
ERROR(string emessage) {
    llMessageLinked(LINK_THIS,MODULE_CLOSE,"ERROR|"+emessage,llGetOwner());
}
 
// FISTSOFF
FISTSOFF() {
    SET_FLAG("FISTS",FALSE); // turn off fist fighter
    if ( GET_FLAG("CONTROLS") == TRUE ) { // if we own controls...
        llReleaseControls(); // release them
        SET_FLAG("CONTROLS",FALSE); // and remember
    }
}
 
// FISTSON
FISTSON() {
    SET_FLAG("FISTS",TRUE); // turn fist fighter back on
    if ( GET_FLAG("CONTROLS") == FALSE ) { // if we don't have controls
        llReleaseControls(); // release them just in case
        llRequestPermissions(llGetOwner(),PERMISSION_TAKE_CONTROLS|PERMISSION_TRIGGER_ANIMATION); // then ask to take controls
    }
}
 
// MEMORY
GET_MEMORY() {
    OWNERSAY(BASENAME+" free memory: "+(string)llGetFreeMemory());
}
 
// GETVERSION
GET_VERSION() {
    OWNERSAY(BASENAME+" v"+VERSION+"-"+VERSIONDATE);
}
 
// HAND_TO_HAND attack for fist fighter
// TODO fix timing to Myriad rules
HAND_TO_HAND(integer delay,string anim,float reach) {
    // TODO need "someone moves to attack" RP event messages here?
    TIME_NEXT_ATTACK = llGetUnixTime() + delay; // attack again after delay for attack and followup recovery
    llStartAnimation(anim); // run the punch left animation
    WEAPON_LENGTH = reach; // save the weapon reach from the last attack
    llSensor("",NULL_KEY,(AGENT|ACTIVE|PASSIVE),reach,FIELD_OF_ATTACK); // sensor sweep to see if we hit someone
}
 
//
// OWNERSAY
//
OWNERSAY(string msg) {
    llMessageLinked(LINK_THIS,MODULE_CLOSE,"OWNERSAY|"+msg,llGetOwner());    
}
 
// 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
}
 
 
// RESET - shut down running animations then reset the script to reload character sheet
RESET() {
    // stop all running animations
    if ( GET_FLAG("ANIMATE") == TRUE ) { // do we have permission to animate?
        list anims = llGetAnimationList(llGetOwner()); // get list of current animations for owner
        integer animcount = llGetListLength(anims); // count the number of animations in the list
        while (animcount--) { // step from end of animation list to beginning
            llStopAnimation(llList2String(anims,animcount)); // stopping each animation
        }
    }
    llResetScript(); // now reset
}
 
// RPEVENT
RPEVENT(string rpevent) {
    llMessageLinked(LINK_THIS,MODULE_CLOSE,"RPEVENT|"+rpevent,llGetOwner());
}
 
// SETUP - begin bringing the HUD online
SETUP() {
 
    // setup bitfield of controls we're going to monitor in fist fighter mode
    CONTROLS = CONTROL_ML_LBUTTON | CONTROL_LBUTTON | CONTROL_FWD | CONTROL_BACK | CONTROL_ROT_LEFT | CONTROL_LEFT | CONTROL_RIGHT | CONTROL_ROT_RIGHT | CONTROL_UP | CONTROL_DOWN;
    FIELD_OF_ATTACK = PI/6; // set fist fighter field of attack to +/- 30 degree cone from direction avatar faces - PERSONAL CHOICE NOT IN MYRIAD RULES
    llRequestPermissions(llGetOwner(),PERMISSION_TRIGGER_ANIMATION|PERMISSION_TAKE_CONTROLS);
 
    ARM_LENGTH = (float)GET_CONFIG("ARMLENGTH");
    LEG_LENGTH = (float)GET_CONFIG("LEGLENGTH");
    MELEEATTACKDICE = (integer)GET_CONFIG("MELEEATTACKDICE");
    ANIM_PUNCH_LEFT = GET_CONFIG("ANIMPUNCHLEFT");
    ANIM_PUNCH_RIGHT = GET_CONFIG("ANIMPUNCHRIGHT");
    ANIM_PUNCH_ONETWO = GET_CONFIG("ANIMPUNCHONETWO");
    ANIM_KICK = GET_CONFIG("ANIMKICK");
    SINGLE_PUNCH_DELAY = (integer)GET_CONFIG("SINGLEPUNCHDELAY");
    DOUBLE_PUNCH_DELAY = (integer)GET_CONFIG("DOUBLEPUNCHDELAY");
    KICK_DELAY = (integer)GET_CONFIG("KICKDELAY");
    FIELD_OF_ATTACK = (float)GET_CONFIG("FIELDOFATTACK");
    OWNERSAY("Close Combat module active.");
}
 
// DEFAULT STATE - load character sheet
default {
 
    // CHANGED - triggered for many changes to the avatar
    changed(integer changes) {
        if ( changes & CHANGED_REGION || changes & CHANGED_TELEPORT ) {
            llRequestPermissions(llGetOwner(),PERMISSION_TRIGGER_ANIMATION|PERMISSION_TAKE_CONTROLS);
        }
    }
 
    // CONTROL - read arrow keys and mouse button in first or third person mode
    control(key id,integer level,integer edge) {
        id = NULL_KEY; // LSLINT
        if ( GET_FLAG("DEAD") == TRUE || GET_FLAG("INCAPACITATED") == TRUE ) return; // dead or incapacitated can't fight
        if ( GET_FLAG("FISTS") == FALSE ) return; // not using fist fighter
        if ( GET_FLAG("ANIMATE") == FALSE ) return; // can't show animations
        if ( llGetUnixTime() <= TIME_NEXT_ATTACK ) return; // too soon since last attack
 
        // Is the mouse button held down?
        if ( ( level & CONTROL_LBUTTON ) || ( level & CONTROL_ML_LBUTTON ) ) {
            // Mouse + Left Arrow = left-handed punch
            if ( ( edge & CONTROL_LEFT ) || ( edge & CONTROL_ROT_LEFT ) ) {
                // TODO fix timing to Myriad rules
                HAND_TO_HAND(SINGLE_PUNCH_DELAY,ANIM_PUNCH_LEFT,ARM_LENGTH); // left punch with 1m reach, 1 second recover
                return;
            }
            // Mouse + Rigth Arrow = right-handed punch
            if ( ( edge & CONTROL_RIGHT ) || ( edge & CONTROL_ROT_RIGHT ) ) {
                // TODO fix timing to Myriad rules
                HAND_TO_HAND(SINGLE_PUNCH_DELAY,ANIM_PUNCH_RIGHT,ARM_LENGTH); // right punch, 1m reach, 1 second recover
                return;
            }
            if ( ( edge & CONTROL_UP ) || ( edge & CONTROL_FWD ) ) {
                // TODO fix timing to Myriad rules
                HAND_TO_HAND(DOUBLE_PUNCH_DELAY,ANIM_PUNCH_ONETWO,ARM_LENGTH); // left-right combo, 1m reach, 2 second recover
                return;
            }
            if ( ( edge & CONTROL_DOWN ) || ( edge & CONTROL_BACK ) ) {
                // TODO fix timing to Myriad rules
                HAND_TO_HAND(KICK_DELAY,ANIM_KICK,LEG_LENGTH); // kick, 1.5m reach, 3 second recover
                return;
            }
        } // end if mouse button held down
    } // end of control event
 
    // 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_CLOSE || sending_module == LM_SENDTOATTACHMENT ) return; // ignore our own messages
 
        list fields = llParseString2List(str,["|"],[]); // 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 message
 
        // break down the rest of the message
        //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 debug after we've ignore stuff for other modules
        DEBUG("EVENT: link_message("+(string)sender+","+(string)sending_module+","+str+","+(string)id+")");
 
        if ( command == "memory" ) { GET_MEMORY(); return; }
        if ( command == "reset" ) { RESET(); return; }
        if ( command == "version" ) { GET_VERSION(); return; }
 
        if ( command == "combatoff" ) { COMBATOFF(); return; }
        if ( command == "combaton" ) { COMBATON(); return; }
        if ( command == "attachmelee" || command == "attachranged" ) { FISTSOFF(); return;}
        if ( command == "detachmelee" || command == "detachranged" ) { FISTSON(); return; }
 
        // we've been hit and have to make an opposed ability test to avoid it
        if ( command == "closehit" ) { // 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 stat value "+(string)attackstat+" out of range: "+(string)GET_MYRIAD("MINSTAT")+"-"+(string)GET_MYRIAD("MAXSTAT"));
                // TODO 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"));
                // TODO 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 dice value out of range: "+(string)GET_MYRIAD("MINDAMAGE")+"-"+(string)GET_MYRIAD("MAXDAMAGE"));
                // TODO make a tattletale RP event?
                return;
            }
            integer skillamount = 0; // create a place to hold the defenders mortal combat skill rank
            skillamount = GET_SKILL("Close Combat"); // get close 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 been hit in close combat by "+ownername+"'s "+item+"!");
                llMessageLinked(LINK_THIS,MODULE_CLOSE,"HIT|"+(string)attackdice,owner); // Send HIT to the Message Bus
            }
            return;
        }
        // Actions NOT Allowed When Dead/Incapacitated go below here
        if ( GET_FLAG("DEAD") == TRUE || GET_FLAG("INCAPACITATED") == TRUE ) return;
        // If Your Bullet has hit, fire a hitcheck regionwide at targetplayer's channel
        if ( command == "closecombat" ) {
            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 attskill = 0; // zero our attack skill
            attskill = GET_SKILL("Close Combat"); // get close combat skill level
            llRegionSay(victimchan,"CLOSEHIT"+"|"+(string)GET_STATISTIC("Power")+"|"+(string)attskill+"|"+(string)attdice+"|"+bywho+"|"+bywhat); // attack!
            return;
        } // end if CLOSECOMBAT/RANGEDCOMBAT/TOHID
    }
 
    // NO_SENSOR - this is called when the attack sensor detects nothing in range and field of attack
    no_sensor() {
        // here to fix rare bugs where sensor fails unles no_sensor is in state too
    }
 
    // RUN_TIME_PERMISSIONS
    run_time_permissions(integer perm) {
        if ( perm & PERMISSION_TAKE_CONTROLS ) { // was script granted permission to take avatar controls?
            llTakeControls(CONTROLS,TRUE,TRUE); // then take them, but still pass them to other scripts like vehicles
            SET_FLAG("CONTROLS",TRUE); // remember that we got permission for this
        }
        if ( perm & PERMISSION_TRIGGER_ANIMATION ) { // we script granted permission to trigger animations on the avatar?
            SET_FLAG("ANIMATE",TRUE); // remember that we got permission for this
        }
    }
 
    // SENSOR for who was in attack range and field of attack
    sensor(integer num_detected) {
        while(num_detected--) { // count down all results in range and field of attack
            key hitwho = llDetectedKey(num_detected); // key of who or what we hit
            string name = llDetectedName(num_detected); // name of who we hit
            integer attskill = GET_SKILL("Close Combat"); // get our close combat skill rank
            integer victimchan = (integer)("0x"+llGetSubString(hitwho,0,6)); // calculate dynamic channel of who we hit
            RPEVENT("strikes at "+name+" in Close Combat!");
            // tell victim HUD to perform a CLOSE COMBAT opposed ability test
            // attacker Power stat/Close Combat skill rank vs. Defender Grace stat/Close Combat skill rank
            // See Myriad PDF pp. 21-22 and Myriad Special Edition pp.27-28
            llRegionSay(victimchan,"CLOSEHIT"+"|"+(string)GET_STATISTIC("Power")+"|"+(string)attskill+"|"+(string)MELEEATTACKDICE+"|"+(string)llGetOwner()+"|"+"fists and feet");
            OWNERSAY("You struck at "+name+" in Close Combat");
        } // end while
    } // end sensor
 
    // STATE ENTRY
    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", "Close 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. Close 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_CLOSE || 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