User:Allen Kerensky/Myriad Lite/Myriad Lite Module Skill Close Combat-v0.0.1-20120826.lsl

From OpenSimulator

Jump to: navigation, search

Myriad_Lite_Module_Skill_Close_Combat-v0.0.1-20120826.lsl

// Myriad_Lite_Module_Skill_Close_Combat-v0.0.1-20120826.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/
 
// CONSTANTS - DO NOT CHANGE DURING RUN
string VERSION = "0.0.1"; // Allen Kerensky's script version
string VERDATE = "20120826"; // Allen Kerensky's script yyyymmdd
integer CHANMYRIAD = -999; // Myriad Lite Region Channel Number
integer MINSTAT = 1; // min value for statistics
integer MAXSTAT = 5; // max human value for a statistic/attribute
integer MINSKILL = 1; // min value for skill rank
integer MAXSKILL = 5; // max value for skill rank
integer MINDAMAGE = 1; // min attack dice for weapon
integer MAXDAMAGE = 5; // max attack dice for weapon
string DIV = "|"; // message field divider
float WEAPON_LENGTH = 0.0; // weapon length in last attack
float ARM_LENGTH = 1.0; // arm is 1m long
float LEG_LENGTH = 1.5; // leg is 1.5m long
integer MELEEATTACKDICE = 1; // 1 attack dice for fists and feet
string ANIM_PUNCH_LEFT = "punch_l"; // anim for left punch
string ANIM_PUNCH_RIGHT = "punch_r"; // anim for right punch
string ANIM_PUNCH_ONETWO = "punch_onetwo"; // anim for 1-2 punch
string ANIM_KICK = "kick_roundhouse_r"; // anim for kick
integer SINGLE_PUNCH_DELAY = 1; // recovery time between single punches TODO fix to Myriad rules times
integer DOUBLE_PUNCH_DELAY = 2; // recovery time between one-two punches TODO fix to Myriad rules times
integer KICK_DELAY = 3; // recovery time between kicks TODO fix to Myriad rules times
 
// Module to Module Messaging Constants
// integer MODULE_HUD = -1;
// integer MODULE_CHARSHEET = -2;
// integer MODULE_ARMOR = -3;
// integer MODULE_BAM = -4;
// integer MODULE_RUMORS = -5;
integer MODULE_CLOSE = -6;
// integer MODULE_RANGED = -7;
// integer MODULE_RESILIENCE = -8;
// integer MODULE_PROGRESS = -9;
// integer MODULE_WELL = -10;
// integer MODULE_METER = -11;
integer LM_SENDTOATTACHMENT = 0x80000000;
 
// RUNTIME GLOBALS - CAN CHANGE DURING RUN
integer FLAG_DEBUG; // see debug messages?
key PLAYERID = NULL_KEY; // cached player UUID
string PLAYERNAME = ""; // cached player name
string NAME = ""; // character name
list STATISTICS = [];
list SKILLS = []; // skills [ string SkillName, integer SkillRank ]
integer FLAG_INCAPACITATED = FALSE; // incapacitated by wounds?
integer FLAG_DEAD = FALSE; // killed by critical wounds?
integer FLAG_FISTS = FALSE; // using fist-fighter?
integer FLAG_CONTROLS = FALSE; // permission to take controls?
integer FLAG_ANIMATE = FALSE; // permission to animate avatar?
integer TIME_NEXT_ATTACK = 0; // time of last attack
integer CONTROLS = 0; // bitfield of controls to monitor
float FIELD_OF_ATTACK = PI; // controls field of attack. set later to PI/6 later for 60 degree field of attack in front of avatar
 
// 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(5.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() {
    FLAG_FISTS = FALSE; // disable flag to exit/ignore any more control events
    if ( FLAG_CONTROLS == TRUE ) { // do we have control permission?
        llReleaseControls(); // release the controls
        FLAG_CONTROLS = FALSE; // remember that we released controls
    }
    llOwnerSay("Close Combat Deactivated");
}
 
// COMBATON - turn on fist fighter
COMBATON() {
    FLAG_FISTS = TRUE; // yep, using fist fighter, for control events
    if ( FLAG_CONTROLS == FALSE ) { // do we have permission to read controls? No? we need it.
        llReleaseControls(); // release any previous controls on avatar
        llRequestPermissions(PLAYERID,PERMISSION_TAKE_CONTROLS|PERMISSION_TRIGGER_ANIMATION); // request permissions needed for fist fighter
    }
    llOwnerSay("Close Combat Activated");
}
 
