include library.beast_lib; include library.colors; include library.weapons; include library.utils; include library.jedi; include library.armor; include library.heavyweapons; include library.movement; include library.buff; include library.space_utils; include library.group; include library.static_item; include library.prose; // DO NOT UNCOMMENT THIS AND SUBMIT - IT'S FOR LOCAL TESTING ONLY! //#define COMBATLOG 1; // Regen multipliers const float IN_COMBAT_REGEN_MULT = 1.0f; const float OUT_COMBAT_REGEN_MULT = 4.0f; const int PLAYER_COMBAT_BASE_DAMAGE = 60; const int PLAYER_ATTACKER_DAMAGE_LEVEL_MULTIPLIER = 5; // old weapon type defines const int RANGED_WEAPON = -1; const int MELEE_WEAPON = -2; const int ALL_WEAPONS= -3; const int ALL_LIGHTSABERS = -4; const int FORCE_POWER = -5; const int ALL_BUT_HEAVY = -6; const int WEAPON_TYPE_FORCE_POWER = -1; const int LEFT_CLICK_DEFAULT = 1; const int RIGHT_CLICK_SPECIAL = 2; // weapon type defines //Bit values from command_table.tab: // // RIFLE=1,CARBINE=2, PISTOL=3,HEAVY=4, 1HAND_MELEE=5, 2HAND_MELEE=6, UNARMED=7, POLEARM=8, THROWN=9, // 1HAND_LIGHTSABER=10, 2HAND_LIGHTSABER=11,POLEARM_LIGHTSABER=12, RANGED=28, MELEE=29, ALL=30, // ALL_LIGHTSABERS=31, ALL_BUT_HEAVY=32 const int B_RIFLE= 0x00000001; const int B_CARBINE= 0x00000002; const int B_PISTOL= 0x00000004; const int B_HEAVY= 0x00000008; const int B_1HAND_MELEE= 0x00000010; const int B_2HAND_MELEE= 0x00000020; const int B_UNARMED= 0x00000040; const int B_POLEARM= 0x00000080; const int B_THROWN= 0x00000100; const int B_1HAND_LIGHTSABER= 0x00000200; const int B_2HAND_LIGHTSABER= 0x00000400; const int B_POLEARM_LIGHTSABER= 0x00000800; const int B_GROUND_TARGETTING= 0x00001000; const int B_DIRECTIONAL= 0x00002000; const int B_RANGED= 0x08000000; const int B_MELEE= 0x10000000; const int B_ALL= 0x20000000; const int B_ALL_LIGHTSABERS= 0x40000000; const int[] weaponTypeToBitValueMap = { B_ALL | B_RANGED | B_RIFLE, //WEAPON_TYPE_RIFLE B_ALL | B_RANGED | B_CARBINE, //WEAPON_TYPE_LIGHT_RIFLE B_ALL | B_RANGED | B_PISTOL, //WEAPON_TYPE_PISTOL B_ALL | B_RANGED | B_HEAVY, //WEAPON_TYPE_HEAVY B_ALL | B_MELEE | B_1HAND_MELEE, //WEAPON_TYPE_1HAND_MELEE B_ALL | B_MELEE | B_2HAND_MELEE, //WEAPON_TYPE_2HAND_MELEE B_ALL | B_MELEE | B_UNARMED, //WEAPON_TYPE_UNARMED B_ALL | B_MELEE | B_POLEARM, //WEAPON_TYPE_POLEARM B_ALL | B_RANGED | B_THROWN, //WEAPON_TYPE_THROWN B_ALL | B_ALL_LIGHTSABERS | B_1HAND_LIGHTSABER, //WEAPON_TYPE_WT_1HAND_LIGHTSABER B_ALL | B_ALL_LIGHTSABERS | B_2HAND_LIGHTSABER, //WEAPON_TYPE_WT_2HAND_LIGHTSABER B_ALL | B_ALL_LIGHTSABERS | B_POLEARM_LIGHTSABER, //WEAPON_TYPE_WT_POLEARM_LIGHTSABER B_ALL | B_RANGED | B_HEAVY | B_GROUND_TARGETTING, //WEAPON_TYPE_GROUND_TARGETTING B_ALL | B_RANGED | B_HEAVY | B_DIRECTIONAL //WEAPON_TYPE_DIRECTIONAL }; // ---------- const int EFFECT_MAX_DURATION = 180; const float DELAY_EFFECT_TIMER = 30f; const int TKA_JEDI_ARMOR_HARD_CAP = 9000; const int MAX_RE_EXOTIC_ELEMENTAL_PENETRATION_SKILLMOD = 10; // Max reuse time on snare/root const int MOVEMENT_MODIFIER_REUSE_TIME = 30; const int AI_LEVEL_ACCURACY_BONUS = 65; const int AI_LEVEL_DEFENSE_BONUS = 65; const int AI_LEVEL_DAMAGE_BONUS = 40; const int AI_LEVEL_DAMAGE_PENALTY = 25; const int AI_LEVEL_MIN_DAMAGE_CAP = 50; const int AI_LEVEL_MAX_DAMAGE_CAP = 75; const float AI_CON_SCALE_MULT = 8f; //target validation defines const int VALID_TARGET_NONE = -1; const int VALID_TARGET_STANDARD = 0; const int VALID_TARGET_MOB = 1; const int VALID_TARGET_CREATURE = 2; const int VALID_TARGET_NPC = 3; const int VALID_TARGET_DROID = 4; const int VALID_TARGET_PVP = 5; const int VALID_TARGET_JEDI = 6; const int VALID_TARGET_DEAD = 7; const int VALID_TARGET_FRIEND = 8; const int NON_ATTACK = 0; const int NON_DAMAGE_ATTACK = 4; const int HEAL = 5; const int DELAY_ATTACK = 6; const int REVIVE = 7; const int ACTION_NAME = 0; const int WEAPON_NAME = 1; const int NO_ATTACK_NAME = 2; const int NO_HIT_SPAM = 3; // Armor break reduction const float ARMOR_BREAK_REDUCTION = 0.5f; // Cached Attacker Data const string ATTACKER_DATA = "cached_attacker_data"; const string ATTACKER_BASE_CRIT = ATTACKER_DATA+".base_crit"; const string ATTACKER_PVP_CRIT = ATTACKER_DATA+".pvp_crit"; const string ATTACKER_DROID_CRIT = ATTACKER_DATA+".droid_crit"; const string ATTACKER_NPC_CRIT = ATTACKER_DATA+".npc_crit"; const string ATTACKER_CREATURE_CRIT = ATTACKER_DATA+".creature_crit"; const string ATTACKER_HIT_BONUS = ATTACKER_DATA+".hit_bonus"; const string ATTACKER_STRIKETHROUGH = ATTACKER_DATA+".strikethrough_chance"; const string ATTACKER_REDUCE_PARRY = ATTACKER_DATA+".reduce_parry"; const string ATTACKER_REDUCE_BLOCK = ATTACKER_DATA+".reudce_block"; const string ATTACKER_REDUCE_DODGE = ATTACKER_DATA+".reduce_dodge"; const string ATTACKER_REDUCE_GLANCING = ATTACKER_DATA+".reduce_glancing"; const string ATTACKER_REDUCE_EVADE = ATTACKER_DATA+".reduce_evade"; const string DEFENDER_DATA = "cached_defender_data"; const string DEFENDER_REDUCE_CRIT = DEFENDER_DATA+".crit_reduce"; const string DEFENDER_INCREASE_MISS = DEFENDER_DATA+".increase_miss"; const string DEFENDER_REDUCE_STRIKETHROUGH = DEFENDER_DATA+".reduce_strikethrough"; const string DEFENDER_REDUCE_PUNISH = DEFENDER_DATA+".reduce_punish"; const string DEFENDER_GLANCING = DEFENDER_DATA+".increase_glancing"; const string DEFENDER_PARRY = DEFENDER_DATA+".parry_chance"; const string DEFENDER_DODGE = DEFENDER_DATA+".dodge_chance"; const string DEFENDER_EVADE = DEFENDER_DATA+".evade_chance"; const string DEFENDER_BLOCK = DEFENDER_DATA+".block_chance"; // Armor slot hit location defines const string[][] ARMOR_SLOT_LOCATIONS = { { "chest2", "chest2" }, //body { "hat", "hat" }, //head { "bicep_r", "bracer_upper_r" }, //right arm { "bicep_l", "bracer_upper_l" }, //left arm { "pants2", "pants2" }, //right leg { "pants2", "pants2" }//left leg }; const string PSG_SLOT_LOCATION = "utility_belt"; // values used in the psg damage calculation const float PSG_BASE_DMG_PER_HIT = 50.0f; const float PSG_DAMAGE_SCALE = 200.0f; const int CONE = 0; const int SINGLE_TARGET= 1; const int AREA = 2; const int TARGET_AREA = 3; // area around target const int DUAL_WIELD = 4; // 2 targets sometimes const int RAMPAGE = 5; const int RANDOM_HATE_TARGET = 6; const int RANDOM_HATE_TARGET_CONE = 7; const int RANDOM_HATE_TARGET_CONE_TERMINUS = 8; const int HATE_LIST = 9; const int RANDOM_HATE_MULTI = 10; const int AREA_PROGRESSIVE = 11; const int SPLIT_DAMAGE_TARGET_AREA = 12; const int DISTANCE_FARTHEST = 13; const string NONCOMBAT_DATATABLE = "datatables/combat/non_combat_data.iff"; const string WEAPON_LEVEL_TABLE = "datatables/combat/weapon_level.iff"; const int[] RANGED_WEAPONS = { WEAPON_TYPE_RIFLE, WEAPON_TYPE_LIGHT_RIFLE, WEAPON_TYPE_PISTOL, WEAPON_TYPE_HEAVY}; const int[] MELEE_WEAPONS = { WEAPON_TYPE_1HAND_MELEE, WEAPON_TYPE_2HAND_MELEE, WEAPON_TYPE_UNARMED, WEAPON_TYPE_POLEARM, WEAPON_TYPE_WT_1HAND_LIGHTSABER, WEAPON_TYPE_WT_2HAND_LIGHTSABER, WEAPON_TYPE_WT_POLEARM_LIGHTSABER }; const string VAR_VOLLEY_GROUPS = "squadleader.volleyGroups"; // IDs of the group that has marked a target const string ID_VOLLEY_FIRE_PARTICLE = "squadleader.volleyFire"; // particle ID for volley effect const int VOLLEY_INTERVAL = 30; // how often we can volley (~ equal to the cooldown) const string VAR_GROUP_VOLLEY_TARGET = "squadleader.curVolleyTarget"; // the current volley target - used to turn off particles when player leaves group const string VAR_GROUP_LAST_VOLLEY_TIME = "squadleader.lastVolleyTime"; // last time a SL has execute Volley fire in the group const string_id SID_NONE = new string_id(); const string CHARGE_TARGET = "charge_target"; //scriptvars used to track recent death const string DIED_RECENTLY = "combat.died_recently"; const float RECENT_DEATH_EXPIRATION = 60.0f; const string DAMAGE_REDIRECT = "damage_redirect"; const string DO_ONCE_ATTACK_OVERRIDE = "default.do_once_attack_override"; const string DEFAULT_ATTACK_OVERRIDE = "default.default_attack_override"; //amount of hate to add to top hate for Jedi Taunt const int FS_TAUNT_HATE_INCREASE_LOW = 750; const int FS_TAUNT_HATE_INCREASE_MID = 1200; const int FS_TAUNT_HATE_INCREASE_HIGH = 2000; const int FS_TAUNT_LOW_LEVEL = 64; const int FS_TAUNT_MID_LEVEL = 77; //amount of hate to add to top hate for Bounty Hunter Taunt const int BH_TAUNT_HATE_INCREASE_LOW = 300; const int BH_TAUNT_HATE_INCREASE_MID = 750; const int BH_TAUNT_HATE_INCREASE_HIGH = 1200; const int BH_TAUNT_LOW_LEVEL = 43; const int BH_TAUNT_MID_LEVEL = 66; const string TEMP_COMBAT_BLOCK = "combat_debuff.all_actions_blocked"; const string PERSIST_COMBAT = "persist_combat"; const string BUFF_HATE_XFER = "aggroChannelself"; //egg aura buff constants const int EGG_AURA_TRIGGER_MIN_RANGE = 15; const int EGG_AURA_TRIGGER_MAX_RANGE = 100; const int EGG_AURA_DURATION = 20; const int DR_ATTACKER_CRITICAL = 0; const int DR_ATTACKER_STRIKETHROUGH = 1; const int DR_DEFENDER_BLOCK = 2; const int DR_DEFENDER_DODGE = 3; const int DR_DEFENDER_PARRY = 4; const int DR_DEFENDER_GLANCE = 5; const int DR_DEFENDER_EVADE = 6; const int DR_ATTACKER_CRITICAL_2 = 7; /** * Send some combat spam, from an attack result. */ void sendCombatSpam (obj_id attacker, obj_id defender, obj_id weapon, combat_engine.hit_result result, string_id attackName, boolean sendToAttacker, boolean sendToDefender, boolean sendToBystanders) { sendCombatSpam(attacker, defender, weapon, result, attackName, sendToAttacker, sendToDefender, sendToBystanders, COMBAT_RESULT_GENERIC); } /** * Send some combat spam, from an attack result. */ void sendCombatSpam (obj_id attacker, obj_id defender, string_id weapon, combat_engine.hit_result result, string_id attackName, boolean sendToAttacker, boolean sendToDefender, boolean sendToBystanders) { sendCombatSpam(attacker, defender, weapon, result, attackName, sendToAttacker, sendToDefender, sendToBystanders, COMBAT_RESULT_GENERIC); } /** * Send a generic combat spam message to one player. */ void sendCombatSpamMessage(obj_id player, string_id message) { sendCombatSpamMessage(player, message, COMBAT_RESULT_GENERIC); } /** * Send a generic combat spam message to multiple players. */ void sendCombatSpamMessage(obj_id attacker, obj_id defender, string_id message, boolean sendToAttacker, boolean sendToDefender, boolean sendToBystanders) { sendCombatSpamMessage(attacker, defender, message, sendToAttacker, sendToDefender, sendToBystanders, COMBAT_RESULT_GENERIC); } /** * Send a generic combat spam prose package to one player. */ void sendCombatSpamMessageProse(obj_id player, prose_package pp) { sendCombatSpamMessageProse(player, pp, COMBAT_RESULT_GENERIC); } /** * Send a generic combat spam prose package to multiple players. */ void sendCombatSpamMessageProse(obj_id attacker, obj_id defender, prose_package pp, boolean sendToAttacker, boolean sendToDefender, boolean sendToBystanders) { sendCombatSpamMessageProse(attacker, defender, pp, sendToAttacker, sendToDefender, sendToBystanders, COMBAT_RESULT_GENERIC); } void doBasicCombatSpam(string attackName, attacker_results attackerResults, defender_results[] defenderResults, hit_result[] hitData) { string_id attackId = new string_id("cmd_n", attackName); doBasicCombatSpam(attackId, SID_NONE, null, attackerResults, defenderResults, hitData); } void doBasicCombatSpam(string_id attackId, string_id weaponId, obj_id weapon, attacker_results attackerResults, defender_results[] defenderResults, hit_result[] hitData) { if (isIdValid(weapon)) { for (int i = 0; i < defenderResults.length; ++i) sendCombatSpam(attackerResults.id, defenderResults[i].id, weapon, hitData[i], attackId, true, true, true, defenderResults[i].result); } else { for (int i = 0; i < defenderResults.length; ++i) sendCombatSpam(attackerResults.id, defenderResults[i].id, weaponId, hitData[i], attackId, true, true, true, defenderResults[i].result); } } boolean isAreaAttack(int attackType) { if(attackType == CONE || attackType == AREA || attackType == TARGET_AREA) return true; return false; } boolean breakMez(obj_id defender) { int[] buffs = buff.getAllBuffs(defender); if(buffs == null || buffs.length == 0) return true; for(int i = 0; i < buffs.length; i++) { for(int j = 1; j <= buff.MAX_EFFECTS; j++) { string effect = buff.getEffectParam(buffs[i], j); if(effect.equals("delay_attack")) buff.removeBuff(defender, buffs[i]); } } return true; } int getAiLevelDiff(obj_id attacker, obj_id defender) { obj_id master = null; // Players don't get adjustments when attacking if (isPlayer(attacker)) return 0; // No level difference for inanimate objects if (!isPlayer(defender) && !isMob(defender)) return 0; // Pets don't get bonuses when attacking if (isMob(attacker)) { master = getMaster(attacker); if (!isPlayer(attacker) && isIdValid(master)) return 0; } // Only pets get adjusted when defending. if (isMob(defender)) { master = getMaster(defender); if (!isPlayer(defender) && !isIdValid(master)) return 0; } int aiLevel = getLevel(attacker); int defenderLevel = getLevel(defender); if (!isMob(attacker)) { aiLevel = getIntObjVar(attacker, "intCombatDifficulty"); } obj_id defenderGroup = getGroupObject(defender); if (group.isGroupObject(defenderGroup)) defenderLevel = getGroupObjectLevel(defenderGroup); int buffBonus = getEnhancedSkillStatisticModifier(defender, "private_relative_level_mod"); defenderLevel += buffBonus; return (aiLevel - defenderLevel); } float getConBalanceScalar(int levelDiff) { float scale = (Math.abs(levelDiff)+AI_CON_SCALE_MULT)/(10f+AI_CON_SCALE_MULT); return scale; } int getWeaponDamage(obj_id objAttacker, obj_id objDefender, weapon_data cbtWeaponData, int intAttackerRoll, int intDefenderRoll) { int intFinalDamage = 0; float fltMinDamage = (float)cbtWeaponData.minDamage; float fltMaxDamage= (float)cbtWeaponData.maxDamage; if(!isPlayer(objAttacker)) { if(intAttackerRoll> intDefenderRoll) { return getAiDamage(objAttacker, objDefender, fltMinDamage, fltMaxDamage); } else { return (int)rand(fltMinDamage, fltMinDamage+(fltMaxDamage - fltMinDamage)); } } if(intAttackerRoll> intDefenderRoll) { int intDamage = (int)rand(fltMinDamage+(fltMaxDamage - fltMinDamage), fltMaxDamage); return intDamage; } else { int intDamage = (int)(rand(fltMinDamage, fltMinDamage+(fltMaxDamage - fltMinDamage))); return intDamage; } } int getAiDamage(obj_id attacker, obj_id defender, float minDamage, float maxDamage) { // Ai vs Player damage scaling if (!isPlayer(attacker)) { int levelDiff = getAiLevelDiff(attacker, defender); combatLog(attacker, defender, "modifyAiRawDamage", "AI Level diff = "+levelDiff); float levelDiffMod = levelDiff * getConBalanceScalar(levelDiff); if (levelDiffMod < 0) { float damageMod = 1f + (levelDiffMod / 10f); combatLog(attacker, defender, "modifyAiRawDamage", "AI Damage Mod = "+damageMod); float minCap = minDamage / 2f; float maxCap = maxDamage / 2f; combatLog(attacker, defender, "modifyAiRawDamage", "Min Cap = "+minCap); combatLog(attacker, defender, "modifyAiRawDamage", "Max Cap = "+maxCap); minDamage *= damageMod; maxDamage *= damageMod; if (minDamage < minCap) minDamage = minCap; if (maxDamage < maxCap) maxDamage = maxCap; } else { float maxDmgMod = 1f + (levelDiffMod / 10f); combatLog(attacker, defender, "modifyAiRawDamage", "AI Max Damage Mod = "+maxDmgMod); maxDamage *= maxDmgMod; maxDamage += levelDiff; float minDmgMod = 1f + (levelDiffMod / (10f / levelDiff)); combatLog(attacker, defender, "modifyAiRawDamage", "AI Min Damage Mod = "+minDmgMod); minDamage *= minDmgMod; float minCap = maxDamage / 2f; if (minDamage > minCap) minDamage = minCap; } combatLog(attacker, defender, "modifyAiRawDamage", "New Min Damage = "+minDamage); combatLog(attacker, defender, "modifyAiRawDamage", "New Max Damage = "+maxDamage); } if(utils.checkConfigFlag("ScriptFlags","e3Demo")) { if(isPlayer(defender)) { int intCurrentHP = getHealth(defender); int intMaxHP = getMaxHealth(defender); if((minDamage>intMaxHP)||(maxDamage>intMaxHP)||(intCurrentHP< (intMaxHP/2))) { minDamage = 1; maxDamage = 5; } } } int intDamage = (int)rand(minDamage, maxDamage); return intDamage; } void assignDamageCredit(obj_id attacker, obj_id defender, weapon_data weaponData, int damage) { string xpType = xp.getWeaponXpType(weaponData.weaponType); if (!isPlayer(attacker)) { // Check to see if this is a CH pet if (pet_lib.isPet(attacker)) { obj_id master = getMaster(attacker); if (ai_lib.isMonster(attacker) && isIdValid(master) && hasSkill(master, "outdoors_creaturehandler_novice")) { // Assign CH xp credit to the master xp.updateCombatXpList(defender, master, xp.CREATUREHANDLER, damage); // Assign training xp credit to pet xp.updateCombatXpList(defender, attacker, xp.CREATUREHANDLER, damage); return; } else if (isIdValid(master) && !beast_lib.isBeast(attacker)) { // Assign pet xp to master (for master XP reductions) xp.updateCombatXpList(defender, master, xp.PET_DAMAGE, damage); // Also assign npc credit to pet for kill rights xp.updateCombatXpList(defender, attacker, xpType, damage); return; } } } xp.updateCombatXpList(defender, attacker, xpType, damage); } // Expertise chance to cast a buff/debuff boolean expertiseRandomBuffChance(obj_id attacker, combat_data actionData) { string specialLine = actionData.specialLine; if(getSkillStatisticModifier(attacker, "expertise_use_buff_chance_line_" + specialLine) > 0 || getSkillStatisticModifier(attacker, "private_use_buff_chance_line_" + specialLine) > 0) { // Get the action's specialLine value, which is a percentage chance to cast. int chance = getSkillStatisticModifier(attacker, "expertise_buff_chance_line_" + specialLine ); // Negative means that it cannot be cast. // Most buffs do not have an expertise associated with them and have no chance assigned. // Those buffs should always cast, if they're 0. If the chance is set, then check it. if(rand(0,99) > chance) { return false; } } return true; } boolean applyDefenderCombatBuffs(obj_id attacker, obj_id defender, weapon_data weaponData, combat_data actionData) { string actionName = actionData.actionName; string buffName = actionData.buffNameTarget; float buffStrength = actionData.buffStrengthTarget; float buffDuration = actionData.buffDurationTarget; if (buffName == null || buffName.equals("")) return false; if(!expertiseRandomBuffChance(attacker, actionData)) return false; combatLog(attacker, defender, "applyDefenderCombatBuffs", "Applying defender buff - " + buffName); //if this is a dot type buff, we need to check for resistance. //later this data will be in the action data, for now we have to use the less effiencent way string effectName = buff.getEffectParam(buffName, 2); if(effectName.equals("dot")) { effectName = buff.getEffectParam(buffName, 1); string type = effectName.substring((effectName.lastIndexOf("_") + 1), effectName.length()); if (dot.attemptDotResist(defender, type, 100, true)) { return false; } if(!dot.canApplyDotType(defender, type)) { return false; } } buffDuration += getExpertiseBuffDurationMods(attacker, actionData, buffName, buffDuration); return buff.applyBuff(defender, attacker, buffName, buffDuration, buffStrength); } boolean applyAttackerCombatBuffs(obj_id attacker, combat_data actionData) { string actionName = actionData.actionName; string buffName = actionData.buffNameSelf; float buffStrength = actionData.buffStrengthSelf; float buffDuration = actionData.buffDurationSelf; if (buffName == null || buffName.equals("")) return false; if(!expertiseRandomBuffChance(attacker, actionData)) return false; combatLog(attacker, null, "applyAttackerCombatBuffs", "Applying attacker buff - "+buffName); buffDuration += getExpertiseBuffDurationMods(attacker, actionData, buffName, buffDuration); return buff.applyBuff(attacker, attacker, buffName, buffDuration, buffStrength); } float getExpertiseBuffDurationMods(obj_id buffCaster, combat_data actionData, string buffName, float buffDuration) { float expertiseBuffDurationMod = 0.0f; dictionary dic = dataTableGetRow(buff.BUFF_TABLE, buffName); if (dic == null) return expertiseBuffDurationMod; string buffGroupName = dic.getString("GROUP1"); if(buffDuration == 0) buffDuration = dic.getFloat("DURATION"); string specialLine = actionData.specialLine; expertiseBuffDurationMod = buffDuration + getEnhancedSkillStatisticModifierUncapped(buffCaster, "expertise_buff_duration_line_" + specialLine ); expertiseBuffDurationMod += getEnhancedSkillStatisticModifierUncapped(buffCaster, "expertise_buff_duration_group_" + toLower(buffGroupName) ); expertiseBuffDurationMod += getEnhancedSkillStatisticModifierUncapped(buffCaster, "expertise_buff_duration_single_" + toLower(buffName) ); return expertiseBuffDurationMod; } boolean applyCombatMovementModifier(obj_id attacker, obj_id defender, string actionName) { boolean isRoot = movement.isRoot(actionName); boolean isSnare = movement.isSnare(actionName); boolean isStun = movement.isStunEffect(actionName); prose_package pp = new prose_package(); string movementType = ""; if (isRoot) movementType = "root"; if (isSnare) movementType = "snare"; int lastMovementMod; if (isPlayer(defender)) lastMovementMod = utils.getIntScriptVar(defender, "combat.movement."+movementType+".time"); else lastMovementMod = utils.getIntScriptVar(defender, "combat.movement."+movementType+"."+attacker+".time"); if (lastMovementMod + MOVEMENT_MODIFIER_REUSE_TIME > getGameTime()) { showCombatText(defender, attacker, new string_id("combat_effects", "no_effect"), 1.5f, colors.WHITE); return false; } if (buff.hasBuff(defender, actionName) || !buff.canApplyBuff(defender, actionName)) { showCombatText(defender, attacker, new string_id("combat_effects", "no_effect"), 1.5f, colors.WHITE); return false; } if (isStun) { pp = prose.setStringId(pp, new string_id("combat_effects", "go_stunned")); showFlyTextPrivateProseWithFlags(defender, defender, pp, 1.5f, colors.LAWNGREEN, FLY_TEXT_FLAG_IS_SNARE ); playClientEffectObj(defender, "appearance/pt_state_cantmove.prt", defender, "", null, "state_stunned"); playClientEffectObj(defender, "sound/sta_rooted_on.snd", defender, ""); } else if (isRoot) { pp = prose.setStringId(pp, new string_id("combat_effects", "go_rooted")); showFlyTextPrivateProseWithFlags(defender, defender, pp, 1.5f, colors.LAWNGREEN, FLY_TEXT_FLAG_IS_SNARE ); playClientEffectObj(defender, "appearance/pt_state_cantmove.prt", defender, "", null, "state_rooted"); playClientEffectObj(defender, "sound/sta_rooted_on.snd", defender, ""); } else if (isSnare) { pp = prose.setStringId(pp, new string_id("combat_effects", "go_snare")); showFlyTextPrivateProseWithFlags(defender, defender, pp, 1.5f, colors.LAWNGREEN, FLY_TEXT_FLAG_IS_SNARE ); playClientEffectObj(defender, "appearance/pt_state_slowedmove.prt", defender, "", null, "state_snared"); playClientEffectObj(defender, "sound/sta_snared_on.snd", defender, ""); } if (isPlayer(defender)) utils.setScriptVar(defender, "combat.movement."+movementType+".time", getGameTime()); else utils.setScriptVar(defender, "combat.movement."+movementType+"."+attacker+".time", getGameTime()); return true; } void removeCombatMovementModifierEffect(obj_id self, string buffName) { if(buffName == null || buffName.equals("")) return; boolean isVisible = true; dictionary buffData = dataTableGetRow("datatables/buff/buff.iff", buffName); if ( buffData != null ) isVisible = buffData.getInt("VISIBLE")==1; prose_package pp = new prose_package(); if (movement.isStunEffect(buffName)) { if ( isVisible ) { pp = prose.setStringId(pp, new string_id("combat_effects", "no_stunned")); showFlyTextPrivateProseWithFlags(self, self, pp, 1.5f, colors.TOMATO, FLY_TEXT_FLAG_IS_SNARE ); } stopClientEffectObjByLabel(self, "state_stunned"); playClientEffectObj(self, "sound/sta_rooted_off.snd", self, ""); } else if (movement.isRoot(buffName)) { if ( isVisible ) { pp = prose.setStringId(pp, new string_id("combat_effects", "no_rooted")); showFlyTextPrivateProseWithFlags(self, self, pp, 1.5f, colors.TOMATO, FLY_TEXT_FLAG_IS_SNARE ); } stopClientEffectObjByLabel(self, "state_rooted"); playClientEffectObj(self, "sound/sta_rooted_off.snd", self, ""); } else if (movement.isSnare(buffName) && buff.getDuration(buffName) > 0f) { if ( isVisible ) { pp = prose.setStringId(pp, new string_id("combat_effects", "no_snare")); showFlyTextPrivateProseWithFlags(self, self, pp, 1.5f, colors.TOMATO, FLY_TEXT_FLAG_IS_SNARE ); } stopClientEffectObjByLabel(self, "state_snared"); playClientEffectObj(self, "sound/sta_snared_off.snd", self, ""); } } void removeCombatBuffEffect(obj_id self, string buffName) { for (int i = 1; i <= buff.MAX_EFFECTS; i++) { int effectType = getStringCrc(buff.getEffectParam(buffName, i)); float effectValue = buff.getEffectValue(buffName, i); if (effectType == ##"combat_slow") { stopClientEffectObjByLabel(self, "state_slowattack"); playClientEffectObj(self, "sound/sta_slow_off.snd", self, ""); } } } boolean gainCombatActionAttribute(obj_id self, int action) { if(getMaxAction(self) == getAction(self)) return false; int newAction = getAction(self) + action; if(newAction > getMaxAction(self)) newAction = getMaxAction(self); setAction(self, newAction); return true; } boolean drainCombatActionAttributes(obj_id self, int[] actionCost) { if(!(drainAttributes(self, actionCost[1], actionCost[2]))) return false; return true; } boolean canDrainCombatActionAttributes(obj_id self, int actionCost) { int[] tempArray = {0, actionCost, 0}; return canDrainCombatActionAttributes(self, tempArray); } boolean canDrainCombatActionAttributes(obj_id self, int[] actionCost) { if(actionCost[1] > 0) { if(testDrainAttribute(self, ACTION, actionCost[1]) < 0) { // combatLog(self, self, "canDrainCombatActionAttributes", "Not enough Action points"); return false; } } return true; } // Return values for canPerformAction() - Used only by AI at the moment const int ACTION_SUCCESS = 0; const int ACTION_INVALID_DATA = 1; const int ACTION_INVALID_WEAPON = 2; const int ACTION_TOO_TIRED = 3; int canPerformAction(string actionName, obj_id self) { actionName = getBestAction(self, actionName); // Load action data from combat datatable combat_data actionData = combat_engine.getCombatData(actionName); // No valid action data found if (actionData == null) return ACTION_INVALID_DATA; // Load the weapon data weapon_data weaponData = new weapon_data(); weaponData = weapons.getNewWeaponData(getCurrentWeapon(self)); // Validate that this action can be used with this weapon if (!canUseWeaponWithAbility(self, weaponData, actionData, true)) return ACTION_INVALID_WEAPON; // Determine action cost and validate int[] actionCost = getActionCost(self, weaponData, actionData); if (!canDrainCombatActionAttributes(self, actionCost)) return ACTION_TOO_TIRED; return ACTION_SUCCESS; } boolean isCommandoBonus(obj_id self, weapon_data weaponData, int commandType) { return isPlayer(self) && utils.isProfession(self, utils.COMMANDO) && isHeavyWeapon(weaponData) && (commandType == LEFT_CLICK_DEFAULT); } int[] getActionCost(obj_id self, weapon_data weaponData, dictionary actionData) { int[] cost = new int[3]; float healthCost=0; float actionCost; float mindCost; // Get base ability costs actionCost = actionData.getFloat("actionCost"); if(isCommandoBonus(self, weaponData, actionData.getInt("commandType"))) { cost[0] = (int)(0); cost[1] = (int)(0); cost[2] = (int)(0); return cost; } // Modify action cost for expertise string specialLine = actionData.getString("specialLine"); string actionName = actionData.getString("actionName"); int freeshotChance = getEnhancedSkillStatisticModifierUncapped(self, "expertise_freeshot_" + specialLine); prose_package pp = new prose_package(); if(freeshotChance > 0) { int roll = rand(1, 100) + freeshotChance; if(roll > 100) { actionCost = 0; pp = prose.setStringId(pp, new string_id("spam", "freeshot")); showFlyTextPrivateProseWithFlags(self, self, pp, 1.5f, colors.LEMONCHIFFON, FLY_TEXT_FLAG_IS_FREESHOT ); pp = prose.setStringId(pp, new string_id("cbt_spam", "freeshot")); pp = prose.setTU(pp, self); sendCombatSpamMessageProse(self, self, pp, true, true, true, COMBAT_RESULT_GENERIC); cost[0] = (int)(0); cost[1] = (int)(0); cost[2] = (int)(0); return cost; } } if(actionCost > 0) { float expertiseActionCostMod = 0; float percentAddFromWeapon = actionData.getFloat("percentAddFromWeapon"); if(percentAddFromWeapon > 0.0f) { expertiseActionCostMod += getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_weapon_" + weaponData.weaponType); if(isMeleeWeapon(weaponData.id)) { expertiseActionCostMod += getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_weapon_melee"); } if (isRangedWeapon(weaponData.id)) { expertiseActionCostMod += getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_weapon_ranged"); } } expertiseActionCostMod += getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_all"); if(specialLine != null && specialLine != "") expertiseActionCostMod += getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_line_" + specialLine); if(actionName != null && actionName != "") expertiseActionCostMod += getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_single_" + actionName); // Weapon stat bonuses switch(weaponData.weaponType) { case WEAPON_TYPE_PISTOL: expertiseActionCostMod += (float)getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_pistol"); break; case WEAPON_TYPE_RIFLE: expertiseActionCostMod += (float)getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_rifle"); break; case WEAPON_TYPE_LIGHT_RIFLE: expertiseActionCostMod += (float)getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_carbine"); break; case WEAPON_TYPE_HEAVY: expertiseActionCostMod += (float)getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_heavy"); break; case WEAPON_TYPE_GROUND_TARGETTING: expertiseActionCostMod += (float)getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_heavy"); break; case WEAPON_TYPE_DIRECTIONAL: expertiseActionCostMod += (float)getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_heavy"); break; case WEAPON_TYPE_1HAND_MELEE: expertiseActionCostMod += (float)getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_1h"); break; case WEAPON_TYPE_2HAND_MELEE: expertiseActionCostMod += (float)getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_2h"); break; case WEAPON_TYPE_UNARMED: expertiseActionCostMod += (float)getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_unarmed"); break; case WEAPON_TYPE_POLEARM: expertiseActionCostMod += (float)getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_polearm"); break; case WEAPON_TYPE_WT_1HAND_LIGHTSABER: case WEAPON_TYPE_WT_2HAND_LIGHTSABER: case WEAPON_TYPE_WT_POLEARM_LIGHTSABER: expertiseActionCostMod += (float)getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_lightsaber"); break; } actionCost = actionCost - (actionCost * (expertiseActionCostMod/100.0f) ); if(actionCost < 0) actionCost = 0; } // A negative number means cost is a percent of total action. if(actionCost < 0) { float expertiseActionCostMod = 0; // Use the unmodified action max // The datatable had bad values. This is a close estimate. int actionTotal = getLevel(self) * 100; actionCost = actionTotal * (float)(-1.0f * ((float)actionCost / 100.0f)); //In GU10, fixed Abilities that cost a percentage not using the generic expertise_action_all modifier. expertiseActionCostMod += getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_all"); if(specialLine != null && specialLine != "") expertiseActionCostMod += getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_line_" + specialLine); if(actionName != null && actionName != "") expertiseActionCostMod += getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_single_" + actionName); actionCost = actionCost * (1 - (expertiseActionCostMod/100)); } // Action Burn: Lose health based on action used. if(utils.hasScriptVar(self, "buff.action_burn.value") && actionCost > 0) { int rightNow = getGameTime(); int timestamp = utils.getIntScriptVar(self, "buff.action_burn.timestamp"); if(rightNow > timestamp) { utils.setScriptVar(self, "buff.action_burn.timestamp", rightNow); float burnRatio = utils.getFloatScriptVar(self, "buff.action_burn.value"); float actionBurn = (float)actionCost * (burnRatio/100); int currentHealth = getHealth(self); int modifiedHealth = currentHealth - (int)actionBurn; if(modifiedHealth < 1) { modifiedHealth = 1; } int[] burnBuffs = buff.getAllBuffsByEffect(self, "action_burn"); obj_id burnBuffOwner = self; if(burnBuffs.length > 0 && utils.hasScriptVar(self, "buffOwner." + burnBuffs[0])) { burnBuffOwner = utils.getObjIdScriptVar(self, "buffOwner." + burnBuffs[0]); } setHealth(self, modifiedHealth); pp = prose.setStringId(pp, new string_id("spam", "crippling_pain")); showFlyTextPrivateProseWithFlags(self, burnBuffOwner, pp, 1.5f, colors.ORANGERED, FLY_TEXT_FLAG_IS_CRITICAL_HIT); pp = prose.setStringId(pp, new string_id("cbt_spam", "crippling_pain")); pp = prose.setTU(pp, self); pp = prose.setDI(pp, (int)actionBurn); sendCombatSpamMessageProse(self, burnBuffOwner, pp, true, true, true, COMBAT_RESULT_DEBUFF); } } // Modify by weapon costs //healthCost *= ((float)weaponData.attackCost / 100f); //actionCost *= (float)weaponData.attackCost; //mindCost *= (float)weaponData.attackCost; cost[0] = (int)(0); cost[1] = (int)(actionCost); cost[2] = (int)(0); // combatLog(self, null, "getActionCost", "Final Action cost = ["+cost[0]+", "+cost[1]+", "+cost[2]+"]"); return cost; } int[] getActionCost(obj_id self, weapon_data weaponData, combat_data actionData) { int[] cost = new int[3]; float healthCost=0; float actionCost; float mindCost; // Get base ability costs actionCost = actionData.actionCost; if(isCommandoBonus(self, weaponData, actionData.commandType)) { cost[0] = (int)(0); cost[1] = (int)(0); cost[2] = (int)(0); return cost; } if(beast_lib.isBeast(self)) { cost[0] = 0; cost[1] = (int)actionData.vigorCost; cost[2] = 0; return cost; } // Modify action cost for expertise string specialLine = actionData.specialLine; string actionName = actionData.actionName; int freeshotChance = getEnhancedSkillStatisticModifierUncapped(self, "expertise_freeshot_" + specialLine); prose_package pp = new prose_package(); if(freeshotChance > 0) { int roll = rand(1, 100) + freeshotChance; if(roll > 100) { actionCost = 0; pp = prose.setStringId(pp, new string_id("spam", "freeshot")); showFlyTextPrivateProseWithFlags(self, self, pp, 1.5f, colors.LEMONCHIFFON, FLY_TEXT_FLAG_IS_FREESHOT ); pp = prose.setStringId(pp, new string_id("cbt_spam", "freeshot")); pp = prose.setTU(pp, self); sendCombatSpamMessageProse(self, self, pp, true, true, true, COMBAT_RESULT_GENERIC); cost[0] = (int)(0); cost[1] = (int)(0); cost[2] = (int)(0); return cost; } } if(actionCost > 0) { float expertiseActionCostMod = 0; if(actionData.percentAddFromWeapon > 0.0f) { expertiseActionCostMod += getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_weapon_" + weaponData.weaponType); if(isMeleeWeapon(weaponData.id)) { expertiseActionCostMod += getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_weapon_melee"); } if (isRangedWeapon(weaponData.id)) { expertiseActionCostMod += getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_weapon_ranged"); } } //Exotic Action Reduction Healing Modifier Bits if(actionData.hitType == HEAL) { expertiseActionCostMod += getEnhancedSkillStatisticModifierUncapped(self, "exotic_heal_action_reduction"); } expertiseActionCostMod += getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_all"); if(specialLine != null && specialLine != "") expertiseActionCostMod += getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_line_" + specialLine); if(actionName != null && actionName != "") expertiseActionCostMod += getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_single_" + actionName); // Weapon stat bonuses switch(weaponData.weaponType) { case WEAPON_TYPE_PISTOL: expertiseActionCostMod += (float)getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_pistol"); break; case WEAPON_TYPE_RIFLE: expertiseActionCostMod += (float)getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_rifle"); break; case WEAPON_TYPE_LIGHT_RIFLE: expertiseActionCostMod += (float)getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_carbine"); break; case WEAPON_TYPE_HEAVY: expertiseActionCostMod += (float)getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_heavy"); break; case WEAPON_TYPE_GROUND_TARGETTING: expertiseActionCostMod += (float)getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_heavy"); break; case WEAPON_TYPE_DIRECTIONAL: expertiseActionCostMod += (float)getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_heavy"); break; case WEAPON_TYPE_1HAND_MELEE: expertiseActionCostMod += (float)getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_1h"); break; case WEAPON_TYPE_2HAND_MELEE: expertiseActionCostMod += (float)getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_2h"); break; case WEAPON_TYPE_UNARMED: expertiseActionCostMod += (float)getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_unarmed"); break; case WEAPON_TYPE_POLEARM: expertiseActionCostMod += (float)getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_polearm"); break; case WEAPON_TYPE_WT_1HAND_LIGHTSABER: case WEAPON_TYPE_WT_2HAND_LIGHTSABER: case WEAPON_TYPE_WT_POLEARM_LIGHTSABER: expertiseActionCostMod += (float)getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_lightsaber"); break; } actionCost = actionCost - (actionCost * (expertiseActionCostMod/100.0f) ); if(actionCost < 0) actionCost = 0; } // Cost is a percent of total action. if(actionCost < 0) { float expertiseActionCostMod = 0; //int actionTotal = getMaxAction(self); // Use the unmodified action max // The datatable had bad values. This is a close estimate. int actionTotal = getLevel(self) * 100; actionCost = actionTotal * (float)(-1.0f * ((float)actionCost / 100.0f)); //In GU10, fixed Abilities that cost a percentage not using the generic expertise_action_all modifier. expertiseActionCostMod += getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_all"); if(specialLine != null && specialLine != "") expertiseActionCostMod += getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_line_" + specialLine); if(actionName != null && actionName != "") expertiseActionCostMod += getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_single_" + actionName); actionCost = actionCost * (1 - (expertiseActionCostMod/100)); } // Action Burn: Lose health based on action used. if(utils.hasScriptVar(self, "buff.action_burn.value") && actionCost > 0) { int rightNow = getGameTime(); int timestamp = utils.getIntScriptVar(self, "buff.action_burn.timestamp"); if(rightNow > timestamp) { utils.setScriptVar(self, "buff.action_burn.timestamp", rightNow); float burnRatio = utils.getFloatScriptVar(self, "buff.action_burn.value"); float actionBurn = (float)actionCost * (burnRatio/100); int currentHealth = getHealth(self); int modifiedHealth = currentHealth - (int)actionBurn; if(modifiedHealth < 1) { modifiedHealth = 1; } setHealth(self, modifiedHealth); int[] burnBuffs = buff.getAllBuffsByEffect(self, "action_burn"); obj_id burnBuffOwner = self; if(burnBuffs.length > 0 && utils.hasScriptVar(self, "buffOwner." + burnBuffs[0])) { burnBuffOwner = utils.getObjIdScriptVar(self, "buffOwner." + burnBuffs[0]); } pp = prose.setStringId(pp, new string_id("spam", "crippling_pain")); showFlyTextPrivateProseWithFlags(self, burnBuffOwner, pp, 1.5f, colors.ORANGERED, FLY_TEXT_FLAG_IS_CRITICAL_HIT); pp = prose.setStringId(pp, new string_id("cbt_spam", "crippling_pain")); pp = prose.setTU(pp, self); pp = prose.setDI(pp, (int)actionBurn); sendCombatSpamMessageProse(self, burnBuffOwner, pp, true, true, true, COMBAT_RESULT_DEBUFF); } } // Modify by weapon costs //healthCost *= ((float)weaponData.attackCost / 100f); //actionCost *= (float)weaponData.attackCost; //mindCost *= (float)weaponData.attackCost; cost[0] = (int)(0); cost[1] = (int)(actionCost); cost[2] = (int)(0); combatLog(self, null, "getActionCost", "Final Action cost = ["+cost[0]+", "+cost[1]+", "+cost[2]+"]"); return cost; } // various test scripts use this function by passing in fake actionData that's not derived from a command table command int getForceCost(obj_id self, weapon_data weaponData, dictionary actionData) { return 0; } int getForceCost(obj_id self, weapon_data weaponData, combat_data actionData) { return 0; } boolean validateTarget(obj_id target, int targetCheck) { obj_id self = getSelf(); if(!storyteller.storytellerCombatCheck(self, target)) { return false; } switch (targetCheck) { case VALID_TARGET_MOB: return isMob(target) && !vehicle.isVehicle(target); case VALID_TARGET_CREATURE: return ai_lib.isMonster(target); case VALID_TARGET_NPC: return ai_lib.isNpc(target); case VALID_TARGET_DROID: return ai_lib.isDroid(target) || ai_lib.isAndroid(target) ; case VALID_TARGET_PVP: return pvpCanAttack(self, target); case VALID_TARGET_JEDI: if(isPlayer(target)) return isJedi(target); else return jedi.isLightsaber(getCurrentWeapon(target)); case VALID_TARGET_DEAD: return isDead(target); case VALID_TARGET_FRIEND: return pvpCanHelp(self, target); } return true; } string getCreatureAnimationName(obj_id self, string playbackName, hit_result hitData, weapon_data weaponData) { string animName = ""; int niche = ai_lib.aiGetNiche(self); //if (niche == NICHE_DROID || niche == NICHE_VEHICLE) if (niche == NICHE_VEHICLE) { animName ="droid_attack"; } else { if (playbackName.endsWith("ranged")) animName = "creature_attack_ranged"; else if (playbackName.endsWith("melee")) animName = "creature_attack"; else animName = "creature_attack_special_"+rand(1,2); } int avgDamage = (weaponData.minDamage + weaponData.maxDamage) / 2; if (hitData.damage > avgDamage) { animName += "_medium"; } else { animName += "_light"; } return animName; } boolean checkWeaponCerts(obj_id player, weapon_data weaponData) { return checkWeaponCerts(player, weaponData, true); } boolean checkWeaponCerts(obj_id player, weapon_data weaponData, boolean verbose) { // Only players care about certs if (!isPlayer(player)) return true; // Check actual weapon cert if(!combat.hasCertification(player, weaponData.id, verbose)) return false; if (utils.isProfession(player, utils.FORCE_SENSITIVE)) { if (jedi.isLightsaber(weaponData.weaponType)) { // Make sure the saber has a color crystal if (!jedi.hasColorCrystal(weaponData.id)) return false; // Make sure there are not too many crystals in the saber if (!jedi.validateCrystalCount(weaponData.id)) return false; } // Make sure they aren't Force Running if (buff.hasBuff(player, "forceRun_1") || buff.hasBuff(player, "forceRun_2")) { return false; } } return true; } int applyArmorProtection(obj_id attacker, obj_id defender, weapon_data weaponData, hit_result hitData, float bypassArmor) { return applyArmorProtection(attacker, defender, weaponData, hitData, bypassArmor, 0f); } int applyArmorProtection(obj_id attacker, obj_id defender, weapon_data weaponData, hit_result hitData, float bypassArmor, float expertiseDamageBonus) { float baseDamage = hitData.damage; float elementalDamage = weaponData.elementalValue; float baseProtection = 0f; float elementalProtection = 0f; float basePsgProtection = 0f; float elementalPsgProtection = 0f; boolean strikethrough = hitData.strikethrough; int armor_neglect = getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_armor_neglect"); int player_armor_reduction = getEnhancedSkillStatisticModifierUncapped(defender, "expertise_innate_reduction_all_player"); float neglectMod = 1.0f - ((float)armor_neglect / 100.0f); float playerMod = 1.0f - ((float)player_armor_reduction / 100.0f); obj_id armorPiece = null; obj_id psg = null; if (isPlayer(defender)) { boolean isRanged = isRangedWeapon(weaponData.weaponType); // Determine armor piece hit //armorPiece = getArmorPieceHit(defender, hitData.hitLocation); psg = getPsgArmor(defender); if (isIdValid(defender) || isIdValid(psg)) { // Get armor protection values float[] psgProtectionReference = new float[1]; baseProtection = getArmorProtection(defender, defender, psg, weaponData.damageType, psgProtectionReference); baseProtection *= neglectMod; baseProtection *= playerMod; if ( isRanged ) { basePsgProtection = psgProtectionReference[0]; } elementalProtection = getArmorProtection(defender, defender, psg, weaponData.elementalType, psgProtectionReference, attacker); if ( isRanged ) elementalPsgProtection = psgProtectionReference[0]; if ( isRanged ) { if ( basePsgProtection > 0 || elementalPsgProtection > 0 ) { // Play PSG effect between attacker and defender int psgLevel = armor.getArmorLevel(psg); if ( psgLevel < 0 || psgLevel >= armor.PSG_HIT_EFFECTS.length ) psgLevel = 0; // @todo: do we need some sort of delay here? playClientEffectObj(defender, armor.PSG_HIT_EFFECTS[psgLevel], defender, "", transform.identity); } } } } else { baseProtection = getNpcArmorProtection(defender, weaponData.damageType); baseProtection *= neglectMod; elementalProtection = getNpcArmorProtection(defender, weaponData.elementalType); } // Only apply strikethrough if this is a non armor piercing attack. if (bypassArmor == 0 && strikethrough) { float strikethroughRating = (float)(getStrikethroughValue(attacker, defender) / 100.0f); bypassArmor = rand(strikethroughRating / 2.0f ,strikethroughRating ); hitData.strikethroughAmmount = bypassArmor * 100.0f; } if (bypassArmor > 0) { if (bypassArmor > 1.0) bypassArmor = 1f; if (bypassArmor < 0.0) bypassArmor = 0f; } baseProtection *= 1.0 - bypassArmor; elementalProtection *= 1.0 - bypassArmor; // Actually account for diminishing returns so PSGs are not GodMode versus energy. if(basePsgProtection > 0) basePsgProtection = scalePsgProtectionByBaseProtection(basePsgProtection, baseProtection); // Reduce damage accordingly int baseDmgAbsorbed = (int)(baseDamage * (baseProtection + basePsgProtection)); baseDamage -= baseDmgAbsorbed; int blockValue = 0; if (hitData.blockResult) { blockValue = combat.getBlockAmmount(defender); if (baseDamage <= blockValue) { hitData.block = (int)baseDamage; baseDamage = 0.0f; } else { baseDamage -= blockValue; hitData.block = blockValue; } string_id critSpam = new string_id("combat_effects", "block"); prose_package pp = new prose_package(); pp = prose.setStringId(pp, critSpam); pp = prose.setDI(pp, hitData.block); showFlyTextPrivateProseWithFlags(defender, defender, pp, 1.5f, colors.LIMEGREEN, FLY_TEXT_FLAG_IS_GLANCING_BLOW ); showFlyTextPrivateProseWithFlags(defender, attacker, pp, 1.5f, colors.LIMEGREEN, FLY_TEXT_FLAG_IS_GLANCING_BLOW ); } //baseDamage *= crit_mod; // Apply a crit mod, if sliced.(NGE removed slicing) if ( baseDamage < 0 ) { baseDmgAbsorbed += baseDamage; baseDamage = 0; } //apply expertise bonuses to ELEMENTAL damage if(expertiseDamageBonus != 0) { elementalDamage = elementalDamage * (1.0f + (expertiseDamageBonus / 100.0f) ); } int elementalDmgAbsorbed = (int)(elementalDamage * (elementalProtection)); elementalDamage -= elementalDmgAbsorbed; //elementalDamage *= crit_mod; // Apply a crit mod, if sliced.(NGE removed slicing) if ( elementalDamage < 0 ) { elementalDmgAbsorbed += elementalDamage; elementalDamage = 0; } if ( (basePsgProtection > 0 || elementalPsgProtection > 0) && isIdValid(psg) ) { // Decay psg decayArmorPiece(psg, baseDamage * basePsgProtection + elementalDamage * elementalPsgProtection); } /*(NGE removed decay on armor pieces) if ( (baseProtection > 0 || elementalProtection > 0) && isIdValid(armorPiece)) { // Decay armor decayArmorPiece(armorPiece, baseDamage * baseProtection + elementalDamage * elementalProtection); } */ hitData.elementalDamage = (int)elementalDamage; hitData.elementalDamageType = weaponData.elementalType; hitData.damage = (int)baseDamage;// + hitData.elementalDamage; if ( (baseProtection > 0 || elementalProtection > 0) && isIdValid(armorPiece) ) hitData.blockingArmor = armorPiece; else if ( (basePsgProtection > 0 || elementalPsgProtection > 0) && isIdValid(psg)) hitData.blockingArmor = psg; return (baseDmgAbsorbed + elementalDmgAbsorbed); } float scalePsgProtectionByBaseProtection(float basePsgProtection, float baseProtection) { if(baseProtection < .2f) baseProtection = .2f; basePsgProtection *= 10000; basePsgProtection = (basePsgProtection / 100) / (100+((basePsgProtection / 100)))/(baseProtection*4); return basePsgProtection; } obj_id getArmorPieceHit(obj_id defender, int hitLocation) { string[] armorSlots = ARMOR_SLOT_LOCATIONS[hitLocation]; string slotHit = armorSlots[rand(0, armorSlots.length-1)]; obj_id armorPiece = getObjectInSlot(defender, slotHit); //check to see if we are clothing so as not to decay it int armorType = getGameObjectType( armorPiece ); if (isGameObjectTypeOf(armorType, GOT_armor)) return armorPiece; return null; } obj_id getPsgArmor(obj_id defender) { obj_id psg = getObjectInSlot(defender, PSG_SLOT_LOCATION); if ( armor.isPsg(psg) ) return psg; return null; } float getNpcArmorProtection(obj_id defender, int damageType) { int generalProtection = 0; dictionary protections = null; generalProtection = armor.getArmorGeneralProtection(defender); //protections = armor.getArmorSpecialProtections(defender); generalProtection += getEnhancedSkillStatisticModifier(defender, "private_armor_bonus"); generalProtection -= getEnhancedSkillStatisticModifierUncapped(defender, "expertise_innate_reduction_all_mob"); if (generalProtection == 0 && protections == null) return 0f; // Get pet ability bonus if (utils.hasScriptVar(defender, "ai.combat.genProtectionBonus")) { generalProtection += (int)utils.getFloatScriptVar(defender, "ai.combat.genProtectionBonus"); } // Convert protection values to percentage float armorProtection = convertProtectionToPercent(generalProtection); return armorProtection; } float getArmorProtection(obj_id defender, obj_id armorPiece, obj_id psg, int damageType, float[] psgProtectionResult) { return getArmorProtection(defender, armorPiece, psg, damageType, psgProtectionResult, obj_id.NULL_ID); } float getArmorProtection(obj_id defender, obj_id armorPiece, obj_id psg, int damageType, float[] psgProtectionResult, obj_id attacker) { int generalProtection = 0; //remember the "armorPiece" is actually the player not a piece of armor // Check skill mod armor bonus int armorBonus = getEnhancedSkillStatisticModifier( defender, "private_armor_bonus" ); generalProtection += armorBonus; dictionary protections = null; dictionary psgProtections = null; if ( isIdValid(armorPiece) ) { generalProtection = armor.getCombatArmorGeneralProtection(armorPiece); protections = armor.getCombatArmorSpecialProtections(armorPiece); //LOG("ARMOR_TEST", "GetArmorProtection General Protection = " +generalProtection); } if ( isIdValid(psg) ) psgProtections = armor.getPsgSpecialProtections(psg); if (generalProtection == 0 && protections == null && psgProtections == null) return 0f; string damageString = null; switch (damageType) { case DAMAGE_KINETIC: damageString = "kinetic"; break; case DAMAGE_ENERGY: damageString = "energy"; break; case DAMAGE_BLAST: damageString = "blast"; break; case DAMAGE_STUN: damageString = "stun"; break; case DAMAGE_RESTRAINT: damageString = "lightsaber"; break; case DAMAGE_ELEMENTAL_HEAT: damageString = "heat"; break; case DAMAGE_ELEMENTAL_COLD: damageString = "cold"; break; case DAMAGE_ELEMENTAL_ACID: damageString = "acid"; break; case DAMAGE_ELEMENTAL_ELECTRICAL: damageString = "electricity"; break; case DAMAGE_ENVIRONMENTAL_ELECTRICAL: damageString = "electricity"; break; default: break; } //LOG("ARMOR_TEST", "GetArmorProtection Damage Type = " +damageType); //LOG("ARMOR_TEST", "GetArmorProtection Damage String = " +damageString); float value = generalProtection; float psgValue = 0; if ( damageString != null ) { if ( protections != null ) value += protections.getFloat(damageString); //LOG("ARMOR_TEST", "special = " +protections.getFloat(damageString)); //as you got lower in health you would take more damage //if ( value != 0 ) // value = reduceArmorProtectionFromDecay(armorPiece, value); if ( psgProtections != null ) { psgValue = psgProtections.getFloat(damageString); if ( psgValue != 0 ) { if ( psgProtectionResult != null && psgProtectionResult.length > 0 ) psgProtectionResult[0] = psgValue / 10000f; } } } if ( isIdValid(attacker) && damageString != null && damageString.length() > 0 ) { // Check for the attacker having elemental penetration // Elemental penetration allows an attacker's elemental damage to ignore an amount of a defender's elemental protection float elementalPenetration = getElementalPenetration(attacker, damageString); if ( elementalPenetration > 0 ) { value *= elementalPenetration; //LOG("ARMOR_TEST", "GetArmorProtection after elementalPenetration for " +damageString+ " = " +value ); } } //LOG("ARMOR_TEST", "GetArmorProtection " +damageString+ " = " +value ); value = convertProtectionToPercent(value); //LOG("ARMOR_TEST", "GetArmorProtection FINAL VALUE" +damageString+" = " +value ); return value; } float getTerasKasiProtection(obj_id defender) { if (utils.getIntScriptVar(defender, armor.SCRIPTVAR_ARMOR_COUNT) > 0) return 0f; float protection = 0f; float tkaArmor = 0f; float jediArmor = 0f; protection = getEnhancedSkillStatisticModifier(defender, "clothing_armor"); if (hasSkill(defender, "combat_unarmed_novice")) tkaArmor = getSkillStatisticModifier(defender, "tka_armor"); if (tkaArmor > protection) protection = tkaArmor; if (hasSkill(defender, "force_title_jedi_novice")) jediArmor = getSkillStatisticModifier(defender, "jedi_armor"); if (jediArmor > protection) protection = jediArmor; protection *= 100f; // Check skill mod armor bonus int armorBonus = getEnhancedSkillStatisticModifier( defender, "private_armor_bonus" ); protection += armorBonus; return protection; } float convertProtectionToPercent(float protection) { const int reductionThreshold = 2000; if(protection <= reductionThreshold && protection >= (-1 * reductionThreshold)) { return protection / 10000f; } if(protection < 0) { protection += reductionThreshold; // subtract out linear progression portion protection = protection / (100f - (protection / 100f)); // reduce protection amount protection = -0.2f + (protection / 100f); // convert to 0 to -1 and add base linear protection amount } else { protection -= reductionThreshold; // subtract out linear progression portion protection = protection / (100f + (protection / 100f)); // reduce protection amount protection = 0.2f + (protection / 100f); // convert to 0-1 and add base linear protection amount } return protection; } float getArmorDecayPercentage(obj_id armorPiece) { float ARMOR_REDUCTION_THRESHOLD = 0.5f; float reduction = 1.0f; float hp = getHitpoints(armorPiece); float maxhp = getMaxHitpoints(armorPiece); float decayLevel = hp / maxhp; // Reduce armor effectiveness if below threshold. if ( hasObjVar( armorPiece, "slice.resilience" ) ) { // If we are sliced for resilience, we delay the effects of armor reduction. int resilience = getIntObjVar( armorPiece, "slice.resilience" ); ARMOR_REDUCTION_THRESHOLD -= resilience / 100.f; } if (decayLevel < ARMOR_REDUCTION_THRESHOLD) { reduction = 1f - (ARMOR_REDUCTION_THRESHOLD - decayLevel); } return reduction; } float reduceArmorProtectionFromDecay(obj_id armorPiece, float value) { float ARMOR_REDUCTION_THRESHOLD = 0.5f; float hp = getHitpoints(armorPiece); float maxhp = getMaxHitpoints(armorPiece); float decayLevel = hp / maxhp; // Reduce armor effectiveness if below threshold. if ( hasObjVar( armorPiece, "slice.resilience" ) ) { // If we are sliced for resilience, we delay the effects of armor reduction. int resilience = getIntObjVar( armorPiece, "slice.resilience" ); ARMOR_REDUCTION_THRESHOLD -= resilience / 100.f; } if (decayLevel < ARMOR_REDUCTION_THRESHOLD) { float reduction = 1f - (ARMOR_REDUCTION_THRESHOLD - decayLevel); value *= reduction; } return value; } void decayArmorPiece(obj_id armorPiece, float newDamage) { // if this is a psg, also reduce its efficiency if ( armor.isPsg(armorPiece) ) { dictionary protections = armor.getArmorSpecialProtections(armorPiece); float maxEffectiveness = ((Float)(protections.values().iterator().next())).floatValue(); if ( maxEffectiveness > 0 ) { float effectivenessReduction = PSG_BASE_DMG_PER_HIT + (newDamage / PSG_DAMAGE_SCALE); float efficiencyReduction = effectivenessReduction / maxEffectiveness; float currentEfficiency = armor.getPsgEfficiency(armorPiece); currentEfficiency -= efficiencyReduction; armor.setPsgEfficiency(armorPiece, currentEfficiency); } } if ( utils.isAntiDecay(armorPiece) ) return; if ( static_item.isStaticItem(armorPiece) ) return; /*(NGE removed slicing) // If we are sliced for deflection, we might reduce the damage the armor will take by 50%. if ( hasObjVar( armorPiece, "slice.deflect" ) ) { int deflect = getIntObjVar( armorPiece, "slice.deflect" ); if ( rand(1,100) < deflect ) newDamage *= 0.5f; } */ /* int hp = getHitpoints(armorPiece); if ( hp > 0 ) { hp -= (int)(newDamage / 2f); setHitpoints(armorPiece, hp); } */ } boolean getGrenadeData(obj_id player, weapon_data weaponData, string params, boolean verbose) { Long lngId; try { lngId = new Long(params); // convert the string to a long so we can make an obj_id } // try with error handling catch (NumberFormatException err) { if (verbose) sendSystemMessage(player, new string_id("cbt_spam", "grenade_bad_param")); return false; } obj_id grenade = obj_id.getObjId(lngId.longValue()); if (!isIdValid(grenade)) { if (verbose) sendSystemMessage(player, new string_id("cbt_spam", "grenade_invalid")); return false; } if (getFirstParentInWorld(grenade) != player) { if (verbose) sendSystemMessage(player, new string_id("cbt_spam", "grenade_not_owned")); removeObjVar(grenade, "intUsed"); return false; } // set weapon data weaponData.id = grenade; weaponData.weaponName = getNameStringId(grenade); weaponData.minDamage = getWeaponMinDamage(grenade); weaponData.maxDamage = getWeaponMaxDamage(grenade); weaponData.weaponType = WEAPON_TYPE_THROWN; weaponData.attackType = ATTACK_TYPE_THROWN; weaponData.damageType = getWeaponDamageType(grenade); weaponData.elementalType = getWeaponElementalType(grenade); weaponData.elementalValue = getWeaponElementalValue(grenade); weaponData.attackSpeed = getWeaponAttackSpeed(grenade); weaponData.woundChance = getWeaponWoundChance(grenade); range_info rng = getWeaponRangeInfo(grenade); weaponData.minRange = rng.minRange; weaponData.maxRange = rng.maxRange; weaponData.accuracy = getWeaponAccuracy(grenade); weaponData.damageRadius = getWeaponDamageRadius(grenade); weaponData.attackCost = getWeaponAttackCost(grenade); return true; } boolean canPaintTarget(obj_id squadLeader, obj_id target) { // Just to keep things confusing the old Volley Fire command has been renamed to Paint Target // Pretend all "volley" references here are "paint" if(!isValidId(target)) { return false; } if (!pvpCanAttack(squadLeader, target)) { return false; } // Validate group obj_id groupId = getGroupObject(squadLeader); if(!isIdValid(groupId)) { return false; } // make sure no one in this group has execute paintTarget recently // - prevents people switching out squad leaders to confuse the target particle managing if(utils.hasScriptVar(groupId, VAR_GROUP_LAST_VOLLEY_TIME)) { int lastVolley = utils.getIntScriptVar(groupId, VAR_GROUP_LAST_VOLLEY_TIME); int now = getGameTime(); int interval = now - lastVolley; if(interval < VOLLEY_INTERVAL) { return false; } } // Validate group size obj_id[] groupMembers = getGroupMemberIds(groupId); if(groupMembers == null || groupMembers.length < 2 ) { return false; } return true; } void doPaintTarget(obj_id squadLeader, obj_id target) { obj_id groupId = getGroupObject(squadLeader); obj_id[] groupMembers = getGroupMemberIds(groupId); if(!hasScript(target, "systems.combat.volleytarget")) { attachScript(target, "systems.combat.volleytarget"); } utils.setScriptVar(groupId, VAR_GROUP_VOLLEY_TARGET, target); utils.setScriptVar(groupId, VAR_GROUP_LAST_VOLLEY_TIME, getGameTime()); dictionary parms = new dictionary(); parms.put("objGroup", groupId); messageTo(target, "msgMarkedByGroup", parms, 0, false); messageTo(target, "msgUnmarkedByGroup", parms, VOLLEY_INTERVAL * 0.7f , false); // VolleyFire has a 30 second cooldown, lasts for ~21 seconds // show group members the particle playClientEffectObj(groupMembers, "appearance/pt_special_attack_volley_fire.prt", target, "", new transform(), ID_VOLLEY_FIRE_PARTICLE); } boolean canUseWeaponWithAbility(obj_id self, weapon_data weaponData, combat_data actionData, boolean verbose) { if(!isIdValid(weaponData.id)) return false; if( ((actionData.invalidWeapon & weaponTypeToBitValueMap[weaponData.weaponType]) == 0) && ((actionData.validWeapon & weaponTypeToBitValueMap[weaponData.weaponType]) != 0) ) return true; if(verbose) { sendCombatSpamMessage(self, new string_id("cbt_spam", "invalid_weapon")); sendSystemMessage(self, new string_id("cbt_spam", "invalid_weapon_single")); } return false; } boolean canUseWeaponWithAbility(obj_id self, weapon_data weaponData, string actionName, boolean verbose) { return canUseWeaponWithAbility(self, weaponData, combat_engine.getCombatData(actionName), verbose); //ai will stop using force powers if you touch this DAN DO NOT REMOVE THIS //DAN DO NOT TOUCH THIS //DAN DO NOT TOUCH THIS // LINES Below have been removed by Dan } void addHateProcess(obj_id attacker, obj_id defender, hit_result hitData, combat_data actionData) { //base class exceptions if you try to assign yourself as a hate target of yourself if(defender == attacker) return; float hateMod = actionData.hateDamageModifier; int maxHate = actionData.maxHate; int hateAdd = actionData.hateAdd; int hateAddTime = actionData.hateAddTime; int hateReduction = actionData.hateReduce; if (hitData.success) { if(hateReduction < 1) { // Max Hate //This is a taunt. It will set you to the targets max hate value + the passed maxHate if (maxHate > 0) { const float maxHateDamage = getMaxHate(defender); setHate(defender, attacker, maxHateDamage + maxHate); } // Hate DOT if (hateAdd > 0 && hateAddTime > 0) { addHateDot(defender, attacker, hateAdd, hateAddTime); } // Hate Damage float totalHateDamage = hitData.damage * hateMod; if (totalHateDamage > 0.0f) { int skillBonus = getEnhancedSkillStatisticModifier( attacker, "private_taunt_bonus" ); totalHateDamage += totalHateDamage * (skillBonus / 100f); if ( utils.hasScriptVar( attacker, "ai.combat.aggroBonus" ) ) { totalHateDamage += totalHateDamage * utils.getFloatScriptVar( attacker, "ai.combat.aggroBonus" ); } if ( utils.hasScriptVar( attacker, "ai.combat.aggroBonusFlat" ) ) { totalHateDamage += utils.getFloatScriptVar( attacker, "ai.combat.aggroBonusFlat" ); } if ( utils.hasScriptVar( attacker, "ai.combat.aggroReduction" ) ) { totalHateDamage -= totalHateDamage * utils.getFloatScriptVar( attacker, "ai.combat.aggroReduction" ); } if(buff.hasBuff(attacker, BUFF_HATE_XFER)) { int hateTransferBonus = getEnhancedSkillStatisticModifier(attacker, "expertise_aggro_channel"); float hateTransfered = totalHateDamage * (hateTransferBonus / 100f); totalHateDamage -= hateTransfered; obj_id transferTo = utils.getObjIdScriptVar(attacker, buff.AGGRO_TRANSFER_TO); addHate(defender, transferTo, hateTransfered); } addHate(defender, attacker, totalHateDamage); } } else { //Players and Beasts may perform a deAgro special on an NPC, non-beast. if ((isPlayer(attacker) || beast_lib.isBeast(attacker)) && (isMob(defender) && !beast_lib.isBeast(defender))) { float currentHate = getHate(defender, attacker); currentHate -= hateReduction; currentHate = currentHate < 1.0f ? 1.0f : currentHate; setHate(defender, attacker, currentHate); } else if ((isMob(attacker) && !beast_lib.isBeast(attacker)) && (isPlayer(defender) || beast_lib.isBeast(defender))) { // If it is an NPC using this attack on a player, the deAgro applies to the attacking NPC float currentHate = getHate(attacker, defender); currentHate -= hateReduction; currentHate = currentHate < 1.0f ? 1.0f : currentHate; setHate(attacker, defender, currentHate); } } } else { // Miss attack addHate(defender, attacker, 1.0f); } // This is only for attacking players because otherwise it would reset an // attacking AI's hate list timer. If the attacking AI's hate list timer // gets reset everytime they attack a player, they would never break combat // with a retreating player. We want to allow players to get away from AI. if (isPlayer(attacker)) { // Reset the attacking player's hate list timer and make sure the defender is // on the attacking player's hate list. addHate(attacker, defender, 0.0f); // An attacking player continues to reset the hate timers on every AI // of whom the attacking player is a primary hate target. { const obj_id[] hateList = getHateList(attacker); for (int i = 0; i < hateList.length; ++i) { const obj_id hateTarget = hateList[i]; if (!isPlayer(hateTarget)) { if (getHateTarget(hateTarget) == attacker) { resetHateTimer(hateTarget); } } } } } } // Combat Value Caching boolean cachedIsRidingVehicle(obj_id attacker) { string cacheId = "combat.cache.ridingVehicle"; if (hasCachedCombatValue(attacker, cacheId)) return getCachedCombatValueBool(attacker, cacheId); boolean ridingVehicle = vehicle.isRidingVehicle(attacker); setCachedCombatValue(attacker, cacheId, ridingVehicle); return ridingVehicle; } boolean cachedIsGalloping(obj_id attacker) { string cacheId = "combat.cache.galloping"; if (hasCachedCombatValue(attacker, cacheId)) return getCachedCombatValueBool(attacker, cacheId); obj_id mount = getMountId(attacker); boolean galloping = false; if (isIdValid(mount)) { galloping = pet_lib.isGalloping(mount); } setCachedCombatValue(attacker, cacheId, galloping); return galloping; } boolean cachedIsDead(obj_id attacker) { string cacheId = "combat.cache.isDead"; if (hasCachedCombatValue(attacker, cacheId)) return getCachedCombatValueBool(attacker, cacheId); boolean dead = isDead(attacker); setCachedCombatValue(attacker, cacheId, dead); return dead; } boolean cachedIsIncapacitated(obj_id attacker) { string cacheId = "combat.cache.isIncapped"; if (hasCachedCombatValue(attacker, cacheId)) return getCachedCombatValueBool(attacker, cacheId); boolean incapped = isIncapacitated(attacker); setCachedCombatValue(attacker, cacheId, incapped); return incapped; } int cachedGetLocomotion(obj_id attacker) { string cacheId = "combat.cache.locomotion"; if (hasCachedCombatValue(attacker, cacheId)) return getCachedCombatValueInt(attacker, cacheId); int locomotion = getLocomotion(attacker); setCachedCombatValue(attacker, cacheId, locomotion); return locomotion; } float cachedGetMovementSpeed(obj_id attacker) { string cacheId = "combat.cache.moveSpeed"; if (hasCachedCombatValue(attacker, cacheId)) return getCachedCombatValueFloat(attacker, cacheId); float moveSpeed = getMovementSpeed(attacker); setCachedCombatValue(attacker, cacheId, moveSpeed); return moveSpeed; } float cachedGetRunSpeed(obj_id attacker) { string cacheId = "combat.cache.runSpeed"; if (hasCachedCombatValue(attacker, cacheId)) return getCachedCombatValueFloat(attacker, cacheId); float runSpeed = getRunSpeed(attacker); setCachedCombatValue(attacker, cacheId, runSpeed); return runSpeed; } float cachedGetDistance(obj_id attacker, obj_id defender) { string cacheId = "combat.cache."+defender+".distance"; if (hasCachedCombatValue(attacker, cacheId)) return getCachedCombatValueFloat(attacker, cacheId); float dist = getDistance(attacker, defender); setCachedCombatValue(attacker, cacheId, dist); return dist; } float cachedGetSpeedAdjustMin(obj_id attacker, obj_id defender) { string cacheId = "combat.cache."+defender+".speedAdjustMin"; if (hasCachedCombatValue(attacker, cacheId)) return getCachedCombatValueFloat(attacker, cacheId); float adjustMin = 0.0f; if (cachedGetLocomotion(attacker) == LOCOMOTION_RUNNING) { //sendSystemMessageTestingOnly(attackerData.id, "Attacker Speed: " + getMovementSpeed(attackerData.id) + " Defender Speed: " + getMovementSpeed(defenderData[0].id)); // put on even ground, so we only take into account 2d angles during isFacing location defenderLocation = getLocation(defender); defenderLocation.y = getLocation(attacker).y; if (!isFacing(attacker, defenderLocation)) adjustMin -= cachedGetMovementSpeed(attacker) * 0.6f; } if (cachedGetLocomotion(defender) == LOCOMOTION_RUNNING) { // put on even ground, so we only take into account 2d angles during isFacing location attackerLocation = getLocation(attacker); attackerLocation.y = getLocation(defender).y; if (isFacing(defender, attackerLocation)) adjustMin -= cachedGetMovementSpeed(defender) * 0.6f; } // flatten out the adjust min and max to their max values float maxAdjustValue = (cachedGetRunSpeed(attacker) + cachedGetRunSpeed(defender)) * 0.75f; if (adjustMin < -maxAdjustValue) adjustMin = -maxAdjustValue; setCachedCombatValue(attacker, cacheId, adjustMin); return adjustMin; } float cachedGetSpeedAdjustMax(obj_id attacker, obj_id defender) { string cacheId = "combat.cache."+defender+".speedAdjustMax"; if (hasCachedCombatValue(attacker, cacheId)) return getCachedCombatValueFloat(attacker, cacheId); float adjustMax = 0.0f; if (cachedGetLocomotion(attacker) == LOCOMOTION_RUNNING) { //sendSystemMessageTestingOnly(attackerData.id, "Attacker Speed: " + getMovementSpeed(attackerData.id) + " Defender Speed: " + getMovementSpeed(defenderData[0].id)); // put on even ground, so we only take into account 2d angles during isFacing location defenderLocation = getLocation(defender); defenderLocation.y = getLocation(attacker).y; if (isFacing(attacker, defenderLocation)) adjustMax += cachedGetMovementSpeed(attacker) * 0.6f; } if (cachedGetLocomotion(defender) == LOCOMOTION_RUNNING) { // put on even ground, so we only take into account 2d angles during isFacing location attackerLocation = getLocation(attacker); attackerLocation.y = getLocation(defender).y; if (!isFacing(defender, attackerLocation)) adjustMax += cachedGetMovementSpeed(defender) * 0.6f; } // flatten out the adjust min and max to their max values float maxAdjustValue = (cachedGetRunSpeed(attacker) + cachedGetRunSpeed(defender)) * 0.75f; if (adjustMax > maxAdjustValue) adjustMax = maxAdjustValue; setCachedCombatValue(attacker, cacheId, adjustMax); return adjustMax; } boolean cachedCanSee(obj_id attacker, obj_id defender) { // string cacheId = "combat.cache."+defender+".canSee"; // if (hasCachedCombatValue(attacker, cacheId)) // return getCachedCombatValueBool(attacker, cacheId); // boolean attackerCanSee = canSee(attacker, defender); // setCachedCombatValue(attacker, cacheId, attackerCanSee); //return attackerCanSee; return canSee(attacker, defender); } boolean cachedPvpCanAttack(obj_id attacker, obj_id defender) { string cacheId = "combat.cache."+defender+".pvpCanAttack"; if (hasCachedCombatValue(attacker, cacheId)) return getCachedCombatValueBool(attacker, cacheId); boolean attackerCanAttack = pvpCanAttack(attacker, defender); setCachedCombatValue(attacker, cacheId, attackerCanAttack); return attackerCanAttack; } boolean cachedIsSameSocialGroup(obj_id attacker, obj_id defender) { string cacheId = "combat.cache."+defender+".sameSocialGroup"; if (hasCachedCombatValue(attacker, cacheId)) return getCachedCombatValueBool(attacker, cacheId); string attackerSocialGroup = null; if (!isPlayer(attacker) && !pet_lib.isPet(attacker) && !pet_lib.isPet(defender)) { attackerSocialGroup = ai_lib.getSocialGroup(attacker); } boolean sameSocialGroup = (attackerSocialGroup != null && attackerSocialGroup.equals(ai_lib.getSocialGroup(defender))); setCachedCombatValue(attacker, cacheId, sameSocialGroup); return sameSocialGroup; } boolean isCachedServerFrame(obj_id attacker) { int serverFrame = utils.getIntScriptVar(attacker, "combat.cache.serverFrame"); int currentFrame = getServerFrame(); return (serverFrame == currentFrame); } void setCachedServerFrame(obj_id attacker) { if (!isCachedServerFrame(attacker)) { utils.removeScriptVarTree(attacker, "combat.cache"); int currentFrame = getServerFrame(); utils.setScriptVar(attacker, "combat.cache.serverFrame", currentFrame); } } boolean hasCachedCombatValue(obj_id attacker, string cacheId) { if (isCachedServerFrame(attacker)) { return (utils.hasScriptVar(attacker, "combat.cache."+cacheId)); } return false; } int getCachedCombatValueInt(obj_id attacker, string cacheId) { if (isCachedServerFrame(attacker)) { return (utils.getIntScriptVar(attacker, "combat.cache."+cacheId)); } return 0; } float getCachedCombatValueFloat(obj_id attacker, string cacheId) { if (isCachedServerFrame(attacker)) { return (utils.getFloatScriptVar(attacker, "combat.cache."+cacheId)); } return 0f; } boolean getCachedCombatValueBool(obj_id attacker, string cacheId) { if (isCachedServerFrame(attacker)) { return (utils.getBooleanScriptVar(attacker, "combat.cache."+cacheId)); } return false; } void setCachedCombatValue(obj_id attacker, string cacheId, int value) { setCachedServerFrame(attacker); utils.setScriptVar(attacker, "combat.cache."+cacheId, value); } void setCachedCombatValue(obj_id attacker, string cacheId, float value) { setCachedServerFrame(attacker); utils.setScriptVar(attacker, "combat.cache."+cacheId, value); } void setCachedCombatValue(obj_id attacker, string cacheId, boolean value) { setCachedServerFrame(attacker); utils.setScriptVar(attacker, "combat.cache."+cacheId, value); } void combatLog(obj_id attacker, obj_id defender, string logName, string logMsg) { // DO NOT UNCOMMENT THIS AND SUBMIT - UNCOMMENT THE DEFINE AT THE TOP OF THIS FILE // AND DON'T SUBMIT WITH COMBAT LOGGING ON - IT'S FOR LOCAL TESTING ONLY! #ifdef COMBATLOG const string[] enabledLogs = { "combatStandardAction", "getCorrectCombatTarget", "doCombatPreCheck", "getCombatDefenders", "validateDefenders", "getActionCost", "canDrainCombatActionAttributes", "drainCombatActionAttributes", "runHitEngine", "getActionAnimation", "getRawDamage", "applyDamage", "doWrappedDamage", "applyGrenadeEffects", "applyAttackerCombatBuffs", "applyDefenderCombatBuffs", "healingStandardAction", "getCorrectHealingTarget", "validateHealingTargets", "doHealingPreCheck", "setDefenderCombatResults", "checkCombatStates", "checkPosture", "end" }; if (utils.getElementPositionInArray (enabledLogs, logName) == -1) return; string attackerName = "NULL"; string defenderName = "NULL"; if (isIdValid(attacker)) { attackerName = getEncodedName(attacker); if (attackerName.startsWith("@")) attackerName = localize(getNameStringId(attacker)); } if (isIdValid(defender)) { defenderName = getEncodedName(defender); if (defenderName.startsWith("@")) defenderName = localize(getNameStringId(defender)); } LOG ("combat_log", attackerName+"("+attacker+") ATTACKS "+defenderName+"("+defender+")\t:"+logName+"::"+logMsg); // DO NOT UNCOMMENT THIS AND SUBMIT - UNCOMMENT THE DEFINE AT THE TOP OF THIS FILE // AND DON'T SUBMIT WITH COMBAT LOGGING ON - IT'S FOR LOCAL TESTING ONLY! #endif } int getWeaponCategory(string weaponType) { if (weaponType.equals("rifle")) return RANGED_WEAPON; if (weaponType.equals("carbine")) return RANGED_WEAPON; if (weaponType.equals("pistol")) return RANGED_WEAPON; if (weaponType.equals("heavyweapon")) return RANGED_WEAPON; if (weaponType.equals("onehandmelee")) return MELEE_WEAPON; if (weaponType.equals("twohandmelee")) return MELEE_WEAPON; if (weaponType.equals("unarmed")) return MELEE_WEAPON; if (weaponType.equals("polearm")) return MELEE_WEAPON; if (weaponType.equals("thrown")) return RANGED_WEAPON; if (weaponType.equals("onehandlightsaber")) return MELEE_WEAPON; if (weaponType.equals("twohandlightsaber")) return MELEE_WEAPON; if (weaponType.equals("polearmlightsaber")) return MELEE_WEAPON; if (weaponType.equals("force")) return FORCE_POWER; return 0; } int getWeaponCategory(int weaponType) { if (weaponType == WEAPON_TYPE_RIFLE) return RANGED_WEAPON; if (weaponType == WEAPON_TYPE_LIGHT_RIFLE) return RANGED_WEAPON; if (weaponType == WEAPON_TYPE_PISTOL) return RANGED_WEAPON; if (weaponType == WEAPON_TYPE_HEAVY) return RANGED_WEAPON; if (weaponType == WEAPON_TYPE_1HAND_MELEE) return MELEE_WEAPON; if (weaponType == WEAPON_TYPE_2HAND_MELEE) return MELEE_WEAPON; if (weaponType == WEAPON_TYPE_UNARMED) return MELEE_WEAPON; if (weaponType == WEAPON_TYPE_POLEARM) return MELEE_WEAPON; if (weaponType == WEAPON_TYPE_THROWN) return RANGED_WEAPON; if (weaponType == WEAPON_TYPE_WT_1HAND_LIGHTSABER) return MELEE_WEAPON; if (weaponType == WEAPON_TYPE_WT_2HAND_LIGHTSABER) return MELEE_WEAPON; if (weaponType == WEAPON_TYPE_WT_POLEARM_LIGHTSABER) return MELEE_WEAPON; if (weaponType == WEAPON_TYPE_FORCE_POWER) return FORCE_POWER; return 0; } string getWeaponStringCategory(int weaponType) { int weaponCategory = getWeaponCategory(weaponType); if (weaponCategory == RANGED_WEAPON) return "ranged"; if (weaponCategory == MELEE_WEAPON) return "melee"; if (weaponCategory == FORCE_POWER) return "ranged"; return null; } string getWeaponStringCategory(string weaponType) { int weaponCategory = getWeaponCategory(weaponType); if (weaponCategory == RANGED_WEAPON) return "ranged"; if (weaponCategory == MELEE_WEAPON) return "melee"; if (weaponCategory == FORCE_POWER) return "ranged"; return null; } int getWeaponType(string weaponType) { if (weaponType.equals("rifle")) return WEAPON_TYPE_RIFLE; if (weaponType.equals("carbine")) return WEAPON_TYPE_LIGHT_RIFLE; if (weaponType.equals("pistol")) return WEAPON_TYPE_PISTOL; if (weaponType.equals("heavyweapon")) return WEAPON_TYPE_HEAVY; if (weaponType.equals("onehandmelee")) return WEAPON_TYPE_1HAND_MELEE; if (weaponType.equals("twohandmelee")) return WEAPON_TYPE_2HAND_MELEE; if (weaponType.equals("unarmed")) return WEAPON_TYPE_UNARMED; if (weaponType.equals("polearm")) return WEAPON_TYPE_POLEARM; if (weaponType.equals("thrown")) return WEAPON_TYPE_THROWN; if (weaponType.equals("onehandlightsaber")) return WEAPON_TYPE_WT_1HAND_LIGHTSABER; if (weaponType.equals("twohandlightsaber")) return WEAPON_TYPE_WT_2HAND_LIGHTSABER; if (weaponType.equals("polearmlightsaber")) return WEAPON_TYPE_WT_POLEARM_LIGHTSABER; return 0; } int getCorrectedWeaponType(int passedType) { switch(passedType) { case WEAPON_TYPE_GROUND_TARGETTING: return WEAPON_TYPE_HEAVY; case WEAPON_TYPE_DIRECTIONAL: return WEAPON_TYPE_HEAVY; } return passedType; } string getWeaponStringType(int weaponType) { switch(weaponType) { case WEAPON_TYPE_RIFLE : return "rifle"; case WEAPON_TYPE_LIGHT_RIFLE : return "carbine"; case WEAPON_TYPE_PISTOL : return "pistol"; case WEAPON_TYPE_HEAVY : return "heavyweapon"; case WEAPON_TYPE_1HAND_MELEE : return "onehandmelee"; case WEAPON_TYPE_2HAND_MELEE: return "twohandmelee"; case WEAPON_TYPE_UNARMED: return "unarmed"; case WEAPON_TYPE_POLEARM: return "polearm"; case WEAPON_TYPE_THROWN: return "thrown"; case WEAPON_TYPE_WT_1HAND_LIGHTSABER: return "onehandlightsaber"; case WEAPON_TYPE_WT_2HAND_LIGHTSABER: return "twohandlightsaber"; case WEAPON_TYPE_WT_POLEARM_LIGHTSABER: return "polearmlightsaber"; case WEAPON_TYPE_FORCE_POWER: return "force"; } return ""; } boolean isMeleeWeapon(obj_id weapon) { int weaponType = getWeaponType(weapon); return isMeleeWeapon(weaponType); } boolean isMeleeWeapon(int weaponType) { return hasBit(weaponType, B_MELEE); } boolean isRangedWeapon(obj_id weapon) { int weaponType = getWeaponType(weapon); return isRangedWeapon(weaponType); } boolean isHeavyWeapon(int weaponType) { return hasBit(weaponType, B_HEAVY); } boolean isHeavyWeapon(obj_id weapon) { int weaponType = getWeaponType(weapon); return hasBit(weaponType, B_HEAVY); } boolean isRangedWeapon(int weaponType) { return hasBit(weaponType, B_RANGED); } boolean isLightsaberWeapon(obj_id weapon) { int weaponType = getWeaponType(weapon); return isLightsaberWeapon(weaponType); } boolean isLightsaberWeapon(int weaponType) { return hasBit(weaponType, B_ALL_LIGHTSABERS); } boolean isGrenade(weapon_data weaponData) { return hasBit(weaponData.weaponType, B_THROWN); } boolean isHeavyWeapon(weapon_data weaponData) { return hasBit(weaponData.weaponType, B_HEAVY); } boolean canApplyCombatEffect(obj_id objPlayer) { if ( !isIdValid(objPlayer) ) return false; if ( ai_lib.aiGetNiche( objPlayer)==NICHE_DROID || vehicle.isVehicle(objPlayer) || ai_lib.isTurret(objPlayer) || ai_lib.isAndroid(objPlayer)) { return false; } return true; } boolean hasCertification(obj_id objPlayer, obj_id objWeapon) { return hasCertification(objPlayer, objWeapon, true); } boolean hasCertification(obj_id objPlayer, obj_id objWeapon, boolean verbose) { boolean hasCert = true; // Since datatable queries are so expensive, check the last weapon checked against the current weapon if(utils.hasScriptVar(objPlayer, "combat.weaponCertified")) { obj_id lastCertified = utils.getObjIdScriptVar(objPlayer, "combat.weaponCertified"); if(lastCertified == objWeapon) { return true; } } string template = getTemplateName(objWeapon); if(template == null) return false; // unarmed player weapon is object/weapon/melee/unarmed/unarmed_default_player.iff if(template == "object/weapon/melee/unarmed/unarmed_default_player.iff") { return true; } // check for racial certs int speciesRequired = dataTableGetInt(combat.WEAPON_LEVEL_TABLE, template, "species_restriction"); if(speciesRequired >= 0) { int playerSpecies = getSpecies(objPlayer); if(playerSpecies != speciesRequired) { hasCert = false; } } if(hasObjVar (objWeapon, "factionrestricted.rebel")) { if(!factions.isRebel(objPlayer)) { hasCert = false; } } if(hasObjVar(objWeapon, "factionrestricted.imperial")) { if(!factions.isImperial(objPlayer)) { hasCert = false; } } string classTemplate = getSkillTemplate(objPlayer); if (isLightsaberWeapon(objWeapon) && !utils.isProfession(objPlayer, utils.FORCE_SENSITIVE)) hasCert = false; //static weapon check if(static_item.isDynamicItem(objWeapon)) { int levelRequired = getIntObjVar(objWeapon, "dynamic_item.intLevelRequired"); int playerLevel = getLevel(objPlayer); if(playerLevel < levelRequired) { hasCert = false; } } else if(static_item.isStaticItem(objWeapon)) { // Check skill requirement dictionary itemData = static_item.getMasterItemDictionary(objWeapon); string skillRequired = itemData.getString("required_skill"); if (skillRequired != null && skillRequired !="") { if (classTemplate != null && classTemplate !="") { if(!classTemplate.startsWith(skillRequired)) { hasCert = false; } } } // Check level requirement int levelRequired = itemData.getInt("required_level"); int playerLevel = getLevel(objPlayer); if(playerLevel < levelRequired) { hasCert = false; } } //crafted weapon check else { // Check skill requirement string skillRequired = dataTableGetString(WEAPON_LEVEL_TABLE, template, "secondary_restriction"); if (skillRequired != null && skillRequired !="") { if (classTemplate != null && classTemplate !="") { if(!classTemplate.startsWith(skillRequired)) { hasCert = false; } } } // Check level requirement //Post GU5 we changed the way weapons are crafted. So we need to see if this is one of our "new" weapons int levelRequired = -1; int playerLevel = getLevel(objPlayer); if(hasObjVar(objWeapon, weapons.OBJVAR_WP_LEVEL)) { levelRequired = getIntObjVar(objWeapon, weapons.OBJVAR_WP_LEVEL); } else { levelRequired = dataTableGetInt(WEAPON_LEVEL_TABLE, template, "weapon_level"); } if(playerLevel < levelRequired) { hasCert = false; } } if (!hasCert && isGod(objPlayer)) { //sendSystemMessageTestingOnly(objPlayer, "GOD MODE: Passing Certification check due to GOD MODE"); hasCert = true; } if(hasCert) { utils.setScriptVar(objPlayer, "combat.weaponCertified", objWeapon); } return hasCert; } void applyCombatSpeedDelay(obj_id target, float delay, int duration) { int delayStampStart = getGameTime(); int delayStampEnd = delayStampStart + duration; utils.setScriptVar(target, "speedDelayEffectStart", delayStampStart); utils.setScriptVar(target, "speedDelayEffectEnd", delayStampEnd); utils.setScriptVar(target, "speedDelayPotency", delay); return; }//end applyCombatSpeedDelay //////////////////////////////////////////////////////////////////////////////////////////// // Combat Action datatable accessors //////////////////////////////////////////////////////////////////////////////////////////// void doCombatDebuffs (obj_id self) { if(!utils.hasScriptVar(self, "fltNonCombatHealthRegen")) { float fltHealthRegen = getHealthRegenRate(self); utils.setScriptVar(self, "fltNonCombatHealthRegen", fltHealthRegen); setRegenRate(self, HEALTH, 0); } } boolean clearCombatDebuffs (obj_id self) { if (getGameTime() > utils.getIntScriptVar(self, "incap.timeStamp")) { if(utils.hasScriptVar(self, "fltNonCombatHealthRegen")) { float fltHealthRegen = utils.getFloatScriptVar(self, "fltNonCombatHealthRegen"); setRegenRate(self, HEALTH, fltHealthRegen); utils.removeScriptVar(self, "fltNonCombatHealthRegen"); return true; } } return false; } /* void applyWeaponHinderance(obj_id objPlayer) { obj_id weapon = getHeldWeapon(objPlayer); applyWeaponHinderance(objPlayer, weapon); } void applyWeaponHinderance(obj_id owner, obj_id weapon) { if(!isIdValid(owner)) return; float hinderance = getWeaponHinderance(weapon); if(hinderance == 0.0f) { if(buff.hasBuff(owner, "weaponHinderance")) buff.removeBuff(owner, "weaponHinderance"); } else buff.applyBuff(owner, "weaponHinderance", 0f, hinderance); } void clearWeaponHinderance(obj_id player) { obj_id weapon = getCurrentWeapon(player); clearWeaponHinderance(player, weapon); } void clearWeaponHinderance(obj_id owner, obj_id weapon) { float hinderance = getWeaponHinderance(weapon); if(hinderance == 0.0f) { if(buff.hasBuff(owner, "weaponHinderance")) buff.removeBuff(owner, "weaponHinderance"); } else buff.applyBuff(owner, "weaponHinderance", 15f, hinderance); } float getWeaponHinderance(obj_id weapon) { float hinderance = 0f; if(isIdValid(weapon)) { int type = getWeaponType(weapon); switch(type) { case WEAPON_TYPE_HEAVY: hinderance = 20.0f; break; case WEAPON_TYPE_RIFLE: hinderance = 18.0f; break; case WEAPON_TYPE_LIGHT_RIFLE: hinderance = 13.0f; break; case WEAPON_TYPE_PISTOL: hinderance = 10.0f; break; } } return hinderance; } */ boolean isInCombat(obj_id objTarget) { return getState(objTarget, STATE_COMBAT) > 0 ? true : false; } string getAttackName(obj_id self) { string actionName = null; obj_id weapon = getCurrentWeapon(self); int weaponType = getWeaponType(weapon); if(utils.hasScriptVar(self, DO_ONCE_ATTACK_OVERRIDE)) { actionName = utils.getStringScriptVar(self, DO_ONCE_ATTACK_OVERRIDE); utils.removeScriptVar(self, DO_ONCE_ATTACK_OVERRIDE); return actionName; } if(utils.hasScriptVar(self, DEFAULT_ATTACK_OVERRIDE)) { return utils.getStringScriptVar(self, DEFAULT_ATTACK_OVERRIDE); } if(hasObjVar(self, DEFAULT_ATTACK_OVERRIDE)) { LOG("combat.scriptlib", "gcw getStringObjVar(self, DEFAULT_ATTACK_OVERRIDE): " + getStringObjVar(self, DEFAULT_ATTACK_OVERRIDE)); return getStringObjVar(self, DEFAULT_ATTACK_OVERRIDE); } if(getTemplateName(weapon).equals("object/weapon/ranged/droid/droid_flamethrower.iff")) { actionName = "ig88_flame_thrower"; } else if(getTemplateName(weapon).equals("object/weapon/ranged/droid/droid_flamethrower_nomuzzle.iff")) { actionName = "ig88_flame_thrower_vertical"; } else if(getTemplateName(weapon).equals("object/weapon/ranged/droid/droid_rocket_launcher.iff")) { actionName = "ig88_rocket_launcher"; } else if (combat.isRangedWeapon(weaponType)) { if (ai_lib.isHumanoid(self)) actionName = "rangedShot"; else actionName = "creatureRangedAttack"; } else if (combat.isMeleeWeapon(weaponType)) { if (ai_lib.isHumanoid(self)) actionName = "meleeHit"; else actionName = "creatureMeleeAttack"; } else if (combat.isLightsaberWeapon(weaponType)) { if (ai_lib.isHumanoid(self)) actionName = "saberHit"; else actionName = "creatureMeleeAttack"; } return actionName; } string getBestAction(obj_id player, string actionName) { // Loop through possible better actions return highest one. for (int i = 2; i > 0; i--) { string testCmd = actionName+"_"+i; if (hasCommand(player, testCmd)) return testCmd; } return actionName; } string getActionAnimation(combat_data actionData, string weaponType) { //sendSystemMessageTestingOnly(obj_id.getObjId(10219418), "getActionAnimation for " + weaponType); string animation = ""; if(weaponType.equals("unarmed")) { animation = actionData.anim_unarmed; } else if(weaponType.equals("onehandmelee")) { //sendSystemMessageTestingOnly(obj_id.getObjId(10219418), "Matched OHM - '" + actionData.anim_onehandmelee); animation = actionData.anim_onehandmelee; } else if(weaponType.equals("twohandmelee")) { animation = actionData.anim_twohandmelee; } else if(weaponType.equals("polearm")) { animation = actionData.anim_polearm; } else if(weaponType.equals("lightRifle")) { animation = actionData.anim_lightRifle; } else if(weaponType.equals("pistol")) { animation = actionData.anim_pistol; } else if(weaponType.equals("carbine")) { animation = actionData.anim_carbine; } else if(weaponType.equals("rifle")) { animation = actionData.anim_rifle; } else if(weaponType.equals("heavyweapon")) { animation = actionData.anim_heavyweapon; } else if(weaponType.equals("thrown")) { animation = actionData.anim_thrown; } else if(weaponType.equals("thrown")) { animation = actionData.anim_thrown; } else if(weaponType.equals("onehandlightsaber")) { animation = actionData.anim_onehandlightsaber; } else if(weaponType.equals("twohandlightsaber")) { animation = actionData.anim_twohandlightsaber; } else if(weaponType.equals("polearmlightsaber")) { animation = actionData.anim_polearmlightsaber; } if(animation.length() < 1) { animation = actionData.animDefault; } if (animation.startsWith("*creature_attack")) { //sendSystemMessageTestingOnly(obj_id.getObjId(10219418), "Creature Animation"); return getCreatureAnimationName(animation); } // Split out comma delimited animations and pick one randomly string[] anims = split(animation, ','); if (anims.length > 1) { animation = anims[rand(0, anims.length - 1)]; } //sendSystemMessageTestingOnly(obj_id.getObjId(10219418), "Final Animation = " + animation); return animation; } string getCreatureAnimationName(string playbackName) { obj_id self = getSelf(); string animName = ""; int niche = ai_lib.aiGetNiche(self); //if (niche == NICHE_DROID || niche == NICHE_VEHICLE) if (niche == NICHE_VEHICLE) { animName ="droid_attack"; } else { if(playbackName.endsWith("ranged")) { animName = "creature_attack_ranged"; } else if(playbackName.endsWith("melee")) { // Default Melee attacks have a chance to trigger one of the following attacks: // creature_attack_heavy // creature_attack_light // creature_attack_special_1_heavy // creature_attack_special_1_light if(rand(1,2) == 1) animName = "creature_attack"; else animName = "creature_attack_special_1"; if(rand(1,2) == 1) animName += "_light"; else animName += "_heavy"; return animName; } else animName = "creature_attack_special_"+rand(1,2); } animName += "_medium"; return animName; } obj_id makeTrackerEgg(obj_id objPlayer, location locTest, combat_data actionData) { obj_id objEgg = createObject(actionData.delayAttackEggTemplate, locTest); if(!isIdValid(objEgg)) { return null; } dictionary combatDict = combat_engine.getCombatDataDictionary(actionData); utils.setScriptVar(objEgg, "combatDataDict", combatDict); utils.setScriptVar(objEgg, "objOwner", objPlayer); utils.setScriptVar(objPlayer, "objEgg", objEgg); attachScript(objEgg, "systems.combat.combat_delayed_tracker"); return objEgg; } // Bitwise functions for use with Weapon Type Restrictions changes by AHunter (with minor stuff by EJMoreland) int makeBit(int bit) { return (1<<(bit-1)); } boolean hasBit(int weaponType, int bitValue) { if(weaponType == -1) return false; return ((weaponTypeToBitValueMap[weaponType] & bitValue) != 0); } float doCriticalHitEffect(attacker_data attackerData, defender_data defenderData, weapon_data weaponData, hit_result hitData, combat_data actionData) { int elementalType = weaponData.elementalType; float elementalDamage = weaponData.elementalValue; float damageMod = 1.5f; hitData.critDamage =2 ; // 2: standard crit int critResist = 0; float heatResist = 0; float coldResist = 0; float acidResist = 0; float electricResist = 0; int resistRoll = rand(1, 10000); // Only do any special elemental weapon stuff if we get some of our damage from a weapon. if(actionData.percentAddFromWeapon > 0.0f) { if(utils.hasScriptVar(defenderData.id, armor.SCRIPTVAR_CACHED_GENERAL_PROTECTION)) { critResist += utils.getIntScriptVar(defenderData.id, armor.SCRIPTVAR_CACHED_GENERAL_PROTECTION); } if(utils.hasScriptVar(defenderData.id, armor.SCRIPTVAR_CACHED_SPECIAL_PROTECTIONS) ) { dictionary protections = utils.getDictionaryScriptVar(defenderData.id, armor.SCRIPTVAR_CACHED_SPECIAL_PROTECTIONS); heatResist = protections.getFloat("heat"); coldResist = protections.getFloat("cold"); acidResist = protections.getFloat("acid"); electricResist = protections.getFloat("electricity"); } switch(elementalType) { case DAMAGE_ELEMENTAL_HEAT: if(resistRoll > critResist + heatResist) { int level = getLevel(attackerData.id); if(level == 90) { buff.applyBuff(defenderData.id, attackerData.id, "crit_fire_dot_4"); } else if(level >= 75) { buff.applyBuff(defenderData.id, attackerData.id, "crit_fire_dot_3"); } else if(level >= 50) { buff.applyBuff(defenderData.id, attackerData.id, "crit_fire_dot_2"); } else if(level >= 25) { buff.applyBuff(defenderData.id, attackerData.id, "crit_fire_dot_1"); } damageMod = 1.5f; hitData.critDamage = 3; // 3: heat crit return damageMod; } break; case DAMAGE_ELEMENTAL_COLD: if(resistRoll > critResist + coldResist && !buff.hasBuff(defenderData.id, "criticalImmunity")) { if(!buff.hasBuff(defenderData.id, "criticalColdEffect")) { buff.applyBuff(defenderData.id, "criticalColdEffect", 6, (float)elementalDamage / 10.0f); } damageMod = 1.5f; hitData.critDamage = 4; // 4: cold crit return damageMod; } break; case DAMAGE_ELEMENTAL_ACID: if(resistRoll > critResist + acidResist && !buff.hasBuff(defenderData.id, "criticalAcidEffect") && !buff.hasBuff(defenderData.id, "criticalImmunity")) { buff.applyBuff(defenderData.id, "criticalAcidEffect"); damageMod = 1.5f; hitData.critDamage = 5; // 5: acid crit return damageMod; } break; case DAMAGE_ELEMENTAL_ELECTRICAL: if(resistRoll > critResist + electricResist && !buff.hasBuff(defenderData.id, "criticalElectricEffect") && !buff.hasBuff(defenderData.id, "criticalImmunity")) { buff.applyBuff(defenderData.id, "criticalElectricEffect"); damageMod = 1.5f; hitData.critDamage = 6; // 6: electric crit return damageMod; } break; } } // sendSystemMessageTestingOnly(attackerData.id, "specialEffectChance " + specialEffectChance + ". hitData.critDamage " + hitData.critDamage); return damageMod; } color getCriticalColor(int hitDataCritDamage) { color criticalColor = colors.MEDIUMVIOLETRED; switch(hitDataCritDamage) { case 1: criticalColor = colors.SLATEGREY; break; case 3: criticalColor = colors.ORANGERED; break; case 4: criticalColor = colors.LIGHTSKYBLUE; break; case 5: criticalColor = colors.DARKOLIVEGREEN; break; case 6: criticalColor = colors.SILVER; break; } return criticalColor; } obj_id directDamageToDifferentTarget(obj_id attacker, obj_id defender) { if(buff.hasBuff(defender, "bm_shield_master_player")) { obj_id guardian = beast_lib.getBeastOnPlayer(defender); if(isIdValid(guardian) && exists(guardian) && !isDead(guardian) && !isIncapacitated(guardian) && getEnhancedSkillStatisticModifierUncapped(defender, "damage_immune") <= 0) // Stasis { return guardian; } } if(utils.hasScriptVar(defender, DAMAGE_REDIRECT)) { obj_id guardian = utils.getObjIdScriptVar(defender, DAMAGE_REDIRECT); // Non-mobile guardians (barricades) should return before checking isDead, incap, and pvpCanAttack if(isIdValid(guardian) && exists(guardian) && !isMob(guardian) && getEnhancedSkillStatisticModifierUncapped(defender, "damage_immune") <= 0) { return guardian; } if(isIdValid(guardian) && exists(guardian) && isMob(guardian) && !isDead(guardian) && !isIncapacitated(guardian) && getEnhancedSkillStatisticModifierUncapped(defender, "damage_immune") <= 0 && // Stasis pvpCanAttack(attacker, guardian)) // Cannot use damage redirection on a target that the attacker cannot attack. { return guardian; } else { utils.removeScriptVar(defender, DAMAGE_REDIRECT); } } return defender; } float getMissChance(attacker_data attackerData, defender_data defenderData, combat_data actionData, boolean autoAim) { obj_id attacker = attackerData.id; obj_id defender = defenderData.id; float missChance = isPlayer(attacker) ? 0.0f : 5.0f; if (autoAim && isRangedWeapon(getCurrentWeapon(attacker))) { missChance += 5.0f; // 1 on a d20 is always a miss boolean attackerMovePenalty = (getLocomotion(attacker) == LOCOMOTION_RUNNING && isRangedWeapon(getCurrentWeapon(attacker))); boolean defenderMovePenalty = (getLocomotion(defender) == LOCOMOTION_RUNNING && isRangedWeapon(getCurrentWeapon(defender))); if (isPlayer(attacker) && attackerMovePenalty) missChance += 5.0f; if (defenderMovePenalty) missChance += 3.0f; } if(isRangedWeapon(getCurrentWeapon(attackerData.id))) { missChance += ((float)getEnhancedSkillStatisticModifierUncapped(defenderData.id, "combat_ranged_attack_avoidance") / 10.0f); missChance -= ((float)getEnhancedSkillStatisticModifierUncapped(defenderData.id, "combat_ranged_attack_vulnerability") / 10.0f); } if(isMeleeWeapon(getCurrentWeapon(attackerData.id)) || isLightsaberWeapon(getCurrentWeapon(attackerData.id))) { missChance += ((float)getEnhancedSkillStatisticModifierUncapped(defenderData.id, "combat_melee_attack_avoidance") / 10.0f); missChance -= ((float)getEnhancedSkillStatisticModifierUncapped(defenderData.id, "combat_melee_attack_vulnerability") / 10.0f); } missChance += getAttackerMissChance(attackerData, actionData, autoAim); missChance += getDefenderMissChance(defenderData, actionData, autoAim); missChance -= attackerData.hitChance; missChance += defenderData.increaseMiss; return missChance; } float getAttackerMissChance(attacker_data attackerData, combat_data actionData, boolean autoAim) { float attackerMissChance = 0.0f; if (isRangedWeapon(getCurrentWeapon(attackerData.id))) { attackerMissChance += ((float)getEnhancedSkillStatisticModifierUncapped(attackerData.id, "combat_ranged_attack_miss") / 10.0f); attackerMissChance -= ((float)getEnhancedSkillStatisticModifierUncapped(attackerData.id, "combat_ranged_attack_miss_reduction") / 10.0f); } if (isMeleeWeapon(getCurrentWeapon(attackerData.id)) || isLightsaberWeapon(getCurrentWeapon(attackerData.id))) { attackerMissChance += ((float)getEnhancedSkillStatisticModifierUncapped(attackerData.id, "combat_melee_attack_miss") / 10.0f); attackerMissChance -= ((float)getEnhancedSkillStatisticModifierUncapped(attackerData.id, "combat_melee_attack_miss_reduction") / 10.0f); } attackerMissChance += ((float)getEnhancedSkillStatisticModifierUncapped(attackerData.id, "combat_all_attack_miss") / 10.0f); attackerMissChance -= ((float)getEnhancedSkillStatisticModifierUncapped(attackerData.id, "combat_all_attack_miss_reduction") / 10.0f); return attackerMissChance; } float getDefenderMissChance(defender_data defenderData, combat_data actionData, boolean autoAim) { float defenderMissChance = 0.0f; defenderMissChance += ((float)getEnhancedSkillStatisticModifierUncapped(defenderData.id, "combat_all_attack_avoidance") / 10.0f); defenderMissChance -= ((float)getEnhancedSkillStatisticModifierUncapped(defenderData.id, "combat_all_attack_miss_vulnerability") / 10.0f); return defenderMissChance; } float getGlancingBlowChance(attacker_data attackerData, defender_data defenderData, combat_data actionData) { obj_id attacker = attackerData.id; obj_id defender = defenderData.id; float glancingChance = 0.0f; if(isMob(defender) && beast_lib.isBeast(defender)) glancingChance += (int)beast_lib.getBeastGlanceChance(defender); // Players have a 5% chance per level to land a glancing blow on a NPC if((isPlayer(attacker) || beast_lib.isBeast(attacker)) && (!isPlayer(defender) && !beast_lib.isBeast(defender))) { int attackerLevel = getLevel(attacker); int defenderLevel = getLevel(defender); int difficultyClass = getIntObjVar(defender, "difficultyClass"); if(attackerLevel < defenderLevel) glancingChance = 5.0f * (float)(defenderLevel - attackerLevel); switch (difficultyClass) { case 1: if (glancingChance < 5.0f) glancingChance = 5.0f; break; case 2: if (glancingChance < 15.0f) glancingChance = 15.0f; } } //Mobs below the reduced player level have a 2% per level chance to land a glancing blow. if ((!isPlayer(attacker) && !beast_lib.isBeast(attacker)) && (isPlayer(defender) || beast_lib.isBeast(defender))) { int attackerLevel = getLevel(attacker); int defenderLevel = getLevel(defender); defenderLevel = Math.round((float)defenderLevel * 0.75f); if (attackerLevel < defenderLevel) glancingChance = 2.0f * (float)(defenderLevel - attackerLevel); } glancingChance += defenderData.glancingChance; glancingChance -= attackerData.reduceGlancing; boolean isRangedAttacker = isRangedWeapon(getCurrentWeapon(attacker)); boolean isRangedDefender = isRangedWeapon(getCurrentWeapon(defender)); if(isRangedAttacker) // ranged weapon { glancingChance += (float)getEnhancedSkillStatisticModifierUncapped(defender, "expertise_glancing_blow_ranged"); if(armor.hasExpertiseArmorSetBonus(defender)) glancingChance += (float)getEnhancedSkillStatisticModifierUncapped(defender, "expertise_armorset_glancing_blow_ranged_" + armor.getExpertiseArmorSetId(defender)); } if(!isRangedAttacker) // melee weapon { glancingChance += (float)getEnhancedSkillStatisticModifierUncapped(defender, "expertise_glancing_blow_melee"); if(armor.hasExpertiseArmorSetBonus(defender)) glancingChance += (float)getEnhancedSkillStatisticModifierUncapped(defender, "expertise_armorset_glancing_blow_melee_" + armor.getExpertiseArmorSetId(defender)); } glancingChance -= actionData.reduceGlancing; if(beast_lib.isBeast(defender)) { LOG("beastDamage", "glancingChance: " + glancingChance); } return glancingChance; } float getDefenderGlancingBlowChance(obj_id defender) { float glancingChance = 0.0f; boolean isRangedDefender = isRangedWeapon(getCurrentWeapon(defender)); if(!isRangedDefender) { glancingChance += (float)getEnhancedSkillStatisticModifierUncapped(defender, "expertise_glancing_blow_melee_defense"); } glancingChance += (float)getEnhancedSkillStatisticModifierUncapped(defender, "expertise_glancing_blow_all"); glancingChance += (float)utils.getIntScriptVar(defender, "expertise_stance_glance"); glancingChance += (getDiminishedReturnValue((float)getEnhancedSkillStatisticModifierUncapped(defender, "combat_glancing") , DR_DEFENDER_GLANCE) / 10.0f); return glancingChance; } float getAttackerGlancingReduction(obj_id attacker) { float glancingReduce = 0.0f; if (beast_lib.isBeastMaster(attacker) || beast_lib.isBeast(attacker)) glancingReduce += beast_lib.getBeastModGlancingReduction(attacker); glancingReduce += (float)(getEnhancedSkillStatisticModifierUncapped(attacker, "combat_glancing_blow_reduction") / 7.0f); glancingReduce += (float)(getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_glancing_blow_reduction")); glancingReduce -= (float)getEnhancedSkillStatisticModifierUncapped(attacker, "glancing_blow_vulnerable"); return glancingReduce; } float getPunishingBlowChance(attacker_data attackerData, defender_data defenderData) { obj_id attacker = attackerData.id; obj_id defender = defenderData.id; float punishingChance = 0.0f; // NPC have a 5% chance per level to land a punishing blow on a player if ((!isPlayer(attacker) && !beast_lib.isBeast(attacker)) && (isPlayer(defender) || beast_lib.isBeast(defender))) { int attackerLevel = getLevel(attacker); int defenderLevel = getLevel(defender); if (attackerLevel > defenderLevel) punishingChance = 5.0f * (float)(attackerLevel - defenderLevel); int difficultyClass = getIntObjVar(attacker, "difficultyClass"); switch (difficultyClass) { case 1: if (punishingChance < 5.0f) punishingChance = 5.0f; break; case 2: if (punishingChance < 15.0f) punishingChance = 15.0f; } } punishingChance -= defenderData.reducePunishing; return punishingChance; } float getDefenderPunishingBlowReduction(obj_id defender) { float punishingChance = 0.0f; if (beast_lib.isBeastMaster(defender) || beast_lib.isBeast(defender)) punishingChance += beast_lib.getBeastModPunishingReduction(defender); return punishingChance; } float getDodgeChance(attacker_data attackerData, defender_data defenderData, combat_data actionData) { obj_id attacker = attackerData.id; obj_id defender = defenderData.id; float dodgeChance = 0.0f; dodgeChance += defenderData.dodgeChance; dodgeChance -= attackerData.reduceDodge; dodgeChance -= actionData.reduceDodge; boolean isRangedAttacker = isRangedWeapon(getCurrentWeapon(attacker)); if(isRangedAttacker) // ranged weapon { if(armor.hasExpertiseArmorSetBonus(defender)) dodgeChance += (float)getEnhancedSkillStatisticModifierUncapped(defender, "expertise_armorset_ranged_dodge_" + armor.getExpertiseArmorSetId(defender)); } if(!isRangedAttacker) // melee weapon { if(armor.hasExpertiseArmorSetBonus(defender)) dodgeChance += (float)getEnhancedSkillStatisticModifierUncapped(defender, "expertise_armorset_melee_dodge_" + armor.getExpertiseArmorSetId(defender)); } return dodgeChance; } float getDefenderDodgeChance(obj_id player) { float dodgeChance = isPlayer(player) || beast_lib.isBeast(player)? 5.0f : (float)getLevel(player) * 0.10f; dodgeChance += ((float)getEnhancedSkillStatisticModifierUncapped(player, "agility_modified") / 100.0f); dodgeChance += ((float)getEnhancedSkillStatisticModifierUncapped(player, "agility") / 100.0f); dodgeChance += ((float)getEnhancedSkillStatisticModifierUncapped(player, "luck_modified")/ 300.0f); dodgeChance += ((float)getEnhancedSkillStatisticModifierUncapped(player, "luck") / 300.0f); dodgeChance += (getDiminishedReturnValue((float)getEnhancedSkillStatisticModifierUncapped(player, "combat_dodge"), DR_DEFENDER_DODGE) / 10.0f); dodgeChance += (float)getEnhancedSkillStatisticModifierUncapped(player, "expertise_dodge"); if (beast_lib.isBeastMaster(player) || beast_lib.isBeast(player)) dodgeChance += beast_lib.getBeastDodgeChance(player); return dodgeChance; } float getAttackerDodgeReduction(obj_id attacker) { float dodgeReduction = 0.0f; dodgeReduction += (float)(getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_dodge_reduction")); //Exotic Mod dodgeReduction += (float)(getEnhancedSkillStatisticModifierUncapped(attacker, "exotic_dodge_reduction") / 7.0f); dodgeReduction += (float)(getEnhancedSkillStatisticModifierUncapped(attacker, "combat_dodge_reduction") / 7.0f); return dodgeReduction; } float getParryChance(attacker_data attackerData, defender_data defenderData, combat_data actionData) { float parryChance = 0.0f; parryChance += defenderData.parryChance; parryChance -= attackerData.reduceParry; parryChance -= actionData.reduceParry; return parryChance; } float getDefenderParryChance(obj_id player) { float parrySkill = isPlayer(player) || beast_lib.isBeast(player) ? 5.0f : (float)getLevel(player) * 0.09f; parrySkill += (getDiminishedReturnValue((float)getEnhancedSkillStatisticModifierUncapped(player, "combat_parry"),DR_DEFENDER_PARRY) / 10.0f); parrySkill += ((float)getEnhancedSkillStatisticModifierUncapped(player, "precision_modified") / 200.0f ); parrySkill += ((float)getSkillStatisticModifier(player, "precision") / 200.0f); parrySkill += ((float)getEnhancedSkillStatisticModifierUncapped(player, "agility_modified") / 200.0f); parrySkill += ((float)getSkillStatisticModifier(player, "agility") / 200.0f); parrySkill += (float)getEnhancedSkillStatisticModifierUncapped(player, "expertise_parry"); if (beast_lib.isBeastMaster(player) || beast_lib.isBeast(player)) parrySkill += beast_lib.getBeastParryChance(player); parrySkill+= (float)getSkillStatisticModifier(player, "expertise_stance_saber_block"); boolean isSaberBlocking = buff.hasBuff(player, "saberBlock"); if (isSaberBlocking) { float enhancedSaberBlockSkillMod = (float)getSkillStatisticModifier(player, "expertise_saber_block"); parrySkill += enhancedSaberBlockSkillMod; } parrySkill = parrySkill > 75.0f ? 75.0f : parrySkill; return parrySkill; } float getAttackerParryReduction(obj_id attacker) { float parryReduction = 0.0f; parryReduction += (float)(getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_parry_reduction")); //Exotic Mod parryReduction += (float)(getEnhancedSkillStatisticModifierUncapped(attacker, "exotic_parry_reduction") / 7.0f); parryReduction += (float)(getEnhancedSkillStatisticModifierUncapped(attacker, "combat_parry_reduction") / 7.0f); return parryReduction; } float getBlockChance(attacker_data attackerData, defender_data defenderData, combat_data actionData) { float blockChance = 0.0f; obj_id attacker = attackerData.id; obj_id defender = defenderData.id; blockChance += defenderData.blockChance; blockChance -= attackerData.reduceBlock; blockChance -= actionData.reduceBlock; boolean isRangedAttacker = isRangedWeapon(getCurrentWeapon(attacker)); if(isRangedAttacker) // ranged weapon { if(armor.hasExpertiseArmorSetBonus(defender)) blockChance += (float)getEnhancedSkillStatisticModifierUncapped(defender, "expertise_armorset_ranged_block_" + armor.getExpertiseArmorSetId(defender)); } if(!isRangedAttacker) // melee weapon { if(armor.hasExpertiseArmorSetBonus(defender)) blockChance += (float)getEnhancedSkillStatisticModifierUncapped(defender, "expertise_armorset_melee_block_" + armor.getExpertiseArmorSetId(defender)); } return blockChance; } float getDefenderBlockChance(obj_id player) { float blockChance = isPlayer(player) ? 5.0f : (float)getLevel(player) * 0.14f; blockChance += ((float)getEnhancedSkillStatisticModifierUncapped(player, "precision_modified") / 200.0f ); blockChance += ((float)getEnhancedSkillStatisticModifierUncapped(player, "precision") / 200.0f); blockChance += ((float)getEnhancedSkillStatisticModifierUncapped(player, "strength_modified") / 200.0f); blockChance += ((float)getEnhancedSkillStatisticModifierUncapped(player, "strength") / 200.0f); blockChance += (getDiminishedReturnValue((float)getEnhancedSkillStatisticModifierUncapped(player, "combat_block_chance"),DR_DEFENDER_BLOCK) / 5.0f); blockChance += (float)getEnhancedSkillStatisticModifierUncapped(player, "expertise_block_chance"); if (beast_lib.isBeastMaster(player) || beast_lib.isBeast(player)) blockChance += beast_lib.getBeastBlockChance(player); if (blockChance > 30.0f) { int nextBlockLog = utils.getIntScriptVar(player, "nextBlockLog"); if (getGameTime() > nextBlockLog) { float fromBlockChance = (float)getEnhancedSkillStatisticModifierUncapped(player, "combat_block_chance") / 5.0f; float fromBlockChanceDiminished = (getDiminishedReturnValue((float)getEnhancedSkillStatisticModifierUncapped(player, "combat_block_chance"),DR_DEFENDER_BLOCK) / 5.0f); float fromPreMod = (float)getEnhancedSkillStatisticModifierUncapped(player, "precision_modified") / 200.0f; float fromPre = (float)getEnhancedSkillStatisticModifierUncapped(player, "precision") / 200.0f; float fromStrMod = (float)getEnhancedSkillStatisticModifierUncapped(player, "strength_modified") / 200.0f; float fromStr = (float)getEnhancedSkillStatisticModifierUncapped(player, "strength") / 200.0f; float fromExChance = (float)getEnhancedSkillStatisticModifierUncapped(player, "expertise_block_chance"); float fromBeastMod = 0.0f; if (beast_lib.isBeastMaster(player) || beast_lib.isBeast(player)) fromBeastMod += beast_lib.getBeastBlockChance(player); utils.setScriptVar(player, "nextBlockLog", getGameTime() + 600); CustomerServiceLog("BLOCK_CHANCE", "Player %TU total block chance: 5% + "+blockChance+ " = combat_block_chance("+fromBlockChance+ "/ DRF: "+fromBlockChanceDiminished+ ") + precision_modified("+fromPreMod+ ") + precision("+fromPre+ ") + strength_modified("+fromStrMod+ ") + strength("+fromStr+ ") + Expertise Block("+fromExChance+ ") + beast_mod("+fromBeastMod+")", player); } } return blockChance; } float getAttackerBlockReduction(obj_id attacker) { float blockReduction = 0.0f; blockReduction += (float)(getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_block_reduction")); //Exotic Mod blockReduction += (float)(getEnhancedSkillStatisticModifierUncapped(attacker, "exotic_block_reduction") / 5.0f); blockReduction += (float)(getEnhancedSkillStatisticModifierUncapped(attacker, "combat_block_reduction") / 5.0f); return blockReduction; } int getBlockAmmount(obj_id player) { int blockAmmount = isPlayer(player) || beast_lib.isBeast(player) ? 25 : Math.round(getLevel(player) * 2); float blockStat = 0.0f; blockStat += (float)getEnhancedSkillStatisticModifierUncapped(player, "strength_modified"); blockStat += (float)getSkillStatisticModifier(player, "strength"); blockStat += (float)getEnhancedSkillStatisticModifierUncapped(player, "combat_block_value"); blockStat = Math.round((float)blockStat * 0.5f); blockAmmount += blockStat; if (beast_lib.isBeastMaster(player) || beast_lib.isBeast(player)) blockAmmount += beast_lib.getBeastBlockValue(player); return blockAmmount; } float getEvasionChance(attacker_data attackerData, defender_data defenderData) { float evasionChance = 0.0f; evasionChance += defenderData.evadeChance; evasionChance -= attackerData.reduceEvade; return evasionChance; } float getDefenderEvasionChance(obj_id player) { float evasionChance = isPlayer(player) || beast_lib.isBeast(player) ? 5.0f : ((float)getLevel(player) * 0.2f); evasionChance += ((float)getEnhancedSkillStatisticModifierUncapped(player, "agility_modified") / 100.0f); evasionChance += ((float)getSkillStatisticModifier(player, "agility") / 100.0f); evasionChance += ((float)getEnhancedSkillStatisticModifierUncapped(player, "luck_modified")/ 300.0f); evasionChance += ((float)getSkillStatisticModifier(player, "luck") / 300.0f); evasionChance += (getDiminishedReturnValue((float)getEnhancedSkillStatisticModifierUncapped(player, "combat_evasion_chance"), DR_DEFENDER_EVADE) / 10.0f ); evasionChance += (float)getEnhancedSkillStatisticModifierUncapped(player, "expertise_evasion_chance"); if (beast_lib.isBeastMaster(player) || beast_lib.isBeast(player)) evasionChance += beast_lib.getBeastEvadeChance(player); return evasionChance; } float getAttackerEvasionReduction(obj_id attacker) { return 0.0f; } float getHitReductionChance(obj_id player) { return getEnhancedSkillStatisticModifierUncapped(player, "expertise_critical_hit_reduction"); } float getPvPHitReductionChance(obj_id player) { return getEnhancedSkillStatisticModifierUncapped(player, "expertise_critical_hit_pvp_reduction"); } float getEvasionRoll(obj_id player) { float evasionRoll = 0.0f; // -1 is evade all damage evasionRoll += ((float)getEnhancedSkillStatisticModifierUncapped(player, "combat_evasion_value") / 4.0f); if (evasionRoll < 0) return -1.0f; evasionRoll += 10.0f; evasionRoll += ((float)getEnhancedSkillStatisticModifierUncapped(player, "luck_modified")/ 10.0f); evasionRoll += ((float)getSkillStatisticModifier(player, "luck") / 10.0f); if (beast_lib.isBeastMaster(player) || beast_lib.isBeast(player)) evasionRoll += beast_lib.getBeastEvadeValue(player); return evasionRoll > 99.0f ? 99.0f : evasionRoll; } float getCriticalHitChance(attacker_data attackerData, defender_data defenderData, combat_data actionData, boolean autoAiming) { obj_id attacker = attackerData.id; obj_id defender = defenderData.id; float critChance = attackerData.critChance; float altCritChance = 0.0f; string specialLine = actionData.specialLine; critChance += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_critical_line_" + specialLine); // +3% for manual aim if(!autoAiming && isPlayer(attacker)) { critChance += 3.0f; } // Defender niche mods float specificDefenderMod = 0.0f; if(isPlayer(defender) ) // Criticals against PCs { altCritChance += attackerData.pvpCrit; } else if(ai_lib.isDroid(defender) || ai_lib.isAndroid(defender)) // Crits against Droids and Android { altCritChance += attackerData.droidCrit; } else if(ai_lib.isNpc(defender)) // NPC { altCritChance += attackerData.npcCrit; } else if(ai_lib.isMonster(defender)) // Monster { altCritChance += attackerData.creatureCrit; } altCritChance = getDiminishedReturnValue(altCritChance, DR_ATTACKER_CRITICAL_2); critChance -= defenderData.reduceCritical; if(isPlayer(defender) && (isPlayer(attacker) || (isMob(attacker) && isIdValid(getMaster(attacker)) && isPlayer(getMaster(attacker))))) { critChance -= (float)getEnhancedSkillStatisticModifierUncapped(defender, "expertise_critical_hit_pvp_reduction"); } critChance += actionData.increaseCritical; critChance += altCritChance; return critChance; } float getAttackerCritMod(obj_id attacker) { float critMod = isPlayer(attacker) ? 5.0f : (float)getLevel(attacker) * 0.15f; float dr_critMod = 0.0f; float dr_critMod2 = 0.0f; critMod += (float)utils.getIntScriptVar(attacker, "expertise_stance_critical"); critMod += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_critical_niche_all"); critMod += (getDiminishedReturnValue((float)getEnhancedSkillStatisticModifierUncapped(attacker, "combat_critical_hit"), DR_ATTACKER_CRITICAL) / 10.0f); if (beast_lib.isBeastMaster(attacker) || beast_lib.isBeast(attacker)) critMod += beast_lib.getBeastCritChance(attacker); // Get stat and weapon cat bonuses critMod += ((float)getEnhancedSkillStatisticModifierUncapped(attacker, "precision_modified") / 100.0f ); critMod += ((float)getSkillStatisticModifier(attacker, "precision") / 100.0f); critMod += ((float)getEnhancedSkillStatisticModifierUncapped(attacker, "luck_modified") / 300.0f); critMod += ((float)getSkillStatisticModifier(attacker, "luck") / 300.0f); obj_id weapon = getCurrentWeapon(attacker); if (!isIdValid(weapon)) return critMod; weapon_data weaponData = weapons.getNewWeaponData(weapon); if (weaponData == null) { CustomerServiceLog ("COMBAT_exception", "getAttackerCritMod: Player %TU caused an exception with bad cached data on weapon "+weapon+"/"+getTemplateName(weapon), attacker); return critMod; } boolean isRangedAttacker = isRangedWeapon(weapon); if(isRangedAttacker) // ranged weapon, use precision mods { dr_critMod2 += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_critical_ranged"); dr_critMod += ((float)getEnhancedSkillStatisticModifierUncapped(attacker, "combat_critical_ranged")/10.0f); critMod += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_undiminished_critical_ranged"); } else { dr_critMod2 += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_critical_melee"); dr_critMod += ((float)getEnhancedSkillStatisticModifierUncapped(attacker, "combat_critical_melee")/10.0f); critMod += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_undiminished_critical_melee"); } // Weapon stat bonuses switch(weaponData.weaponType) { case WEAPON_TYPE_PISTOL: dr_critMod2 += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_critical_pistol"); critMod += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_undiminished_critical_pistol"); break; case WEAPON_TYPE_RIFLE: dr_critMod2 += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_critical_rifle"); critMod += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_undiminished_critical_rifle"); break; case WEAPON_TYPE_LIGHT_RIFLE: dr_critMod2 += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_critical_carbine"); critMod += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_undiminished_critical_carbine"); break; case WEAPON_TYPE_HEAVY: dr_critMod2 += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_critical_heavy"); critMod += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_undiminished_critical_heavy"); break; case WEAPON_TYPE_GROUND_TARGETTING: dr_critMod2 += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_critical_heavy"); critMod += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_undiminished_critical_heavy"); break; case WEAPON_TYPE_DIRECTIONAL: dr_critMod2 += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_critical_heavy"); critMod += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_undiminished_critical_heavy"); break; case WEAPON_TYPE_1HAND_MELEE: dr_critMod2 += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_critical_1h"); critMod += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_undiminished_critical_1h"); break; case WEAPON_TYPE_2HAND_MELEE: dr_critMod2 += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_critical_2h"); critMod += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_undiminished_critical_2h"); break; case WEAPON_TYPE_UNARMED: dr_critMod2 += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_critical_unarmed"); critMod += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_undiminished_critical_unarmed"); break; case WEAPON_TYPE_POLEARM: dr_critMod2 += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_critical_polearm"); critMod += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_undiminished_critical_polearm"); break; case WEAPON_TYPE_WT_1HAND_LIGHTSABER: case WEAPON_TYPE_WT_2HAND_LIGHTSABER: case WEAPON_TYPE_WT_POLEARM_LIGHTSABER: dr_critMod2 += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_critical_lightsaber"); critMod += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_undiminished_critical_lightsaber"); break; } dr_critMod = getDiminishedReturnValue(dr_critMod, DR_ATTACKER_CRITICAL); dr_critMod2 = getDiminishedReturnValue(dr_critMod2, DR_ATTACKER_CRITICAL_2); critMod += dr_critMod; critMod += dr_critMod2; return critMod; } float getDefenderCriticalChance(obj_id defender) { float critChance = 0.0f; critChance -= (float)getEnhancedSkillStatisticModifierUncapped(defender, "critical_hit_vulnerable"); critChance += ((float)getEnhancedSkillStatisticModifierUncapped(defender, "combat_critical_hit_reduction") / 3.0f); critChance += (float)getEnhancedSkillStatisticModifierUncapped(defender, "expertise_critical_hit_reduction"); return critChance; } float getStrikethroughChance(attacker_data attackerData, defender_data defenderData, combat_data actionData) { float strikethroughChance = 0.0f; strikethroughChance += attackerData.strikethroughChance; strikethroughChance -= defenderData.reduceStrikethrough; strikethroughChance += actionData.increaseStrikethrough; return strikethroughChance; } float getAttackerStrikethroughChance(obj_id player) { float strikethrough = isPlayer(player) || beast_lib.isBeast(player) ? 5.0f : (float)getLevel(player) * 0.08f; strikethrough += ((float)getEnhancedSkillStatisticModifierUncapped(player, "precision_modified") / 200.0f ); strikethrough += ((float)getSkillStatisticModifier(player, "precision") / 200.0f); strikethrough += ((float)getEnhancedSkillStatisticModifierUncapped(player, "luck_modified") / 200.0f); strikethrough += ((float)getSkillStatisticModifier(player, "luck") / 200.0f); strikethrough += (getDiminishedReturnValue((float)getEnhancedSkillStatisticModifierUncapped(player, "combat_strikethrough_chance"),DR_ATTACKER_STRIKETHROUGH) / 10.0f); strikethrough += (float)getEnhancedSkillStatisticModifierUncapped(player, "expertise_strikethrough_chance"); if (beast_lib.isBeastMaster(player) || beast_lib.isBeast(player)) strikethrough += beast_lib.getBeastStrikethroughChance(player); return strikethrough; } float getDefenderStrikethroughReduction(obj_id player) { float strikethroughChance = 0.0f; strikethroughChance -= (float)getEnhancedSkillStatisticModifierUncapped(player, "strikethrough_vulnerable"); return strikethroughChance; } float getStrikethroughValue(obj_id attacker, obj_id defender) { return getAttackerStrikethroughValue(attacker); } float getAttackerStrikethroughValue(obj_id player) { float strikethroughValue = isPlayer(player) ? 10.0f : 35.0f; strikethroughValue += ((float)getEnhancedSkillStatisticModifierUncapped(player, "luck_modified")/ 10.0f); strikethroughValue += ((float)getSkillStatisticModifier(player, "luck") / 10.0f); strikethroughValue += ((float)getEnhancedSkillStatisticModifierUncapped(player, "combat_strikethrough_value") / 2.0f); if (beast_lib.isBeastMaster(player) || beast_lib.isBeast(player)) strikethroughValue += beast_lib.getBeastStrikethroughValue(player); return strikethroughValue > 99.0f ? 99.0f : strikethroughValue; } float getToHitBonus(obj_id player) { float hitBonus = 0.0f; hitBonus += ((float)getEnhancedSkillStatisticModifierUncapped(player, "strength_modified") / 100.0f); hitBonus += ((float)getSkillStatisticModifier(player, "strength") / 100.0f); hitBonus += (float)getEnhancedSkillStatisticModifierUncapped(player, "expertise_to_hit"); if (beast_lib.isBeastMaster(player) || beast_lib.isBeast(player)) hitBonus += beast_lib.getBeastHitChance(player); return hitBonus; } float getDefenderHitReduction(obj_id player) { float increaseMiss = 0.0f; return increaseMiss; } attacker_data fillAttackerCombatAttributes(attacker_data attackerData) { if(!utils.hasScriptVar(attackerData.id, ATTACKER_DATA)) cacheAttackerData(attackerData.id); attackerData.critChance = utils.getFloatScriptVar(attackerData.id, ATTACKER_BASE_CRIT); attackerData.pvpCrit = utils.getFloatScriptVar(attackerData.id, ATTACKER_PVP_CRIT); attackerData.npcCrit = utils.getFloatScriptVar(attackerData.id, ATTACKER_NPC_CRIT); attackerData.droidCrit = utils.getFloatScriptVar(attackerData.id, ATTACKER_DROID_CRIT); attackerData.creatureCrit = utils.getFloatScriptVar(attackerData.id, ATTACKER_CREATURE_CRIT); attackerData.hitChance = utils.getFloatScriptVar(attackerData.id, ATTACKER_HIT_BONUS); attackerData.strikethroughChance = utils.getFloatScriptVar(attackerData.id, ATTACKER_STRIKETHROUGH); attackerData.reduceParry = utils.getFloatScriptVar(attackerData.id, ATTACKER_REDUCE_PARRY); attackerData.reduceBlock = utils.getFloatScriptVar(attackerData.id, ATTACKER_REDUCE_BLOCK); attackerData.reduceDodge = utils.getFloatScriptVar(attackerData.id, ATTACKER_REDUCE_DODGE); attackerData.reduceGlancing = utils.getFloatScriptVar(attackerData.id, ATTACKER_REDUCE_GLANCING); attackerData.reduceEvade = utils.getFloatScriptVar(attackerData.id, ATTACKER_REDUCE_EVADE); return attackerData; } defender_data fillDefenderCombatAttributes(defender_data defenderData) { if (!utils.hasScriptVar(defenderData.id, DEFENDER_DATA)) cacheDefenderData(defenderData.id); defenderData.reduceCritical = utils.getFloatScriptVar(defenderData.id, DEFENDER_REDUCE_CRIT); defenderData.increaseMiss = utils.getFloatScriptVar(defenderData.id, DEFENDER_INCREASE_MISS); defenderData.reduceStrikethrough = utils.getFloatScriptVar(defenderData.id, DEFENDER_REDUCE_STRIKETHROUGH); defenderData.reducePunishing = utils.getFloatScriptVar(defenderData.id, DEFENDER_REDUCE_PUNISH); defenderData.glancingChance = utils.getFloatScriptVar(defenderData.id, DEFENDER_GLANCING); defenderData.parryChance = utils.getFloatScriptVar(defenderData.id, DEFENDER_PARRY); defenderData.dodgeChance = utils.getFloatScriptVar(defenderData.id, DEFENDER_DODGE); defenderData.evadeChance = utils.getFloatScriptVar(defenderData.id, DEFENDER_EVADE); defenderData.blockChance = utils.getFloatScriptVar(defenderData.id, DEFENDER_BLOCK); return defenderData; } void cacheCombatData(obj_id self) { cacheAttackerData(self); cacheDefenderData(self); } void clearCachedCombatData(obj_id self) { utils.removeScriptVarTree(self, ATTACKER_DATA); utils.removeScriptVarTree(self, DEFENDER_DATA); } void cacheAttackerData(obj_id attacker) { utils.setScriptVar(attacker, ATTACKER_BASE_CRIT, getAttackerCritMod(attacker)); utils.setScriptVar(attacker, ATTACKER_PVP_CRIT, (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_critical_niche_pvp")); utils.setScriptVar(attacker, ATTACKER_DROID_CRIT, (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_critical_niche_droid")); utils.setScriptVar(attacker, ATTACKER_NPC_CRIT, (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_critical_niche_npc")); utils.setScriptVar(attacker, ATTACKER_CREATURE_CRIT,(float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_critical_niche_creature")); utils.setScriptVar(attacker, ATTACKER_HIT_BONUS, getToHitBonus(attacker)); utils.setScriptVar(attacker, ATTACKER_STRIKETHROUGH, getAttackerStrikethroughChance(attacker)); utils.setScriptVar(attacker, ATTACKER_REDUCE_PARRY, getAttackerParryReduction(attacker)); utils.setScriptVar(attacker, ATTACKER_REDUCE_BLOCK, getAttackerBlockReduction(attacker)); utils.setScriptVar(attacker, ATTACKER_REDUCE_DODGE, getAttackerDodgeReduction(attacker)); utils.setScriptVar(attacker, ATTACKER_REDUCE_GLANCING, getAttackerGlancingReduction(attacker)); utils.setScriptVar(attacker, ATTACKER_REDUCE_EVADE, getAttackerEvasionReduction(attacker)); } void cacheDefenderData(obj_id defender) { utils.setScriptVar(defender, DEFENDER_REDUCE_CRIT, getDefenderCriticalChance(defender)); utils.setScriptVar(defender, DEFENDER_INCREASE_MISS, getDefenderHitReduction(defender)); utils.setScriptVar(defender, DEFENDER_REDUCE_STRIKETHROUGH, getDefenderStrikethroughReduction(defender)); utils.setScriptVar(defender, DEFENDER_REDUCE_PUNISH, getDefenderPunishingBlowReduction(defender)); utils.setScriptVar(defender, DEFENDER_GLANCING, getDefenderGlancingBlowChance(defender)); utils.setScriptVar(defender, DEFENDER_PARRY, getDefenderParryChance(defender)); utils.setScriptVar(defender, DEFENDER_DODGE, getDefenderDodgeChance(defender)); utils.setScriptVar(defender, DEFENDER_EVADE, getDefenderEvasionChance(defender)); utils.setScriptVar(defender, DEFENDER_BLOCK, getDefenderBlockChance(defender)); } obj_id getRandomHateTarget(obj_id self) { obj_id[] potentialTargets = getHateList(self); if (potentialTargets == null || potentialTargets.length == 0) return null; float max_range = 64.0f; resizeable obj_id[] validTargets = new obj_id[0]; for (int i=0;i 0) isFreeshot = true; if (hitData.dodge == true && freeDodge > 0) isFreeshot = true; if (hitData.parry == true && freeParry > 0) isFreeshot = true; } if (hitData.success == true) { if (hitData.critical == true && freeCrit > 0) isFreeshot = true; if (hitData.strikethrough == true && freeStrike > 0) isFreeshot = true; } if (isFreeshot) { prose_package pp = new prose_package(); pp = prose.setStringId(pp, new string_id("spam", "freeshot")); showFlyTextPrivateProseWithFlags(attacker, attacker, pp, 1.5f, colors.LEMONCHIFFON, FLY_TEXT_FLAG_IS_FREESHOT ); return freeShot; } int[] shotCost = getActionCost(attacker, weaponData, actionData); return shotCost; } void setPersistCombatMode(obj_id target, boolean state) { if (!isMob(target)) return; if (!state) { if (utils.hasScriptVar(target, combat.PERSIST_COMBAT)) utils.removeScriptVar(target, combat.PERSIST_COMBAT); return; } utils.setScriptVar(target, combat.PERSIST_COMBAT, 1); return; } boolean isPersistCombatMode(obj_id self) { return utils.hasScriptVar(self, combat.PERSIST_COMBAT); } boolean isValidPersistCombat(obj_id self) { if (isInvulnerable(self)) return false; if (!getCreatureCoverVisibility(self)) return false; if (isDead(self)) return false; obj_id[] hateList = getHateList(self); if (hateList == null || hateList.length == 0) return false; boolean hasLos = true; for (int i=0;i 0; boolean defenderImmune = getEnhancedSkillStatisticModifierUncapped(defender, "damage_immune") > 0; prose_package pp = new prose_package(); boolean isBeneficial = (actionData.hitType == NON_ATTACK || actionData.hitType == NON_DAMAGE_ATTACK) && pvpCanHelp(attacker, defender); if (attackerImmune && !isBeneficial) { pp.stringId = new string_id("cbt_spam", "immune_hit_other"); pp.target.set(defender); pp.other.set(new string_id("cmd_n", actionData.actionName)); sendCombatSpamMessageProse(attacker, pp); pp.target.set(attacker); pp.stringId = new string_id("cbt_spam", "immune_target_hit_other"); sendCombatSpamMessageProse(defender, pp); } if (defenderImmune && !isBeneficial) { pp.stringId = new string_id("cbt_spam", "immune_get_hit"); pp.target.set(attacker); pp.other.set(new string_id("cmd_n", actionData.actionName)); sendCombatSpamMessageProse(defender, pp); pp.target.set(defender); pp.stringId = new string_id("cbt_spam", "immune_target_hit"); sendCombatSpamMessageProse(attacker, pp); } return (attackerImmune || defenderImmune) && !isBeneficial; } float getDiminishedReturnValue(float value, int modType) { if (value < 0.0f) return value; float[] seedList = { 500.0f, // Crit 500.0f, // Strikethrough 500.0f, // Block 500.0f, // Dodge 500.0f, // Parry 500.0f, // Glancing 500.0f, // Evasion 50.0f // Crit2 }; // This block for testing only. /*if (modType == DR_DEFENDER_BLOCK) { debugSpeakMsg(getSelf(), "Pre-Value: "+value+", Type: "+modType); debugSpeakMsg(getSelf(), "Post-Value: "+value * ( 1.0f - (value / (value + seedList[DR_DEFENDER_BLOCK])))+", Type: "+modType); }*/ switch (modType) { case DR_ATTACKER_CRITICAL: return (value * ( 1.0f - (value / (value + seedList[DR_ATTACKER_CRITICAL])))); case DR_ATTACKER_STRIKETHROUGH: return (value * ( 1.0f - (value / (value + seedList[DR_ATTACKER_STRIKETHROUGH])))); case DR_DEFENDER_BLOCK: return (value * ( 1.0f - (value / (value + seedList[DR_DEFENDER_BLOCK])))); case DR_DEFENDER_DODGE: return (value * ( 1.0f - (value / (value + seedList[DR_DEFENDER_DODGE])))); case DR_DEFENDER_PARRY: return (value * ( 1.0f - (value / (value + seedList[DR_DEFENDER_PARRY])))); case DR_DEFENDER_GLANCE: return (value * ( 1.0f - (value / (value + seedList[DR_DEFENDER_GLANCE])))); case DR_DEFENDER_EVADE: return (value * ( 1.0f - (value / (value + seedList[DR_DEFENDER_EVADE])))); case DR_ATTACKER_CRITICAL_2: return (value * ( 1.0f - (value / (value + seedList[DR_ATTACKER_CRITICAL_2])))); } return value; } void mirrorArmor(obj_id commando) { obj_id[] haters = getHateList(commando); if (haters != null || haters.length > 0) { for(int i=0;i 50) delta = 50; setKillMeter(player, delta); return true; } boolean canDrainKillMeter(obj_id player, int value) { if (!isPlayer(player)) return true; int current = getKillMeter(player); int delta = current - value; return delta < 0 ? false : true; } boolean drainKillMeter(obj_id player, int value) { if (!isPlayer(player)) return true; if (!canDrainKillMeter(player, value)) return false; return modifyKillMeter(player, -1*value); } location getCommandGroundTargetLocation(string params) { // ground target. Location is passed in the params string[] stringLocationParams = split(params, ' '); if(stringLocationParams == null || stringLocationParams.length <= 0) return null; location eggLoc = new location(); boolean inCell = false; //check to see if we are in a cell if(isIdValid(utils.stringToObjId(stringLocationParams[3]))) { inCell = true; } //if we are not in a cell, we need to use the first x y z if(!inCell) { eggLoc.x = utils.stringToFloat(stringLocationParams[0]); eggLoc.y = utils.stringToFloat(stringLocationParams[1]); eggLoc.z = utils.stringToFloat(stringLocationParams[2]); } //if we are in a cell, we need to use the 2nd x y z else { eggLoc.cell = utils.stringToObjId(stringLocationParams[3]); eggLoc.x = utils.stringToFloat(stringLocationParams[4]); eggLoc.y = utils.stringToFloat(stringLocationParams[5]); eggLoc.z = utils.stringToFloat(stringLocationParams[6]); } return eggLoc; } float getDevastationChance(obj_id attacker) { // 2% base float devastationBonus = 2.0f; //Commando Super Devistation Bonus devastationBonus += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "commando_devastation"); devastationBonus += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_devastation_bonus") / 10.0f; return devastationBonus; } float getElementalPenetration(obj_id attacker, string damageString) { float elementalPenetrationAmount = 0.0f; if ( isIdValid(attacker) && damageString != null && damageString.length() > 0 ) { string elementalPenetrationSkillModName = "exotic_" + damageString + "_penetration"; float elementalPenetrationSkillMod = (float)getEnhancedSkillStatisticModifierUncapped(attacker, elementalPenetrationSkillModName); //LOG("ARMOR_TEST", "getElementalPenetration elementalPenetrationSkillMod = " +elementalPenetrationSkillMod); if ( elementalPenetrationSkillMod > MAX_RE_EXOTIC_ELEMENTAL_PENETRATION_SKILLMOD ) elementalPenetrationSkillMod = MAX_RE_EXOTIC_ELEMENTAL_PENETRATION_SKILLMOD; elementalPenetrationAmount = 100.0f - (elementalPenetrationSkillMod * 2.5f); elementalPenetrationAmount *= 0.01f; } //LOG("ARMOR_TEST", "getElementalPenetration elementalPenetrationAmount = " +elementalPenetrationAmount); return elementalPenetrationAmount; } boolean hasPrescience(obj_id attacker, obj_id defender, combat_data actionData) { if (!buff.hasBuff(defender, "bh_prescience")) return false; obj_id owner = buff.getBuffCaster(defender, "bh_prescience"); if (attacker == owner) { prose_package pp = new prose_package(); pp.stringId = new string_id("cbt_spam", "bh_prescience_attacker"); pp.target.set(defender); pp.other.set(new string_id("cmd_n", actionData.actionName)); sendCombatSpamMessageProse(attacker, pp); pp.target.set(attacker); pp.stringId = new string_id("cbt_spam", "bh_prescience_defender"); sendCombatSpamMessageProse(defender, pp); return true; } return false; }