User:Allen Kerensky/Myriad Lite/Myriad Lite Module Skill Close Combat-v0.0.1-20120826.lsl
From OpenSimulator
< User:Allen Kerensky | Myriad Lite
Revision as of 13:59, 26 August 2012 by Allen Kerensky (Talk | contribs)
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