// DEBUG - show debug chat with wearer name for sorting
DEBUG(string dmessage) {
    if ( FLAG_DEBUG == TRUE ) { // are we debugging?
        llSay(DEBUG_CHANNEL,"("+llKey2Name(PLAYERID)+") MODULE CLOSE: "+dmessage);
    }
}
 
// DEBUGON - turn on the DEBUG flag
DEBUGON() {
    FLAG_DEBUG = TRUE; // set debug flag TRUE
    llOwnerSay("Debug Mode Activated");
}
 
// DEBUGOFF - turn off the DEBUG flag
DEBUGOFF() {
    FLAG_DEBUG = FALSE; // set debug flag to FALSE
    llOwnerSay("Debug Mode Deactivated");
}
 
// ERROR - show errors on debug channel with wearer name for sorting
ERROR(string emessage) {
    llSay(DEBUG_CHANNEL,"ERROR ("+llKey2Name(PLAYERID)+"): "+emessage);
}
 
// FISTSOFF
FISTSOFF() {
    FLAG_FISTS = FALSE; // turn off fist fighter
    if ( FLAG_CONTROLS == TRUE ) { // if we own controls...
        llReleaseControls(); // release them
        FLAG_CONTROLS = FALSE; // and remember
    }
}
 
// FISTSON
FISTSON() {
    FLAG_FISTS = TRUE; // turn fist fighter back on
    if ( FLAG_CONTROLS == FALSE ) { // if we don't have controls
        llReleaseControls(); // release them just in case
        llRequestPermissions(PLAYERID,PERMISSION_TAKE_CONTROLS|PERMISSION_TRIGGER_ANIMATION); // then ask to take controls
    }
}
 
// GET SKILL RANK
// Requires a SKILL NAME
// Returns the rank value for that skill, or zero if player doesn't have skill
integer GET_SKILL_RANK(string askill) {
    integer atskill = 0; // start with skill zero in case character does not have that skill
    integer skillpos = llListFindList(SKILLS,[askill]); // position of skill name in list 0-n, or -1 if not found
    if ( skillpos >= 0 ) { // skill name was somewhere in list
        atskill = llList2Integer(SKILLS,++skillpos); // move to next pos in list after skillname and get skill rank there
        if ( atskill >= MINSKILL && atskill <= MAXSKILL ) return atskill; // found skill with value in range, return it;
    } // fall through...
    return 0; // player has zero levels in skill
}
 
// GETSTAT
integer GETSTAT(string name) {
    integer pos = llListFindList(STATISTICS,[name]);
    if ( pos >= 0 ) {
        return llList2Integer(STATISTICS,pos + 1);
    }
    return 0;
}
 
// GETVERSION
GETVERSION() {
    SENDTOHUD("VERSION="+VERSION+DIV+"VERSIONDATE="+VERDATE+DIV+llGetObjectName());
}
 
// 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
}
 
// MEMORY
MEMORY() {
    DEBUG("Free Memory: "+(string)llGetFreeMemory());
}
 
// 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 ( FLAG_ANIMATE == TRUE ) { // do we have permission to animate?
        list anims = llGetAnimationList(PLAYERID); // 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) {
    llRegionSay(CHANMYRIAD,"RPEVENT|"+NAME+" ("+PLAYERNAME+") "+rpevent);
}
 
// SENDTOHUD - send reponses to HUD as Link Messages
SENDTOHUD(string str) {
    DEBUG("SENDTOHUD("+str+")");
    llMessageLinked(LINK_THIS,LM_SENDTOATTACHMENT,str,PLAYERID);    
}
 
// SETUP - begin bringing the HUD online
SETUP() {
    FLAG_DEBUG = FALSE;
    PLAYERID = llGetOwner(); // remember the owner's UUID
    PLAYERNAME = llKey2Name(PLAYERID); // remember the owner's legacy name
}
 
