From b6c84fa278500e5d3b6197233d939e5f70bab98f Mon Sep 17 00:00:00 2001 From: Maruno17 Date: Thu, 25 Aug 2022 19:16:21 +0100 Subject: [PATCH] Added AI handlers that calculate a move's base power, moved AI damage calculation --- .../011_Battle/005_AI/001_Battle_AI.rb | 4 +- Data/Scripts/011_Battle/005_AI/004_AI_Move.rb | 12 +- .../005_AI/008_AI_Move_Utilities.rb | 287 ------------- .../052_AI_MoveHandlers_BattlerStats.rb | 12 + .../053_AI_MoveHandlers_BattlerOther.rb | 5 + .../054_AI_MoveHandlers_MoveAttributes.rb | 207 ++++++++-- .../005_AI/055_AI_MoveHandlers_MultiHit.rb | 77 +++- .../005_AI/056_AI_MoveHandlers_Healing.rb | 11 +- .../005_AI/057_AI_MoveHandlers_Items.rb | 10 + .../058_AI_MoveHandlers_ChangeMoveEffect.rb | 31 +- .../059_AI_MoveHandlers_SwitchingActing.rb | 5 + Data/Scripts/011_Battle/005_AI/103_AIMove.rb | 377 +++++++++++++----- 12 files changed, 581 insertions(+), 457 deletions(-) diff --git a/Data/Scripts/011_Battle/005_AI/001_Battle_AI.rb b/Data/Scripts/011_Battle/005_AI/001_Battle_AI.rb index 04f9037d3..6794c36b2 100644 --- a/Data/Scripts/011_Battle/005_AI/001_Battle_AI.rb +++ b/Data/Scripts/011_Battle/005_AI/001_Battle_AI.rb @@ -73,14 +73,12 @@ module Battle::AI::Handlers # Move failure check def self.apply_move_effect_score(function_code, score, *args) - function_code = function_code.to_sym ret = MoveEffectScore.trigger(function_code, score, *args) return (ret.nil?) ? score : ret end def self.get_base_power(function_code, power, *args) - function_code = function_code.to_sym - ret = MoveBasePower.trigger(function_code, *args) + ret = MoveBasePower.trigger(function_code, power, *args) return (ret.nil?) ? power : ret end end diff --git a/Data/Scripts/011_Battle/005_AI/004_AI_Move.rb b/Data/Scripts/011_Battle/005_AI/004_AI_Move.rb index 3d223b382..1bc02b779 100644 --- a/Data/Scripts/011_Battle/005_AI/004_AI_Move.rb +++ b/Data/Scripts/011_Battle/005_AI/004_AI_Move.rb @@ -29,7 +29,7 @@ class Battle::AI choices.each_with_index do |c, i| logMsg += "#{battler.moves[c[0]].name}=#{c[1]}" logMsg += " (target #{c[2]})" if c[2] >= 0 - logMsg += ", " if i < choices.length-1 + logMsg += ", " if i < choices.length - 1 end PBDebug.log(logMsg) end @@ -117,8 +117,8 @@ class Battle::AI score = pbGetStatusMoveBaseScore end # Modify the score according to the move's effect - score = Battle::AI::Handlers.apply_move_effect_score(@move.move.function, - score, @move.move, user_battler, target_battler, self, @battle) + score = Battle::AI::Handlers.apply_move_effect_score(@move.function, + score, @move, @user, @target, self, @battle) # A score of 0 here means it absolutely should not be used return 0 if score <= 0 @@ -327,8 +327,7 @@ class Battle::AI target_battler = @target.battler # Calculate how much damage the move will do (roughly) - base_damage = @move.base_power - calc_damage = pbRoughDamage(@move, @target, base_damage) + calc_damage = @move.rough_damage # TODO: Maybe move this check elsewhere? Note that Reborn's base score does # not include this halving, but the predicted damage does. @@ -362,6 +361,9 @@ class Battle::AI return damage_percentage.to_i end + #============================================================================= + # + #============================================================================= def pbGetStatusMoveBaseScore # TODO: Call @target.immune_to_move? here too, not just for damaging moves # (only if this status move will be affected). diff --git a/Data/Scripts/011_Battle/005_AI/008_AI_Move_Utilities.rb b/Data/Scripts/011_Battle/005_AI/008_AI_Move_Utilities.rb index a10eab79e..d824a5be2 100644 --- a/Data/Scripts/011_Battle/005_AI/008_AI_Move_Utilities.rb +++ b/Data/Scripts/011_Battle/005_AI/008_AI_Move_Utilities.rb @@ -36,293 +36,6 @@ class Battle::AI return mod1 * mod2 end - #============================================================================= - # Damage calculation - #============================================================================= - def pbRoughDamage(move, target, baseDmg) - # Fixed damage moves - return baseDmg if move.move.is_a?(Battle::Move::FixedDamageMove) - - user_battler = @user.battler - target_battler = target.battler - - # Get the move's type - type = move.rough_type - - ##### Calculate user's attack stat ##### - atk = @user.rough_stat(:ATTACK) - if move.move.function == "UseTargetAttackInsteadOfUserAttack" # Foul Play - atk = target.rough_stat(:ATTACK) - elsif move.move.function == "UseUserBaseDefenseInsteadOfUserBaseAttack" # Body Press - atk = @user.rough_stat(:DEFENSE) - elsif move.move.specialMove?(type) - if move.move.function == "UseTargetAttackInsteadOfUserAttack" # Foul Play - atk = target.rough_stat(:SPECIAL_ATTACK) - else - atk = @user.rough_stat(:SPECIAL_ATTACK) - end - end - - ##### Calculate target's defense stat ##### - defense = target.rough_stat(:DEFENSE) - if move.move.specialMove?(type) && move.move.function != "UseTargetDefenseInsteadOfTargetSpDef" # Psyshock - defense = target.rough_stat(:SPECIAL_DEFENSE) - end - - ##### Calculate all multiplier effects ##### - multipliers = { - :base_damage_multiplier => 1.0, - :attack_multiplier => 1.0, - :defense_multiplier => 1.0, - :final_damage_multiplier => 1.0 - } - # Ability effects that alter damage - moldBreaker = @trainer.high_skill? && target_battler.hasMoldBreaker? - - if @user.ability_active? - # NOTE: These abilities aren't suitable for checking at the start of the - # round. - abilityBlacklist = [:ANALYTIC, :SNIPER, :TINTEDLENS, :AERILATE, :PIXILATE, :REFRIGERATE] - canCheck = true - abilityBlacklist.each do |m| - next if move.move.id != m - canCheck = false - break - end - if canCheck - Battle::AbilityEffects.triggerDamageCalcFromUser( - user_battler.ability, user_battler, target_battler, move.move, multipliers, baseDmg, type - ) - end - end - - if @trainer.medium_skill? && !moldBreaker - user_battler.allAllies.each do |b| - next if !b.abilityActive? - Battle::AbilityEffects.triggerDamageCalcFromAlly( - b.ability, user_battler, target_battler, move.move, multipliers, baseDmg, type - ) - end - end - - if !moldBreaker && target.ability_active? - # NOTE: These abilities aren't suitable for checking at the start of the - # round. - abilityBlacklist = [:FILTER, :SOLIDROCK] - canCheck = true - abilityBlacklist.each do |m| - next if move.move.id != m - canCheck = false - break - end - if canCheck - Battle::AbilityEffects.triggerDamageCalcFromTarget( - target_battler.ability, user_battler, target_battler, move.move, multipliers, baseDmg, type - ) - end - end - - if @trainer.high_skill? && !moldBreaker - target_battler.allAllies.each do |b| - next if !b.abilityActive? - Battle::AbilityEffects.triggerDamageCalcFromTargetAlly( - b.ability, user_battler, target_battler, move.move, multipliers, baseDmg, type - ) - end - end - - # Item effects that alter damage - # NOTE: Type-boosting gems aren't suitable for checking at the start of the - # round. - if @user.item_active? - # NOTE: These items aren't suitable for checking at the start of the - # round. - itemBlacklist = [:EXPERTBELT, :LIFEORB] - if !itemBlacklist.include?(user_battler.item_id) - Battle::ItemEffects.triggerDamageCalcFromUser( - user_battler.item, user_battler, target_battler, move.move, multipliers, baseDmg, type - ) - user_battler.effects[PBEffects::GemConsumed] = nil # Untrigger consuming of Gems - end - # TODO: Prefer (1.5x?) if item will be consumed and user has Unburden. - end - - if target.item_active? && target_battler.item && !target_battler.item.is_berry? - Battle::ItemEffects.triggerDamageCalcFromTarget( - target_battler.item, user_battler, target_battler, move.move, multipliers, baseDmg, type - ) - end - - # Global abilities - if @trainer.medium_skill? && - ((@battle.pbCheckGlobalAbility(:DARKAURA) && type == :DARK) || - (@battle.pbCheckGlobalAbility(:FAIRYAURA) && type == :FAIRY)) - if @battle.pbCheckGlobalAbility(:AURABREAK) - multipliers[:base_damage_multiplier] *= 2 / 3.0 - else - multipliers[:base_damage_multiplier] *= 4 / 3.0 - end - end - - # Parental Bond - if @user.has_active_ability?(:PARENTALBOND) - multipliers[:base_damage_multiplier] *= 1.25 - end - - # Me First - # TODO - - # Helping Hand - n/a - - # Charge - if @trainer.medium_skill? && - user_battler.effects[PBEffects::Charge] > 0 && type == :ELECTRIC - multipliers[:base_damage_multiplier] *= 2 - end - - # Mud Sport and Water Sport - if @trainer.medium_skill? - if type == :ELECTRIC - if @battle.allBattlers.any? { |b| b.effects[PBEffects::MudSport] } - multipliers[:base_damage_multiplier] /= 3 - end - if @battle.field.effects[PBEffects::MudSportField] > 0 - multipliers[:base_damage_multiplier] /= 3 - end - end - if type == :FIRE - if @battle.allBattlers.any? { |b| b.effects[PBEffects::WaterSport] } - multipliers[:base_damage_multiplier] /= 3 - end - if @battle.field.effects[PBEffects::WaterSportField] > 0 - multipliers[:base_damage_multiplier] /= 3 - end - end - end - - # Terrain moves - if @trainer.medium_skill? - case @battle.field.terrain - when :Electric - multipliers[:base_damage_multiplier] *= 1.5 if type == :ELECTRIC && user_battler.affectedByTerrain? - when :Grassy - multipliers[:base_damage_multiplier] *= 1.5 if type == :GRASS && user_battler.affectedByTerrain? - when :Psychic - multipliers[:base_damage_multiplier] *= 1.5 if type == :PSYCHIC && user_battler.affectedByTerrain? - when :Misty - multipliers[:base_damage_multiplier] /= 2 if type == :DRAGON && target_battler.affectedByTerrain? - end - end - - # Badge multipliers - if @trainer.high_skill? && @battle.internalBattle && target_battler.pbOwnedByPlayer? - # Don't need to check the Atk/Sp Atk-boosting badges because the AI - # won't control the player's Pokémon. - if move.move.physicalMove?(type) && @battle.pbPlayer.badge_count >= Settings::NUM_BADGES_BOOST_DEFENSE - multipliers[:defense_multiplier] *= 1.1 - elsif move.move.specialMove?(type) && @battle.pbPlayer.badge_count >= Settings::NUM_BADGES_BOOST_SPDEF - multipliers[:defense_multiplier] *= 1.1 - end - end - - # Multi-targeting attacks - if @trainer.high_skill? && move.targets_multiple_battlers? - multipliers[:final_damage_multiplier] *= 0.75 - end - - # Weather - if @trainer.medium_skill? - case user_battler.effectiveWeather - when :Sun, :HarshSun - case type - when :FIRE - multipliers[:final_damage_multiplier] *= 1.5 - when :WATER - multipliers[:final_damage_multiplier] /= 2 - end - when :Rain, :HeavyRain - case type - when :FIRE - multipliers[:final_damage_multiplier] /= 2 - when :WATER - multipliers[:final_damage_multiplier] *= 1.5 - end - when :Sandstorm - if target.has_type?(:ROCK) && move.move.specialMove?(type) && - move.move.function != "UseTargetDefenseInsteadOfTargetSpDef" # Psyshock - multipliers[:defense_multiplier] *= 1.5 - end - end - end - - # Critical hits - n/a - - # Random variance - n/a - - # STAB - if type && @user.has_type?(type) - if @user.has_active_ability?(:ADAPTABILITY) - multipliers[:final_damage_multiplier] *= 2 - else - multipliers[:final_damage_multiplier] *= 1.5 - end - end - - # Type effectiveness - typemod = target.effectiveness_of_type_against_battler(type, @user) - multipliers[:final_damage_multiplier] *= typemod.to_f / Effectiveness::NORMAL_EFFECTIVE - - # Burn - if @trainer.high_skill? && move.move.physicalMove?(type) && - user_battler.status == :BURN && !@user.has_active_ability?(:GUTS) && - !(Settings::MECHANICS_GENERATION >= 6 && - move.move.function == "DoublePowerIfUserPoisonedBurnedParalyzed") # Facade - multipliers[:final_damage_multiplier] /= 2 - end - - # Aurora Veil, Reflect, Light Screen - if @trainer.medium_skill? && !move.move.ignoresReflect? && !@user.has_active_ability?(:INFILTRATOR) - if target_battler.pbOwnSide.effects[PBEffects::AuroraVeil] > 0 - if @battle.pbSideBattlerCount(target_battler) > 1 - multipliers[:final_damage_multiplier] *= 2 / 3.0 - else - multipliers[:final_damage_multiplier] /= 2 - end - elsif target_battler.pbOwnSide.effects[PBEffects::Reflect] > 0 && move.move.physicalMove?(type) - if @battle.pbSideBattlerCount(target_battler) > 1 - multipliers[:final_damage_multiplier] *= 2 / 3.0 - else - multipliers[:final_damage_multiplier] /= 2 - end - elsif target_battler.pbOwnSide.effects[PBEffects::LightScreen] > 0 && move.move.specialMove?(type) - if @battle.pbSideBattlerCount(target_battler) > 1 - multipliers[:final_damage_multiplier] *= 2 / 3.0 - else - multipliers[:final_damage_multiplier] /= 2 - end - end - end - - # Minimize - if @trainer.medium_skill? && target_battler.effects[PBEffects::Minimize] && move.move.tramplesMinimize? - multipliers[:final_damage_multiplier] *= 2 - end - - # Move-specific base damage modifiers - # TODO - - # Move-specific final damage modifiers - # TODO - - ##### Main damage calculation ##### - baseDmg = [(baseDmg * multipliers[:base_damage_multiplier]).round, 1].max - atk = [(atk * multipliers[:attack_multiplier]).round, 1].max - defense = [(defense * multipliers[:defense_multiplier]).round, 1].max - damage = ((((2.0 * user_battler.level / 5) + 2).floor * baseDmg * atk / defense).floor / 50).floor + 2 - damage = [(damage * multipliers[:final_damage_multiplier]).round, 1].max - return damage.floor - end - #============================================================================= # Check if battler has a move that meets the criteria in the block provided #============================================================================= diff --git a/Data/Scripts/011_Battle/005_AI/052_AI_MoveHandlers_BattlerStats.rb b/Data/Scripts/011_Battle/005_AI/052_AI_MoveHandlers_BattlerStats.rb index 2e2fcf352..da66fdd61 100644 --- a/Data/Scripts/011_Battle/005_AI/052_AI_MoveHandlers_BattlerStats.rb +++ b/Data/Scripts/011_Battle/005_AI/052_AI_MoveHandlers_BattlerStats.rb @@ -920,6 +920,11 @@ Battle::AI::Handlers::MoveEffectScore.add("LowerTargetDefense1", } ) +Battle::AI::Handlers::MoveBasePower.add("LowerTargetDefense1PowersUpInGravity", + proc { |power, move, user, target, ai, battle| + next move.pbBaseDamage(power, user.battler, target.battler) + } +} Battle::AI::Handlers::MoveEffectScore.add("LowerTargetDefense1PowersUpInGravity", proc { |score, move, user, target, ai, battle| if move.statusMove? @@ -1089,6 +1094,12 @@ Battle::AI::Handlers::MoveEffectScore.add("LowerTargetSpeed1", Battle::AI::Handlers::MoveEffectScore.copy("LowerTargetSpeed1", "LowerTargetSpeed1WeakerInGrassyTerrain") +Battle::AI::Handlers::MoveBasePower.add("LowerTargetSpeed1WeakerInGrassyTerrain", + proc { |power, move, user, target, ai, battle| + next move.pbBaseDamage(power, user.battler, target.battler) + } +} + Battle::AI::Handlers::MoveEffectScore.add("LowerTargetSpeed1MakeTargetWeakerToFire", proc { |score, move, user, target, ai, battle| next 0 if !target.battler.pbCanLowerStatStage?(:SPEED, user.battler) && @@ -1394,6 +1405,7 @@ Battle::AI::Handlers::MoveEffectScore.add("UserCopyTargetStatStages", } ) +# TODO: Account for stat theft before damage calculation. Battle::AI::Handlers::MoveEffectScore.add("UserStealTargetPositiveStatStages", proc { |score, move, user, target, ai, battle| numStages = 0 diff --git a/Data/Scripts/011_Battle/005_AI/053_AI_MoveHandlers_BattlerOther.rb b/Data/Scripts/011_Battle/005_AI/053_AI_MoveHandlers_BattlerOther.rb index 1fe7f8b8e..318b7f8a8 100644 --- a/Data/Scripts/011_Battle/005_AI/053_AI_MoveHandlers_BattlerOther.rb +++ b/Data/Scripts/011_Battle/005_AI/053_AI_MoveHandlers_BattlerOther.rb @@ -297,6 +297,11 @@ Battle::AI::Handlers::MoveEffectScore.add("FlinchTargetFailsIfNotUserFirstTurn", } ) +Battle::AI::Handlers::MoveBasePower.add("FlinchTargetDoublePowerIfTargetInSky", + proc { |power, move, user, target, ai, battle| + next move.pbBaseDamage(power, user.battler, target.battler) + } +} Battle::AI::Handlers::MoveEffectScore.add("FlinchTargetDoublePowerIfTargetInSky", proc { |score, move, user, target, ai, battle| score += 30 if !target.has_active_ability?(:INNERFOCUS) && diff --git a/Data/Scripts/011_Battle/005_AI/054_AI_MoveHandlers_MoveAttributes.rb b/Data/Scripts/011_Battle/005_AI/054_AI_MoveHandlers_MoveAttributes.rb index bf24b7a08..e5ad53cc2 100644 --- a/Data/Scripts/011_Battle/005_AI/054_AI_MoveHandlers_MoveAttributes.rb +++ b/Data/Scripts/011_Battle/005_AI/054_AI_MoveHandlers_MoveAttributes.rb @@ -1,6 +1,11 @@ #=============================================================================== # #=============================================================================== +Battle::AI::Handlers::MoveBasePower.add("FixedDamage20", + proc { |power, move, user, target, ai, battle| + next move.pbFixedDamage(user.battler, target.battler) + } +} Battle::AI::Handlers::MoveEffectScore.add("FixedDamage20", proc { |score, move, user, target, ai, battle| if target.hp <= 20 @@ -12,12 +17,22 @@ Battle::AI::Handlers::MoveEffectScore.add("FixedDamage20", } ) +Battle::AI::Handlers::MoveBasePower.add("FixedDamage40", + proc { |power, move, user, target, ai, battle| + next move.pbFixedDamage(user.battler, target.battler) + } +} Battle::AI::Handlers::MoveEffectScore.add("FixedDamage40", proc { |score, move, user, target, ai, battle| next score + 80 if target.hp <= 40 } ) +Battle::AI::Handlers::MoveBasePower.add("FixedDamageHalfTargetHP", + proc { |power, move, user, target, ai, battle| + next move.pbFixedDamage(user.battler, target.battler) + } +} Battle::AI::Handlers::MoveEffectScore.add("FixedDamageHalfTargetHP", proc { |score, move, user, target, ai, battle| score -= 50 @@ -25,18 +40,33 @@ Battle::AI::Handlers::MoveEffectScore.add("FixedDamageHalfTargetHP", } ) +Battle::AI::Handlers::MoveBasePower.add("FixedDamageUserLevel", + proc { |power, move, user, target, ai, battle| + next move.pbFixedDamage(user.battler, target.battler) + } +} Battle::AI::Handlers::MoveEffectScore.add("FixedDamageUserLevel", proc { |score, move, user, target, ai, battle| next score + 80 if target.hp <= user.level } ) +Battle::AI::Handlers::MoveBasePower.add("FixedDamageUserLevelRandom", + proc { |power, move, user, target, ai, battle| + next user.level # Average power + } +} Battle::AI::Handlers::MoveEffectScore.add("FixedDamageUserLevelRandom", proc { |score, move, user, target, ai, battle| next score + 30 if target.hp <= user.level } ) +Battle::AI::Handlers::MoveBasePower.add("LowerTargetHPToUserHP", + proc { |power, move, user, target, ai, battle| + next move.pbFixedDamage(user.battler, target.battler) + } +} Battle::AI::Handlers::MoveEffectScore.add("LowerTargetHPToUserHP", proc { |score, move, user, target, ai, battle| if user.hp >= target.hp @@ -48,6 +78,11 @@ Battle::AI::Handlers::MoveEffectScore.add("LowerTargetHPToUserHP", } ) +Battle::AI::Handlers::MoveBasePower.add("OHKO", + proc { |power, move, user, target, ai, battle| + next 999 + } +} Battle::AI::Handlers::MoveEffectScore.add("OHKO", proc { |score, move, user, target, ai, battle| next 0 if target.has_active_ability?(:STURDY) @@ -55,6 +90,9 @@ Battle::AI::Handlers::MoveEffectScore.add("OHKO", } ) +Battle::AI::Handlers::MoveBasePower.copy("OHKO", + "OHKOIce", + "OHKOHitsUndergroundTarget") Battle::AI::Handlers::MoveEffectScore.copy("OHKO", "OHKOIce", "OHKOHitsUndergroundTarget") @@ -69,40 +107,70 @@ Battle::AI::Handlers::MoveEffectScore.add("DamageTargetAlly", } ) -# PowerHigherWithUserHP +Battle::AI::Handlers::MoveBasePower.add("PowerHigherWithUserHP", + proc { |power, move, user, target, ai, battle| + next move.pbBaseDamage(power, user.battler, target.battler) + } +} -# PowerLowerWithUserHP +Battle::AI::Handlers::MoveBasePower.copy("PowerHigherWithUserHP", + "PowerLowerWithUserHP", + "PowerHigherWithTargetHP", + "PowerHigherWithUserHappiness", + "PowerLowerWithUserHappiness", + "PowerHigherWithUserPositiveStatStages", + "PowerHigherWithTargetPositiveStatStages", + "PowerHigherWithUserFasterThanTarget", + "PowerHigherWithTargetFasterThanUser") -# PowerHigherWithTargetHP +Battle::AI::Handlers::MoveBasePower.add("PowerHigherWithLessPP", + proc { |power, move, user, target, ai, battle| + next 0 if move.move.pp == 0 && move.move.totalpp > 0 + dmgs = [200, 80, 60, 50, 40] + ppLeft = [move.pp - 1, dmgs.length - 1].min + next dmgs[ppLeft] + } +} -# PowerHigherWithUserHappiness +Battle::AI::Handlers::MoveBasePower.add("PowerHigherWithTargetWeight", + proc { |power, move, user, target, ai, battle| + next move.pbBaseDamage(power, user.battler, target.battler) + } +} -# PowerLowerWithUserHappiness +Battle::AI::Handlers::MoveBasePower.copy("PowerHigherWithTargetWeight", + "PowerHigherWithUserHeavierThanTarget") -# PowerHigherWithUserPositiveStatStages +Battle::AI::Handlers::MoveBasePower.add("PowerHigherWithConsecutiveUse", + proc { |power, move, user, target, ai, battle| + next power << user.effects[PBEffects::FuryCutter] + } +} -# PowerHigherWithTargetPositiveStatStages +Battle::AI::Handlers::MoveBasePower.add("PowerHigherWithConsecutiveUseOnUserSide", + proc { |power, move, user, target, ai, battle| + next power * (user.pbOwnSide.effects[PBEffects::EchoedVoiceCounter] + 1) + } +} -# PowerHigherWithUserFasterThanTarget +Battle::AI::Handlers::MoveBasePower.add("RandomPowerDoublePowerIfTargetUnderground", + proc { |power, move, user, target, ai, battle| + power = 71 # Average damage + next move.pbModifyDamage(power, user.battler, target.battler) + } +} -# PowerHigherWithTargetFasterThanUser +Battle::AI::Handlers::MoveBasePower.add("DoublePowerIfTargetHPLessThanHalf", + proc { |power, move, user, target, ai, battle| + next move.pbBaseDamage(power, user.battler, target.battler) + } +} -# PowerHigherWithLessPP - -# PowerHigherWithTargetWeight - -# PowerHigherWithUserHeavierThanTarget - -# PowerHigherWithConsecutiveUse - -# PowerHigherWithConsecutiveUseOnUserSide - -# RandomPowerDoublePowerIfTargetUnderground - -# DoublePowerIfTargetHPLessThanHalf - -# DoublePowerIfUserPoisonedBurnedParalyzed +Battle::AI::Handlers::MoveBasePower.copy("DoublePowerIfTargetHPLessThanHalf", + "DoublePowerIfUserPoisonedBurnedParalyzed") +Battle::AI::Handlers::MoveBasePower.copy("DoublePowerIfTargetHPLessThanHalf", + "DoublePowerIfTargetAsleepCureTarget") Battle::AI::Handlers::MoveEffectScore.add("DoublePowerIfTargetAsleepCureTarget", proc { |score, move, user, target, ai, battle| next score - 20 if target.status == :SLEEP && # Will cure status @@ -110,33 +178,51 @@ Battle::AI::Handlers::MoveEffectScore.add("DoublePowerIfTargetAsleepCureTarget", } ) -# DoublePowerIfTargetPoisoned +Battle::AI::Handlers::MoveBasePower.add("DoublePowerIfTargetPoisoned", + proc { |power, move, user, target, ai, battle| + next move.pbBaseDamage(power, user.battler, target.battler) + } +} +Battle::AI::Handlers::MoveBasePower.copy("DoublePowerIfTargetPoisoned", + "DoublePowerIfTargetParalyzedCureTarget") Battle::AI::Handlers::MoveEffectScore.add("DoublePowerIfTargetParalyzedCureTarget", proc { |score, move, user, target, ai, battle| next score - 20 if target.status == :PARALYSIS # Will cure status } ) -# DoublePowerIfTargetStatusProblem - -# DoublePowerIfUserHasNoItem - -# DoublePowerIfTargetUnderwater - -# DoublePowerIfTargetUnderground - -# DoublePowerIfTargetInSky - -Battle::AI::Handlers::MoveEffectScore.add("DoublePowerInElectricTerrain", - proc { |score, move, user, target, ai, battle| - next score + 40 if battle.field.terrain == :Electric && target.battler.affectedByTerrain? +Battle::AI::Handlers::MoveBasePower.add("DoublePowerIfTargetStatusProblem", + proc { |power, move, user, target, ai, battle| + next move.pbBaseDamage(power, user.battler, target.battler) } -) +} -# DoublePowerIfUserLastMoveFailed +Battle::AI::Handlers::MoveBasePower.add("DoublePowerIfUserHasNoItem", + proc { |power, move, user, target, ai, battle| + next power * 2 if !user.item || user.has_active_item?(:FLYINGGEM) + } +} -# DoublePowerIfAllyFaintedLastTurn +Battle::AI::Handlers::MoveBasePower.add("DoublePowerIfTargetUnderwater", + proc { |power, move, user, target, ai, battle| + next move.pbModifyDamage(power, user.battler, target.battler) + } +} + +Battle::AI::Handlers::MoveBasePower.copy("DoublePowerIfTargetUnderwater", + "DoublePowerIfTargetUnderground") + +Battle::AI::Handlers::MoveBasePower.add("DoublePowerIfTargetInSky", + proc { |power, move, user, target, ai, battle| + next move.pbBaseDamage(power, user.battler, target.battler) + } +} + +Battle::AI::Handlers::MoveBasePower.copy("DoublePowerIfTargetInSky", + "DoublePowerInElectricTerrain", + "DoublePowerIfUserLastMoveFailed", + "DoublePowerIfAllyFaintedLastTurn") Battle::AI::Handlers::MoveEffectScore.add("DoublePowerIfUserLostHPThisTurn", proc { |score, move, user, target, ai, battle| @@ -393,7 +479,23 @@ Battle::AI::Handlers::MoveEffectScore.add("RecoilHalfOfDamageDealt", } ) -# EffectivenessIncludesFlyingType +Battle::AI::Handlers::MoveBasePower.add("EffectivenessIncludesFlyingType", + proc { |power, move, user, target, ai, battle| + if GameData::Type.exists?(:FLYING) + if ai.trainer.high_skill? + targetTypes = target.battler.pbTypes(true) + mult = Effectiveness.calculate( + :FLYING, targetTypes[0], targetTypes[1], targetTypes[2] + ) + else + mult = Effectiveness.calculate( + :FLYING, target.types[0], target.types[1], target.effects[PBEffects::Type3] + ) + end + next (power.to_f * mult / Effectiveness::NORMAL_EFFECTIVE).round + end + } +} Battle::AI::Handlers::MoveEffectScore.add("CategoryDependsOnHigherDamagePoisonTarget", proc { |score, move, user, target, ai, battle| @@ -446,8 +548,19 @@ Battle::AI::Handlers::MoveEffectScore.add("StartNegateTargetEvasionStatStageAndD # TypeIsUserFirstType -# TypeDependsOnUserIVs +Battle::AI::Handlers::MoveBasePower.add("TypeDependsOnUserIVs", + proc { |power, move, user, target, ai, battle| + next move.pbBaseDamage(power, user.battler, target.battler) + } +} +Battle::AI::Handlers::MoveBasePower.add("TypeAndPowerDependOnUserBerry", + proc { |power, move, user, target, ai, battle| + # TODO: Can't this just call move.pbBaseDamage? + ret = move.pbNaturalGiftBaseDamage(user.item_id) + next (ret == 1) ? 0 : ret + } +} Battle::AI::Handlers::MoveEffectScore.add("TypeAndPowerDependOnUserBerry", proc { |score, move, user, target, ai, battle| next 0 if !user.item || !user.item.is_berry? || !user.item_active? @@ -466,8 +579,14 @@ Battle::AI::Handlers::MoveEffectScore.add("TypeDependsOnUserMorpekoFormRaiseUser } ) -# TypeAndPowerDependOnWeather +Battle::AI::Handlers::MoveBasePower.add("TypeAndPowerDependOnWeather", + proc { |power, move, user, target, ai, battle| + next move.pbBaseDamage(power, user.battler, target.battler) + } +} +Battle::AI::Handlers::MoveBasePower.copy("TypeAndPowerDependOnWeather", + "TypeAndPowerDependOnTerrain") Battle::AI::Handlers::MoveEffectScore.add("TypeAndPowerDependOnTerrain", proc { |score, move, user, target, ai, battle| next score + 40 if battle.field.terrain != :None diff --git a/Data/Scripts/011_Battle/005_AI/055_AI_MoveHandlers_MultiHit.rb b/Data/Scripts/011_Battle/005_AI/055_AI_MoveHandlers_MultiHit.rb index 1b57f276e..45c590d63 100644 --- a/Data/Scripts/011_Battle/005_AI/055_AI_MoveHandlers_MultiHit.rb +++ b/Data/Scripts/011_Battle/005_AI/055_AI_MoveHandlers_MultiHit.rb @@ -2,8 +2,14 @@ # #=============================================================================== -# HitTwoTimes +Battle::AI::Handlers::MoveBasePower.add("HitTwoTimes", + proc { |power, move, user, target, ai, battle| + next power * move.pbNumHits(user.battler, [target.battler]) + } +} +Battle::AI::Handlers::MoveBasePower.copy("HitTwoTimes", + "HitTwoTimesPoisonTarget") Battle::AI::Handlers::MoveEffectScore.add("HitTwoTimesPoisonTarget", proc { |score, move, user, target, ai, battle| next 0 if !target.battler.pbCanPoison?(user.battler, false) @@ -22,16 +28,31 @@ Battle::AI::Handlers::MoveEffectScore.add("HitTwoTimesPoisonTarget", } ) +Battle::AI::Handlers::MoveBasePower.copy("HitTwoTimes", + "HitTwoTimesFlinchTarget") Battle::AI::Handlers::MoveEffectScore.add("HitTwoTimesFlinchTarget", proc { |score, move, user, target, ai, battle| next score + 30 if target.effects[PBEffects::Minimize] } ) -# HitTwoTimesTargetThenTargetAlly +Battle::AI::Handlers::MoveBasePower.add("HitTwoTimesTargetThenTargetAlly", + proc { |power, move, user, target, ai, battle| + next power * 2 + } +} -# HitThreeTimesPowersUpWithEachHit +Battle::AI::Handlers::MoveBasePower.add("HitThreeTimesPowersUpWithEachHit", + proc { |power, move, user, target, ai, battle| + next power * 6 # Hits do x1, x2, x3 ret in turn, for x6 in total + } +} +Battle::AI::Handlers::MoveBasePower.add("HitThreeTimesAlwaysCriticalHit", + proc { |power, move, user, target, ai, battle| + next power * move.pbNumHits(user.battler, [target.battler]) + } +} Battle::AI::Handlers::MoveEffectScore.add("HitThreeTimesAlwaysCriticalHit", proc { |score, move, user, target, ai, battle| if ai.trainer.high_skill? @@ -41,10 +62,29 @@ Battle::AI::Handlers::MoveEffectScore.add("HitThreeTimesAlwaysCriticalHit", } ) -# HitTwoToFiveTimes +Battle::AI::Handlers::MoveBasePower.add("HitTwoToFiveTimes", + proc { |power, move, user, target, ai, battle| + next power * 5 if user.has_active_ability?(:SKILLLINK) + next power * 31 / 10 # Average damage dealt + } +} -# HitTwoToFiveTimesOrThreeForAshGreninja +Battle::AI::Handlers::MoveBasePower.add("HitTwoToFiveTimesOrThreeForAshGreninja", + proc { |power, move, user, target, ai, battle| + if user.battler.isSpecies?(:GRENINJA) && user.battler.form == 2 + next move.pbBaseDamage(power, user.battler, target.battler) * move.pbNumHits(user.battler, [target.battler]) + end + next power * 5 if user.has_active_ability?(:SKILLLINK) + next power * 31 / 10 # Average damage dealt + } +} +Battle::AI::Handlers::MoveBasePower.add("HitTwoToFiveTimesRaiseUserSpd1LowerUserDef1", + proc { |power, move, user, target, ai, battle| + next power * 5 if user.has_active_ability?(:SKILLLINK) + next power * 31 / 10 # Average damage dealt + } +} Battle::AI::Handlers::MoveEffectScore.add("HitTwoToFiveTimesRaiseUserSpd1LowerUserDef1", proc { |score, move, user, target, ai, battle| aspeed = user.rough_stat(:SPEED) @@ -59,13 +99,25 @@ Battle::AI::Handlers::MoveEffectScore.add("HitTwoToFiveTimesRaiseUserSpd1LowerUs } ) -# HitOncePerUserTeamMember +Battle::AI::Handlers::MoveBasePower.add("HitTwoToFiveTimesRaiseUserSpd1LowerUserDef1", + proc { |power, move, user, target, ai, battle| + ret = 0 + ai.battle.eachInTeamFromBattlerIndex(user.index) do |pkmn, _i| + ret += 5 + (pkmn.baseStats[:ATTACK] / 10) + end + next ret + } +} # AttackAndSkipNextTurn # TwoTurnAttack -# TwoTurnAttackOneTurnInSun +Battle::AI::Handlers::MoveBasePower.add("TwoTurnAttackOneTurnInSun", + proc { |power, move, user, target, ai, battle| + next move.pbBaseDamageMultiplier(power, user.battler, target.battler) + } +} Battle::AI::Handlers::MoveEffectScore.add("TwoTurnAttackParalyzeTarget", proc { |score, move, user, target, ai, battle| @@ -184,8 +236,17 @@ Battle::AI::Handlers::MoveEffectScore.add("TwoTurnAttackChargeRaiseUserSpAtk1", # MultiTurnAttackConfuseUserAtEnd -# MultiTurnAttackPowersUpEachTurn +Battle::AI::Handlers::MoveBasePower.add("MultiTurnAttackPowersUpEachTurn", + proc { |power, move, user, target, ai, battle| + next power * 2 if user.effects[PBEffects::DefenseCurl] + } +} +Battle::AI::Handlers::MoveBasePower.add("MultiTurnAttackBideThenReturnDoubleDamage", + proc { |power, move, user, target, ai, battle| + next 40 # Representative value + } +} Battle::AI::Handlers::MoveEffectScore.add("MultiTurnAttackBideThenReturnDoubleDamage", proc { |score, move, user, target, ai, battle| if user.hp <= user.totalhp / 4 diff --git a/Data/Scripts/011_Battle/005_AI/056_AI_MoveHandlers_Healing.rb b/Data/Scripts/011_Battle/005_AI/056_AI_MoveHandlers_Healing.rb index 381014f26..61f339514 100644 --- a/Data/Scripts/011_Battle/005_AI/056_AI_MoveHandlers_Healing.rb +++ b/Data/Scripts/011_Battle/005_AI/056_AI_MoveHandlers_Healing.rb @@ -273,6 +273,11 @@ Battle::AI::Handlers::MoveEffectScore.add("UserFaintsExplosive", } ) +Battle::AI::Handlers::MoveBasePower.add("UserFaintsPowersUpInMistyTerrainExplosive", + proc { |power, move, user, target, ai, battle| + next power * 3 / 2 if battle.field.terrain == :Misty + } +} Battle::AI::Handlers::MoveEffectScore.add("UserFaintsPowersUpInMistyTerrainExplosive", proc { |score, move, user, target, ai, battle| reserves = battle.pbAbleNonActiveCount(user.idxOwnSide) @@ -292,7 +297,11 @@ Battle::AI::Handlers::MoveEffectScore.add("UserFaintsPowersUpInMistyTerrainExplo } ) -# UserFaintsFixedDamageUserHP +Battle::AI::Handlers::MoveBasePower.add("UserFaintsFixedDamageUserHP", + proc { |power, move, user, target, ai, battle| + next user.hp + } +} Battle::AI::Handlers::MoveEffectScore.add("UserFaintsLowerTargetAtkSpAtk2", proc { |score, move, user, target, ai, battle| diff --git a/Data/Scripts/011_Battle/005_AI/057_AI_MoveHandlers_Items.rb b/Data/Scripts/011_Battle/005_AI/057_AI_MoveHandlers_Items.rb index 7add4b991..ed2955c0d 100644 --- a/Data/Scripts/011_Battle/005_AI/057_AI_MoveHandlers_Items.rb +++ b/Data/Scripts/011_Battle/005_AI/057_AI_MoveHandlers_Items.rb @@ -58,6 +58,11 @@ Battle::AI::Handlers::MoveEffectScore.add("RestoreUserConsumedItem", } ) +Battle::AI::Handlers::MoveBasePower.add("RemoveTargetItem", + proc { |power, move, user, target, ai, battle| + next move.pbBaseDamage(power, user.battler, target.battler) + } +} Battle::AI::Handlers::MoveEffectScore.add("RemoveTargetItem", proc { |score, move, user, target, ai, battle| if ai.trainer.high_skill? @@ -186,6 +191,11 @@ Battle::AI::Handlers::MoveEffectScore.add("UserConsumeTargetBerry", } ) +Battle::AI::Handlers::MoveBasePower.add("ThrowUserItemAtTarget", + proc { |power, move, user, target, ai, battle| + next move.pbBaseDamage(power, user.battler, target.battler) + } +} Battle::AI::Handlers::MoveEffectScore.add("ThrowUserItemAtTarget", proc { |score, move, user, target, ai, battle| next 0 if !user.item || !user.item_active? || diff --git a/Data/Scripts/011_Battle/005_AI/058_AI_MoveHandlers_ChangeMoveEffect.rb b/Data/Scripts/011_Battle/005_AI/058_AI_MoveHandlers_ChangeMoveEffect.rb index bd9d16487..a71494434 100644 --- a/Data/Scripts/011_Battle/005_AI/058_AI_MoveHandlers_ChangeMoveEffect.rb +++ b/Data/Scripts/011_Battle/005_AI/058_AI_MoveHandlers_ChangeMoveEffect.rb @@ -32,7 +32,11 @@ Battle::AI::Handlers::MoveEffectScore.add("CannotBeRedirected", } ) -# RandomlyDamageOrHealTarget +Battle::AI::Handlers::MoveBasePower.add("RandomlyDamageOrHealTarget", + proc { |power, move, user, target, ai, battle| + next 50 # Average power, ish + } +} Battle::AI::Handlers::MoveEffectScore.add("HealAllyOrDamageFoe", proc { |score, move, user, target, ai, battle| @@ -73,6 +77,11 @@ Battle::AI::Handlers::MoveEffectScore.add("CurseTargetOrLowerUserSpd1RaiseUserAt # EffectDependsOnEnvironment +Battle::AI::Handlers::MoveBasePower.add("HitsAllFoesAndPowersUpInPsychicTerrain", + proc { |power, move, user, target, ai, battle| + next move.pbBaseDamage(power, user.battler, target.battler) + } +} Battle::AI::Handlers::MoveEffectScore.add("HitsAllFoesAndPowersUpInPsychicTerrain", proc { |score, move, user, target, ai, battle| next score + 40 if battle.field.terrain == :Psychic && user.battler.affectedByTerrain? @@ -103,6 +112,11 @@ Battle::AI::Handlers::MoveEffectScore.add("PowerUpAllyMove", } ) +Battle::AI::Handlers::MoveBasePower.add("CounterPhysicalDamage", + proc { |power, move, user, target, ai, battle| + next 60 # Representative value + } +} Battle::AI::Handlers::MoveEffectScore.add("CounterPhysicalDamage", proc { |score, move, user, target, ai, battle| if target.effects[PBEffects::HyperBeam] > 0 @@ -121,6 +135,11 @@ Battle::AI::Handlers::MoveEffectScore.add("CounterPhysicalDamage", } ) +Battle::AI::Handlers::MoveBasePower.add("CounterSpecialDamage", + proc { |power, move, user, target, ai, battle| + next 60 # Representative value + } +} Battle::AI::Handlers::MoveEffectScore.add("CounterSpecialDamage", proc { |score, move, user, target, ai, battle| if target.effects[PBEffects::HyperBeam] > 0 @@ -139,6 +158,11 @@ Battle::AI::Handlers::MoveEffectScore.add("CounterSpecialDamage", } ) +Battle::AI::Handlers::MoveBasePower.add("CounterDamagePlusHalf", + proc { |power, move, user, target, ai, battle| + next 60 # Representative value + } +} Battle::AI::Handlers::MoveEffectScore.add("CounterDamagePlusHalf", proc { |score, move, user, target, ai, battle| next score - 90 if target.effects[PBEffects::HyperBeam] > 0 @@ -161,6 +185,11 @@ Battle::AI::Handlers::MoveEffectScore.add("UserAddStockpileRaiseDefSpDef1", } ) +Battle::AI::Handlers::MoveBasePower.add("PowerDependsOnUserStockpile", + proc { |power, move, user, target, ai, battle| + next move.pbBaseDamage(power, user.battler, target.battler) + } +} Battle::AI::Handlers::MoveEffectScore.add("PowerDependsOnUserStockpile", proc { |score, move, user, target, ai, battle| next 0 if user.effects[PBEffects::Stockpile] == 0 diff --git a/Data/Scripts/011_Battle/005_AI/059_AI_MoveHandlers_SwitchingActing.rb b/Data/Scripts/011_Battle/005_AI/059_AI_MoveHandlers_SwitchingActing.rb index c65cf26a2..f86968a72 100644 --- a/Data/Scripts/011_Battle/005_AI/059_AI_MoveHandlers_SwitchingActing.rb +++ b/Data/Scripts/011_Battle/005_AI/059_AI_MoveHandlers_SwitchingActing.rb @@ -115,6 +115,11 @@ Battle::AI::Handlers::MoveEffectScore.add("BindTarget", } ) +Battle::AI::Handlers::MoveBasePower.add("BindTargetDoublePowerIfTargetUnderwater", + proc { |power, move, user, target, ai, battle| + next move.pbModifyDamage(power, user.battler, target.battler) + } +} Battle::AI::Handlers::MoveEffectScore.add("BindTargetDoublePowerIfTargetUnderwater", proc { |score, move, user, target, ai, battle| next score + 40 if target.effects[PBEffects::Trapping] == 0 diff --git a/Data/Scripts/011_Battle/005_AI/103_AIMove.rb b/Data/Scripts/011_Battle/005_AI/103_AIMove.rb index 736be5247..2c07de438 100644 --- a/Data/Scripts/011_Battle/005_AI/103_AIMove.rb +++ b/Data/Scripts/011_Battle/005_AI/103_AIMove.rb @@ -16,6 +16,7 @@ class Battle::AI::AIMove #============================================================================= # pp + # totalpp # priority # usableWhenAsleep? # thawsUser? @@ -81,121 +82,281 @@ class Battle::AI::AIMove ret = @move.baseDamage ret = 60 if ret == 1 return ret if !@ai.trainer.medium_skill? + return Battle::AI::Handlers.get_base_power(function, + ret, self, @ai.user, @ai.target, @ai, @ai.battle) + end + + def rough_damage + power = base_power + return power if @move.is_a?(Battle::Move::FixedDamageMove) + # Get the user and target of this move user = @ai.user user_battler = user.battler target = @ai.target target_battler = target.battler - # Covers all function codes which have their own def pbBaseDamage - case @move.function - when "FixedDamage20", "FixedDamage40", "FixedDamageHalfTargetHP", - "FixedDamageUserLevel", "LowerTargetHPToUserHP" - ret = @move.pbFixedDamage(user_battler, target_battler) - when "FixedDamageUserLevelRandom" - ret = user_battler.level - when "OHKO", "OHKOIce", "OHKOHitsUndergroundTarget" - ret = 200 - when "CounterPhysicalDamage", "CounterSpecialDamage", "CounterDamagePlusHalf" - ret = 60 - when "DoublePowerIfTargetUnderwater", "DoublePowerIfTargetUnderground", - "BindTargetDoublePowerIfTargetUnderwater" - ret = @move.pbModifyDamage(ret, user_battler, target_battler) - when "DoublePowerIfTargetInSky", - "FlinchTargetDoublePowerIfTargetInSky", - "DoublePowerIfTargetPoisoned", - "DoublePowerIfTargetParalyzedCureTarget", - "DoublePowerIfTargetAsleepCureTarget", - "DoublePowerIfUserPoisonedBurnedParalyzed", - "DoublePowerIfTargetStatusProblem", - "DoublePowerIfTargetHPLessThanHalf", - "DoublePowerIfAllyFaintedLastTurn", - "TypeAndPowerDependOnWeather", - "PowerHigherWithUserHappiness", - "PowerLowerWithUserHappiness", - "PowerHigherWithUserHP", - "PowerHigherWithTargetHP", - "PowerHigherWithUserPositiveStatStages", - "PowerHigherWithTargetPositiveStatStages", - "TypeDependsOnUserIVs", - "PowerHigherWithConsecutiveUse", - "PowerHigherWithConsecutiveUseOnUserSide", - "PowerHigherWithLessPP", - "PowerLowerWithUserHP", - "PowerHigherWithUserFasterThanTarget", - "PowerHigherWithTargetWeight", - "ThrowUserItemAtTarget", - "PowerDependsOnUserStockpile" - ret = @move.pbBaseDamage(ret, user_battler, target_battler) - when "DoublePowerIfUserHasNoItem" - ret *= 2 if !user_battler.item || user.has_active_item?(:FLYINGGEM) - when "PowerHigherWithTargetFasterThanUser" - targetSpeed = target.rough_stat(:SPEED) - userSpeed = user.rough_stat(:SPEED) - ret = [[(25 * targetSpeed / userSpeed).floor, 150].min, 1].max - when "RandomlyDamageOrHealTarget" - ret = 50 - when "RandomPowerDoublePowerIfTargetUnderground" - ret = 71 - ret *= 2 if target_battler.inTwoTurnAttack?("TwoTurnAttackInvulnerableUnderground") # Dig - when "TypeAndPowerDependOnUserBerry" - ret = @move.pbNaturalGiftBaseDamage(user_battler.item_id) - when "PowerHigherWithUserHeavierThanTarget" - ret = @move.pbBaseDamage(ret, user_battler, target_battler) - ret *= 2 if Settings::MECHANICS_GENERATION >= 7 && @trainer.medium_skill? && - target_battler.effects[PBEffects::Minimize] - when "AlwaysCriticalHit", "HitTwoTimes", "HitTwoTimesPoisonTarget" - ret *= 2 - when "HitThreeTimesPowersUpWithEachHit" - ret *= 6 # Hits do x1, x2, x3 ret in turn, for x6 in total - when "HitTwoToFiveTimes" - if user.has_active_ability?(:SKILLLINK) - ret *= 5 + + # Get the move's type + calc_type = rough_type + + ##### Calculate user's attack stat ##### + atk = user.rough_stat(:ATTACK) + if function == "UseTargetAttackInsteadOfUserAttack" # Foul Play + atk = target.rough_stat(:ATTACK) + elsif function == "UseUserBaseDefenseInsteadOfUserBaseAttack" # Body Press + atk = user.rough_stat(:DEFENSE) + elsif specialMove?(calc_type) + if function == "UseTargetAttackInsteadOfUserAttack" # Foul Play + atk = target.rough_stat(:SPECIAL_ATTACK) else - ret = (ret * 31 / 10).floor # Average damage dealt + atk = user.rough_stat(:SPECIAL_ATTACK) end - when "HitTwoToFiveTimesOrThreeForAshGreninja" - if user_battler.isSpecies?(:GRENINJA) && user_battler.form == 2 - ret *= 4 # 3 hits at 20 power = 4 hits at 15 power - elsif user.has_active_ability?(:SKILLLINK) - ret *= 5 - else - ret = (ret * 31 / 10).floor # Average damage dealt - end - when "HitOncePerUserTeamMember" - mult = 0 - @ai.battle.eachInTeamFromBattlerIndex(user.index) do |pkmn, _i| - mult += 1 if pkmn&.able? && pkmn.status == :NONE - end - ret *= mult - when "TwoTurnAttackOneTurnInSun" - ret = @move.pbBaseDamageMultiplier(ret, user_battler, target_battler) - when "MultiTurnAttackPowersUpEachTurn" - ret *= 2 if user_battler.effects[PBEffects::DefenseCurl] - when "MultiTurnAttackBideThenReturnDoubleDamage" - ret = 40 - when "UserFaintsFixedDamageUserHP" - ret = user_battler.hp - when "EffectivenessIncludesFlyingType" - if GameData::Type.exists?(:FLYING) - if @trainer.high_skill? - targetTypes = target_battler.pbTypes(true) - mult = Effectiveness.calculate( - :FLYING, targetTypes[0], targetTypes[1], targetTypes[2] - ) - else - mult = Effectiveness.calculate( - :FLYING, target.types[0], target.types[1], target.effects[PBEffects::Type3] - ) - end - ret = (ret.to_f * mult / Effectiveness::NORMAL_EFFECTIVE).round - end - ret *= 2 if @trainer.medium_skill? && target_battler.effects[PBEffects::Minimize] - when "DoublePowerIfUserLastMoveFailed" - ret *= 2 if user_battler.lastRoundMoveFailed - when "HitTwoTimesFlinchTarget" - ret *= 2 - ret *= 2 if @trainer.medium_skill? && target_battler.effects[PBEffects::Minimize] end - return ret + + ##### Calculate target's defense stat ##### + defense = target.rough_stat(:DEFENSE) + if specialMove?(calc_type) && function != "UseTargetDefenseInsteadOfTargetSpDef" # Psyshock + defense = target.rough_stat(:SPECIAL_DEFENSE) + end + + ##### Calculate all multiplier effects ##### + multipliers = { + :base_damage_multiplier => 1.0, + :attack_multiplier => 1.0, + :defense_multiplier => 1.0, + :final_damage_multiplier => 1.0 + } + # Ability effects that alter damage + moldBreaker = @ai.trainer.high_skill? && target_battler.hasMoldBreaker? + + if user.ability_active? + # NOTE: These abilities aren't suitable for checking at the start of the + # round. + abilityBlacklist = [:ANALYTIC, :SNIPER, :TINTEDLENS, :AERILATE, :PIXILATE, :REFRIGERATE] + if !abilityBlacklist.include?(user.ability_id) + Battle::AbilityEffects.triggerDamageCalcFromUser( + user.ability, user_battler, target_battler, @move, multipliers, power, calc_type + ) + end + end + + if @ai.trainer.medium_skill? && !moldBreaker + user_battler.allAllies.each do |b| + next if !b.abilityActive? + Battle::AbilityEffects.triggerDamageCalcFromAlly( + b.ability, user_battler, target_battler, @move, multipliers, power, calc_type + ) + end + end + + if !moldBreaker && target.ability_active? + # NOTE: These abilities aren't suitable for checking at the start of the + # round. + abilityBlacklist = [:FILTER, :SOLIDROCK] + if !abilityBlacklist.include?(target.ability_id) + Battle::AbilityEffects.triggerDamageCalcFromTarget( + target.ability, user_battler, target_battler, @move, multipliers, power, calc_type + ) + end + end + + if @ai.trainer.high_skill? && !moldBreaker + target_battler.allAllies.each do |b| + next if !b.abilityActive? + Battle::AbilityEffects.triggerDamageCalcFromTargetAlly( + b.ability, user_battler, target_battler, @move, multipliers, power, calc_type + ) + end + end + + # Item effects that alter damage + # NOTE: Type-boosting gems aren't suitable for checking at the start of the + # round. + if user.item_active? + # NOTE: These items aren't suitable for checking at the start of the + # round. + itemBlacklist = [:EXPERTBELT, :LIFEORB] + if !itemBlacklist.include?(user.item_id) + Battle::ItemEffects.triggerDamageCalcFromUser( + user.item, user_battler, target_battler, @move, multipliers, power, calc_type + ) + user.effects[PBEffects::GemConsumed] = nil # Untrigger consuming of Gems + end + # TODO: Prefer (1.5x?) if item will be consumed and user has Unburden. + end + + if target.item_active? && target.item && !target.item.is_berry? + Battle::ItemEffects.triggerDamageCalcFromTarget( + target.item, user_battler, target_battler, @move, multipliers, power, calc_type + ) + end + + # Global abilities + if @ai.trainer.medium_skill? && + ((@ai.battle.pbCheckGlobalAbility(:DARKAURA) && calc_type == :DARK) || + (@ai.battle.pbCheckGlobalAbility(:FAIRYAURA) && calc_type == :FAIRY)) + if @ai.battle.pbCheckGlobalAbility(:AURABREAK) + multipliers[:base_damage_multiplier] *= 2 / 3.0 + else + multipliers[:base_damage_multiplier] *= 4 / 3.0 + end + end + + # Parental Bond + if user.has_active_ability?(:PARENTALBOND) + multipliers[:base_damage_multiplier] *= 1.25 + end + + # Me First + # TODO + + # Helping Hand - n/a + + # Charge + if @ai.trainer.medium_skill? && + user.effects[PBEffects::Charge] > 0 && calc_type == :ELECTRIC + multipliers[:base_damage_multiplier] *= 2 + end + + # Mud Sport and Water Sport + if @ai.trainer.medium_skill? + if calc_type == :ELECTRIC + if @ai.battle.allBattlers.any? { |b| b.effects[PBEffects::MudSport] } + multipliers[:base_damage_multiplier] /= 3 + end + if @ai.battle.field.effects[PBEffects::MudSportField] > 0 + multipliers[:base_damage_multiplier] /= 3 + end + elsif calc_type == :FIRE + if @ai.battle.allBattlers.any? { |b| b.effects[PBEffects::WaterSport] } + multipliers[:base_damage_multiplier] /= 3 + end + if @ai.battle.field.effects[PBEffects::WaterSportField] > 0 + multipliers[:base_damage_multiplier] /= 3 + end + end + end + + # Terrain moves + if @ai.trainer.medium_skill? + case @ai.battle.field.terrain + when :Electric + multipliers[:base_damage_multiplier] *= 1.5 if calc_type == :ELECTRIC && user_battler.affectedByTerrain? + when :Grassy + multipliers[:base_damage_multiplier] *= 1.5 if calc_type == :GRASS && user_battler.affectedByTerrain? + when :Psychic + multipliers[:base_damage_multiplier] *= 1.5 if calc_type == :PSYCHIC && user_battler.affectedByTerrain? + when :Misty + multipliers[:base_damage_multiplier] /= 2 if calc_type == :DRAGON && target_battler.affectedByTerrain? + end + end + + # Badge multipliers + if @ai.trainer.high_skill? && @ai.battle.internalBattle && target_battler.pbOwnedByPlayer? + # Don't need to check the Atk/Sp Atk-boosting badges because the AI + # won't control the player's Pokémon. + if physicalMove?(calc_type) && @ai.battle.pbPlayer.badge_count >= Settings::NUM_BADGES_BOOST_DEFENSE + multipliers[:defense_multiplier] *= 1.1 + elsif specialMove?(calc_type) && @ai.battle.pbPlayer.badge_count >= Settings::NUM_BADGES_BOOST_SPDEF + multipliers[:defense_multiplier] *= 1.1 + end + end + + # Multi-targeting attacks + if @ai.trainer.high_skill? && targets_multiple_battlers? + multipliers[:final_damage_multiplier] *= 0.75 + end + + # Weather + if @ai.trainer.medium_skill? + case user_battler.effectiveWeather + when :Sun, :HarshSun + case calc_type + when :FIRE + multipliers[:final_damage_multiplier] *= 1.5 + when :WATER + multipliers[:final_damage_multiplier] /= 2 + end + when :Rain, :HeavyRain + case calc_type + when :FIRE + multipliers[:final_damage_multiplier] /= 2 + when :WATER + multipliers[:final_damage_multiplier] *= 1.5 + end + when :Sandstorm + if target.has_type?(:ROCK) && specialMove?(calc_type) && + function != "UseTargetDefenseInsteadOfTargetSpDef" # Psyshock + multipliers[:defense_multiplier] *= 1.5 + end + end + end + + # Critical hits - n/a + + # Random variance - n/a + + # STAB + if calc_type && user.has_type?(calc_type) + if user.has_active_ability?(:ADAPTABILITY) + multipliers[:final_damage_multiplier] *= 2 + else + multipliers[:final_damage_multiplier] *= 1.5 + end + end + + # Type effectiveness + typemod = target.effectiveness_of_type_against_battler(calc_type, user) + multipliers[:final_damage_multiplier] *= typemod.to_f / Effectiveness::NORMAL_EFFECTIVE + + # Burn + if @ai.trainer.high_skill? && physicalMove?(calc_type) && + user.status == :BURN && !user.has_active_ability?(:GUTS) && + !(Settings::MECHANICS_GENERATION >= 6 && + function == "DoublePowerIfUserPoisonedBurnedParalyzed") # Facade + multipliers[:final_damage_multiplier] /= 2 + end + + # Aurora Veil, Reflect, Light Screen + if @ai.trainer.medium_skill? && !@move.ignoresReflect? && !user.has_active_ability?(:INFILTRATOR) + if target.pbOwnSide.effects[PBEffects::AuroraVeil] > 0 + if @ai.battle.pbSideBattlerCount(target_battler) > 1 + multipliers[:final_damage_multiplier] *= 2 / 3.0 + else + multipliers[:final_damage_multiplier] /= 2 + end + elsif target.pbOwnSide.effects[PBEffects::Reflect] > 0 && physicalMove?(calc_type) + if @ai.battle.pbSideBattlerCount(target_battler) > 1 + multipliers[:final_damage_multiplier] *= 2 / 3.0 + else + multipliers[:final_damage_multiplier] /= 2 + end + elsif target.pbOwnSide.effects[PBEffects::LightScreen] > 0 && specialMove?(calc_type) + if @ai.battle.pbSideBattlerCount(target_battler) > 1 + multipliers[:final_damage_multiplier] *= 2 / 3.0 + else + multipliers[:final_damage_multiplier] /= 2 + end + end + end + + # Minimize + if @ai.trainer.medium_skill? && target.effects[PBEffects::Minimize] && @move.tramplesMinimize? + multipliers[:final_damage_multiplier] *= 2 + end + + # Move-specific base damage modifiers + # TODO + + # Move-specific final damage modifiers + # TODO + + ##### Main damage calculation ##### + power = [(power * multipliers[:base_damage_multiplier]).round, 1].max + atk = [(atk * multipliers[:attack_multiplier]).round, 1].max + defense = [(defense * multipliers[:defense_multiplier]).round, 1].max + damage = ((((2.0 * user.level / 5) + 2).floor * power * atk / defense).floor / 50).floor + 2 + damage = [(damage * multipliers[:final_damage_multiplier]).round, 1].max + return damage.floor end #=============================================================================