// 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(PLAYERID,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 ( FLAG_DEAD == TRUE || FLAG_INCAPACITATED == TRUE ) return; // dead or incapacitated can't fight
        if ( FLAG_FISTS == FALSE ) return; // not using fist fighter
        if ( 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
        DEBUG("EVENT: link_message("+(string)sender+","+(string)sending_module+","+str+","+(string)id+")");
 
        list fields = llParseString2List(str,[DIV],[]); // break line of text into = delimited fields
        string command = llToLower(llStringTrim(llList2String(fields,0),STRING_TRIM)); // field zero is the "command"
        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
        if ( command == "set_statistic" ) {
            string statname = llList2String(subfields,0); // find the boon name
            integer statrank = llList2Integer(subfields,1); // find the boon rank value
            // TODO how to verify stat names are valid?
            if ( statrank >= MINSTAT && statrank <= MAXSTAT ) { // rank valid?
                STATISTICS = [statname,statrank] + STATISTICS; // add statistic to list
            } else { // invalid, report it
                ERROR("STATISTIC "+statname+" rank "+(string)statrank+" value out of allowed range: "+(string)MINSTAT+"-"+(string)MAXSTAT);
            }
            return;
        }
 
        if ( command == "set_skill" ) {
            string skillname = llList2String(subfields,0); // find the skill name
            integer skillrank = llList2Integer(subfields,1); // find the skill rank
            // TODO how to verify skill names are valid?
            if ( skillrank >= MINSKILL && skillrank <= MAXSKILL ) { // skill rank valid?
                if ( skillname == "Close Combat" )  {
                    SKILLS = [skillname,skillrank] + SKILLS; // add skill to list
                }
            } else { // invalid, report it
                ERROR("SKILL "+skillname+" rank "+(string)skillrank+" value out of allowed range: "+(string)MINSKILL+"-"+(string)MAXSKILL);
            }
            return;
        }
        if ( command == "combatoff" ) { COMBATOFF(); return; }
        if ( command == "combaton" ) { COMBATON(); return; }
        if ( command == "debugoff" ) { DEBUGOFF(); return; }
        if ( command == "debugon" ) { DEBUGON(); return; }
        if ( command == "attachmelee" || command == "attachranged" ) { FISTSOFF(); return;}
        if ( command == "detachmelee" || command == "detachranged" ) { FISTSON(); return; }
        if ( command == "reset" ) { RESET(); return; }
        if ( command == "memory" ) { MEMORY(); return; }
        if ( command == "version" ) { GETVERSION(); return; }
        if ( command == "dead" ) { FLAG_DEAD = TRUE; return; }
        if ( command == "alive" ) { FLAG_DEAD = FALSE; return; }
        if ( command == "incapacitated" ) { FLAG_INCAPACITATED = TRUE; return; }
        if ( command == "revived" ) { FLAG_INCAPACITATED = FALSE; 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 < MINSTAT || attackstat > MAXSTAT ) { // is the attack stat value out of allowed range?
                ERROR("Attack stat value "+(string)attackstat+" out of range: "+(string)MINSTAT+"-"+(string)MAXSTAT);
                // TODO make a tattletale RP event?
                return;
            }
            if ( attackskill < MINSKILL || attackstat > MAXSKILL ) { // is the attack skill value out of allowed range?
                ERROR("Attack skill value "+(string)attackskill+" out of range: "+(string)MINSKILL+"-"+(string)MAXSKILL);
                // TODO make a tattletale RP event?
                return;
            }
            if ( attackdice < MINDAMAGE || attackdice > MAXDAMAGE ) { // is the attacking weapon's attack dice value out of allowed range?
                ERROR("Attack dice value out of range: "+(string)MINDAMAGE+"-"+(string)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_RANK("Close Combat"); // get close combat skill rank
            // see if we're hit
            integer amihit = OPPOSED_TEST(attackstat,attackskill,GETSTAT("Grace"),skillamount); // attacker power+skill vs. defender grace+skill
            if ( amihit == TRUE ) { // we're hit!
                llOwnerSay("You been hit in close combat by "+llKey2Name(owner)+"'s "+item+"!");
                llMessageLinked(LINK_THIS,MODULE_CLOSE,"HIT|"+(string)attackdice,llGetOwner()); // Send HIT to the Message Bus
            }
            return;
        }
        // Actions NOT Allowed When Dead/Incapacitated go below here
        if ( FLAG_DEAD == TRUE || 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_RANK("Close Combat"); // get close combat skill level
            llRegionSay(victimchan,"CLOSEHIT"+DIV+(string)GETSTAT("Power")+DIV+(string)attskill+DIV+(string)attdice+DIV+bywho+DIV+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
            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?
            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_RANK("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"+DIV+(string)GETSTAT("Power")+DIV+(string)attskill+DIV+(string)MELEEATTACKDICE+DIV+(string)PLAYERID+DIV+"fists and feet");
            llOwnerSay("You struck at "+name+" in Close Combat");
        } // end while
    } // end sensor
 
    // STATE ENTRY
    state_entry() {
        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(PLAYERID,PERMISSION_TRIGGER_ANIMATION|PERMISSION_TAKE_CONTROLS);
    }
 
} // end state
// END
Personal tools
General
About This Wiki