diff --git a/Data/Scripts/011_Battle/002_Battler/006_Battler_AbilityAndItem.rb b/Data/Scripts/011_Battle/002_Battler/006_Battler_AbilityAndItem.rb index 11fb3feb0..50f1d83ea 100644 --- a/Data/Scripts/011_Battle/002_Battler/006_Battler_AbilityAndItem.rb +++ b/Data/Scripts/011_Battle/002_Battler/006_Battler_AbilityAndItem.rb @@ -387,7 +387,7 @@ class Battle::Battler #============================================================================= # Item effects #============================================================================= - def pbConfusionBerry(item_to_use, forced, flavor, confuse_msg) + def pbConfusionBerry(item_to_use, forced, confuse_stat, confuse_msg) return false if !forced && !canHeal? return false if !forced && !canConsumePinchBerry?(Settings::MECHANICS_GENERATION >= 7) used_item_name = GameData::Item.get(item_to_use).name @@ -415,12 +415,9 @@ class Battle::Battler @battle.pbDisplay(_INTL("{1} restored its health using its {2}!", pbThis, used_item_name)) end end - flavor_stat = [:ATTACK, :DEFENSE, :SPEED, :SPECIAL_ATTACK, :SPECIAL_DEFENSE][flavor] - self.nature.stat_changes.each do |change| - next if change[1] > 0 || change[0] != flavor_stat + if self.nature.stat_changes.any? { |val| val[0] == confuse_stat && val[1] < 0 } @battle.pbDisplay(confuse_msg) pbConfuse if pbCanConfuseSelf?(false) - break end return true end diff --git a/Data/Scripts/011_Battle/003_Move/013_MoveEffects_SwitchingActing.rb b/Data/Scripts/011_Battle/003_Move/013_MoveEffects_SwitchingActing.rb index e2c436ec1..edc0d8307 100644 --- a/Data/Scripts/011_Battle/003_Move/013_MoveEffects_SwitchingActing.rb +++ b/Data/Scripts/011_Battle/003_Move/013_MoveEffects_SwitchingActing.rb @@ -3,7 +3,7 @@ #=============================================================================== class Battle::Move::FleeFromBattle < Battle::Move def pbMoveFailed?(user, targets) - if !@battle.pbCanRun?(user.index) + if !@battle.pbCanRun?(user.index) || (user.wild? && user.allAllies.length > 0) @battle.pbDisplay(_INTL("But it failed!")) return true end @@ -23,7 +23,7 @@ end class Battle::Move::SwitchOutUserStatusMove < Battle::Move def pbMoveFailed?(user, targets) if user.wild? - if !@battle.pbCanRun?(user.index) + if !@battle.pbCanRun?(user.index) || user.allAllies.length > 0 @battle.pbDisplay(_INTL("But it failed!")) return true end @@ -145,9 +145,9 @@ class Battle::Move::SwitchOutUserPassOnEffects < Battle::Move end #=============================================================================== -# In wild battles, makes target flee. Fails if target is a higher level than the -# user. -# In trainer battles, target switches out. +# When used against a sole wild Pokémon, makes target flee and ends the battle; +# fails if target is a higher level than the user. +# When used against a trainer's Pokémon, target switches out. # For status moves. (Roar, Whirlwind) #=============================================================================== class Battle::Move::SwitchOutTargetStatusMove < Battle::Move @@ -171,38 +171,40 @@ class Battle::Move::SwitchOutTargetStatusMove < Battle::Move @battle.pbDisplay(_INTL("{1} anchored itself with its roots!", target.pbThis)) if show_message return true end - if !@battle.canRun - @battle.pbDisplay(_INTL("But it failed!")) if show_message - return true - end - if @battle.wildBattle? && target.level > user.level - @battle.pbDisplay(_INTL("But it failed!")) if show_message - return true - end - if @battle.trainerBattle? + if target.wild? && target.allAllies.length == 0 && @battle.canRun + # End the battle + if target.level > user.level + @battle.pbDisplay(_INTL("But it failed!")) if show_message + return true + end + elsif !target.wild? + # Switch target out canSwitch = false @battle.eachInTeamFromBattlerIndex(target.index) do |_pkmn, i| - next if !@battle.pbCanSwitchIn?(target.index, i) - canSwitch = true - break + canSwitch = @battle.pbCanSwitchIn?(target.index, i) + break if canSwitch end if !canSwitch @battle.pbDisplay(_INTL("But it failed!")) if show_message return true end + else + @battle.pbDisplay(_INTL("But it failed!")) if show_message + return true end return false end - def pbEffectGeneral(user) - @battle.decision = 3 if @battle.wildBattle? # Escaped from battle + def pbEffectAgainstTarget(user, target) + @battle.decision = 3 if target.wild? # Escaped from battle end def pbSwitchOutTargetEffect(user, targets, numHits, switched_battlers) - return if @battle.wildBattle? || !switched_battlers.empty? + return if !switched_battlers.empty? return if user.fainted? || numHits == 0 targets.each do |b| next if b.fainted? || b.damageState.unaffected + next if b.wild? next if b.effects[PBEffects::Ingrain] next if b.hasActiveAbility?(:SUCTIONCUPS) && !@battle.moldBreaker newPkmn = @battle.pbGetReplacementPokemonIndex(b.index, true) # Random @@ -218,24 +220,26 @@ class Battle::Move::SwitchOutTargetStatusMove < Battle::Move end #=============================================================================== -# In wild battles, makes target flee. Fails if target is a higher level than the -# user. -# In trainer battles, target switches out. +# When used against a sole wild Pokémon, makes target flee and ends the battle; +# fails if target is a higher level than the user. +# When used against a trainer's Pokémon, target switches out. # For damaging moves. (Circle Throw, Dragon Tail) #=============================================================================== class Battle::Move::SwitchOutTargetDamagingMove < Battle::Move def pbEffectAgainstTarget(user, target) - if @battle.wildBattle? && target.level <= user.level && @battle.canRun && + if target.wild? && target.allAllies.length == 0 && @battle.canRun && + target.level <= user.level && (target.effects[PBEffects::Substitute] == 0 || ignoresSubstitute?(user)) - @battle.decision = 3 + @battle.decision = 3 # Escaped from battle end end def pbSwitchOutTargetEffect(user, targets, numHits, switched_battlers) - return if @battle.wildBattle? || !switched_battlers.empty? + return if !switched_battlers.empty? return if user.fainted? || numHits == 0 targets.each do |b| next if b.fainted? || b.damageState.unaffected || b.damageState.substitute + next if b.wild? next if b.effects[PBEffects::Ingrain] next if b.hasActiveAbility?(:SUCTIONCUPS) && !@battle.moldBreaker newPkmn = @battle.pbGetReplacementPokemonIndex(b.index, true) # Random 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 e26773165..281eccd76 100644 --- a/Data/Scripts/011_Battle/005_AI/001_Battle_AI.rb +++ b/Data/Scripts/011_Battle/005_AI/001_Battle_AI.rb @@ -5,13 +5,10 @@ class Battle::AI attr_reader :battle attr_reader :trainer attr_reader :battlers - attr_reader :roles attr_reader :user, :target, :move def initialize(battle) @battle = battle - @roles = [Array.new(@battle.pbParty(0).length) { |i| determine_roles(0, i) }, - Array.new(@battle.pbParty(1).length) { |i| determine_roles(1, i) }] end def create_ai_objects diff --git a/Data/Scripts/011_Battle/005_AI/002_AI_Switch.rb b/Data/Scripts/011_Battle/005_AI/002_AI_Switch.rb index cb77b63ad..be8c922f7 100644 --- a/Data/Scripts/011_Battle/005_AI/002_AI_Switch.rb +++ b/Data/Scripts/011_Battle/005_AI/002_AI_Switch.rb @@ -97,9 +97,7 @@ class Battle::AI reserves.sort! { |a, b| b[1] <=> a[1] } # Sort from highest to lowest rated # Don't bother choosing to switch if all replacements are poorly rated if @trainer.high_skill? && !mandatory - # TODO: Should the current battler be rated as well, to provide a - # threshold instead of using a threshold of 100? - return -1 if reserves[0][1] < 100 # Best replacement rated at <100, don't switch + return -1 if reserves[0][1] < 100 # If best replacement rated at <100, don't switch end # Return the party index of the best rated replacement Pokémon return reserves[0][0] @@ -136,9 +134,8 @@ class Battle::AI pkmn.moves.each do |m| next if m.power == 0 || (m.pp == 0 && m.total_pp > 0) @battle.battlers[idxBattler].allOpposing.each do |b| + next if pokemon_can_absorb_move?(b.pokemon, m, m.type) bTypes = b.pbTypes(true) - # TODO: Consider Wonder Guard, Volt Absorb et al. Consider pkmn's - # ability if it changes the user's types or powers up their moves? score += m.power * Effectiveness.calculate(m.type, *bTypes) / 10 end end @@ -175,7 +172,6 @@ end #=============================================================================== # Pokémon is about to faint because of Perish Song. -# TODO: Also switch to remove other negative effects like Disable, Yawn. #=============================================================================== Battle::AI::Handlers::ShouldSwitch.add(:perish_song, proc { |battler, reserves, ai, battle| @@ -443,65 +439,60 @@ Battle::AI::Handlers::ShouldSwitch.add(:battler_is_useless, ) #=============================================================================== -# Pokémon can't do anything to a Wonder Guard foe. -# TODO: Check other abilities that provide immunities? +# Pokémon can't do anything to any foe because its ability absorbs all damage +# the Pokémon can deal out. #=============================================================================== -Battle::AI::Handlers::ShouldSwitch.add(:foe_has_wonder_guard, +Battle::AI::Handlers::ShouldSwitch.add(:foe_absorbs_all_moves_with_its_ability, proc { |battler, reserves, ai, battle| + next false if battler.battler.turnCount < 2 # Don't switch out too quickly next false if battler.battler.hasMoldBreaker? - non_wonder_guard_foe_exists = false - has_super_effective_move = false - foe_types = battler.pbTypes(true) - next false if foe_types.length == 0 + # Check if battler can damage any of its foes + can_damage_foe = false ai.each_foe_battler(battler.side) do |b, i| - if !b.has_active_ability?(:WONDERGUARD) - non_wonder_guard_foe_exists = true - break - end if ai.trainer.high_skill? && b.rough_end_of_round_damage > 0 - non_wonder_guard_foe_exists = true # Wonder Guard is being overcome already + can_damage_foe = true # Foe is being damaged already break end - # Check for super-effective damaging moves + # Check for battler's moves that can damage the foe (b) battler.battler.eachMove do |move| next if move.statusMove? if ["IgnoreTargetAbility", "CategoryDependsOnHigherDamageIgnoreTargetAbility"].include?(move.function) - has_super_effective_move = true + can_damage_foe = true break end - eff = Effectiveness.calculate(move.pbCalcType(battler.battler), *foe_types) - if Effectiveness.super_effective?(eff) - has_super_effective_move = true + if !ai.pokemon_can_absorb_move?(b, move, move.pbCalcType(battler.battler)) + can_damage_foe = true break end end - break if has_super_effective_move + break if can_damage_foe end - if !non_wonder_guard_foe_exists && !has_super_effective_move - # Check reserves for super-effective moves; only switch if there are any - reserve_has_super_effective_move = false - reserves.each do |pkmn| + next false if can_damage_foe + # Check if a reserve could damage any foe; only switch if one could + reserve_can_damage_foe = false + reserves.each do |pkmn| + ai.each_foe_battler(battler.side) do |b, i| + # Check for reserve's moves that can damage the foe (b) pkmn.moves.each do |move| next if move.status_move? if ["IgnoreTargetAbility", "CategoryDependsOnHigherDamageIgnoreTargetAbility"].include?(move.function_code) - reserve_has_super_effective_move = true + reserve_can_damage_foe = true break end - eff = Effectiveness.calculate(move.type, *foe_types) - if Effectiveness.super_effective?(eff) - reserve_has_super_effective_move = true + if !ai.pokemon_can_absorb_move?(b, move, move.type) + reserve_can_damage_foe = true break end end - break if reserve_has_super_effective_move + break if reserve_can_damage_foe end - next false if !reserve_has_super_effective_move - PBDebug.log_ai("#{battler.name} wants to switch because it can't do anything against Wonder Guard") - next true + break if reserve_can_damage_foe end - next false + next false if !reserve_can_damage_foe + PBDebug.log_ai("#{battler.name} wants to switch because it can't damage the foe(s)") + next true } ) @@ -576,7 +567,6 @@ Battle::AI::Handlers::ShouldSwitch.add(:sudden_death, #=============================================================================== # Pokémon is within 5 levels of the foe, and foe's last move was super-effective # and powerful. -# TODO: Review switch deciding. #=============================================================================== Battle::AI::Handlers::ShouldSwitch.add(:high_damage_from_foe, proc { |battler, reserves, ai, battle| @@ -653,7 +643,7 @@ Battle::AI::Handlers::ShouldNotSwitch.add(:battler_has_super_effective_move, # NOTE: Ideally this would ignore foes that move cannot target, but that # is complicated enough to implement that I'm not bothering. It's # also rare that it would matter. - eff = b.effectiveness_of_type_against_battler(move_type, battler) + eff = b.effectiveness_of_type_against_battler(move_type, battler, move) has_super_effective_move = Effectiveness.super_effective?(eff) break if has_super_effective_move end @@ -670,8 +660,6 @@ Battle::AI::Handlers::ShouldNotSwitch.add(:battler_has_super_effective_move, #=============================================================================== # Don't bother switching if the battler has 4 or more positive stat stages. # Negative stat stages are ignored. -# TODO: Ignore this if deciding whether to use Baton Pass (assuming move-scoring -# uses this code). #=============================================================================== Battle::AI::Handlers::ShouldNotSwitch.add(:battler_has_very_raised_stats, proc { |battler, reserves, ai, battle| diff --git a/Data/Scripts/011_Battle/005_AI/007_AI_ChooseMove_OtherScores.rb b/Data/Scripts/011_Battle/005_AI/007_AI_ChooseMove_OtherScores.rb index cd61ac41e..0061d2235 100644 --- a/Data/Scripts/011_Battle/005_AI/007_AI_ChooseMove_OtherScores.rb +++ b/Data/Scripts/011_Battle/005_AI/007_AI_ChooseMove_OtherScores.rb @@ -146,34 +146,31 @@ Battle::AI::Handlers::GeneralMoveScore.add(:any_battler_can_Snatch_move, #=============================================================================== # Pick a good move for the Choice items. -# TODO: Review score modifier. #=============================================================================== Battle::AI::Handlers::GeneralMoveScore.add(:good_move_for_choice_item, proc { |score, move, user, ai, battle| - if ai.trainer.medium_skill? - if user.has_active_item?([:CHOICEBAND, :CHOICESPECS, :CHOICESCARF]) || - user.has_active_ability?(:GORILLATACTICS) - old_score = score - # Really don't prefer status moves (except Trick) - if move.statusMove? && move.function != "UserTargetSwapItems" - score -= 25 - PBDebug.log_score_change(score - old_score, "don't want to be Choiced into a status move") - next score - end - # Don't prefer moves which are 0x against at least one type - move_type = move.rough_type - GameData::Type.each do |type_data| - score -= 8 if type_data.immunities.include?(move_type) - end - # Don't prefer moves with lower accuracy - if move.accuracy > 0 - score -= (0.4 * (100 - move.accuracy)).to_i # -0 (100%) to -39 (1%) - end - # Don't prefer moves with low PP - score -= 10 if move.move.pp <= 5 - PBDebug.log_score_change(score - old_score, "move is less suitable to be Choiced into") - end + next score if !ai.trainer.medium_skill? + next score if !user.has_active_item?([:CHOICEBAND, :CHOICESPECS, :CHOICESCARF]) && + !user.has_active_ability?(:GORILLATACTICS) + old_score = score + # Really don't prefer status moves (except Trick) + if move.statusMove? && move.function != "UserTargetSwapItems" + score -= 25 + PBDebug.log_score_change(score - old_score, "don't want to be Choiced into a status move") + next score end + # Don't prefer moves which are 0x against at least one type + move_type = move.rough_type + GameData::Type.each do |type_data| + score -= 8 if type_data.immunities.include?(move_type) + end + # Don't prefer moves with lower accuracy + if move.accuracy > 0 + score -= (0.4 * (100 - move.accuracy)).to_i # -0 (100%) to -39 (1%) + end + # Don't prefer moves with low PP + score -= 10 if move.move.pp <= 5 + PBDebug.log_score_change(score - old_score, "move is less suitable to be Choiced into") next score } ) @@ -205,22 +202,42 @@ Battle::AI::Handlers::GeneralMoveScore.add(:damaging_move_and_either_side_no_res ) #=============================================================================== -# +# Don't prefer Fire-type moves if target knows Powder and is faster than the +# user. #=============================================================================== -# TODO: Don't prefer Fire-type moves if target has previously used Powder and is -# faster than the user. +Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:target_can_powder_fire_moves, + proc { |score, move, user, target, ai, battle| + if ai.trainer.high_skill? && move.rough_type == :FIRE && + target.has_move_with_function?("TargetNextFireMoveDamagesTarget") && + target.faster_than?(user) + old_score = score + score -= 5 # Only 5 because we're not sure target will use Powder + PBDebug.log_score_change(score - old_score, "target knows Powder and could negate Fire moves") + end + next score + } +) #=============================================================================== -# +# Don't prefer moves if target knows a move that can make them Electric-type, +# and if target is unaffected by Electric moves. #=============================================================================== -# TODO: Don't prefer Normal-type moves if target has previously used Ion Deluge -# and is immune to Electric moves. - -#=============================================================================== -# -#=============================================================================== -# TODO: Don't prefer a move that is stopped by Wide Guard if any foe has -# previously used Wide Guard. +Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:target_can_make_moves_Electric_and_be_immune, + proc { |score, move, user, target, ai, battle| + next score if !ai.trainer.high_skill? + next score if !target.has_move_with_function?("TargetMovesBecomeElectric") && + !(move.rough_type == :NORMAL && target.has_move_with_function?("NormalMovesBecomeElectric")) + next score if !ai.pokemon_can_absorb_move?(target, move, :ELECTRIC) && + !Effectiveness.ineffective?(target.effectiveness_of_type_against_battler(:ELECTRIC, user)) + priority = move.rough_priority(user) + if priority > 0 || (priority == 0 && target.faster_than?(user)) # Target goes first + old_score = score + score -= 5 # Only 5 because we're not sure target will use Electrify/Ion Deluge + PBDebug.log_score_change(score - old_score, "target knows Electrify/Ion Deluge and is immune to Electric moves") + end + next score + } +) #=============================================================================== # Don't prefer attacking the target if they'd be semi-invulnerable. @@ -293,6 +310,12 @@ Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:predicted_damage, old_score = score score += 10 PBDebug.log_score_change(score - old_score, "predicted to KO the target") + if move.move.multiHitMove? && target.hp == target.totalhp && + (target.has_active_ability?(:STURDY) || target.has_active_item?(:FOCUSSASH)) + old_score = score + score += 8 + PBDebug.log_score_change(score - old_score, "predicted to overcome the target's Sturdy/Focus Sash") + end end end end @@ -313,6 +336,7 @@ Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:external_flinching_effe if battle.moldBreaker || !target.has_active_ability?([:INNERFOCUS, :SHIELDDUST]) old_score = score score += 8 + score += 5 if move.move.multiHitMove? PBDebug.log_score_change(score - old_score, "added chance to cause flinching") end end @@ -338,25 +362,67 @@ Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:thawing_move_against_fr ) #=============================================================================== -# +# Don't prefer a damaging move if it will trigger the target's ability or held +# item when used, e.g. Effect Spore/Rough Skin, Pickpocket, Rocky Helmet, Red +# Card. +# NOTE: These abilities/items may not be triggerable after all (e.g. they +# require the move to make contact but it doesn't), or may have a negative +# effect for the target (e.g. Air Balloon popping), but it's too much +# effort to go into detail deciding all this. #=============================================================================== -# TODO: Check all effects that trigger upon using a move, including per-hit -# stuff in def pbEffectsOnMakingHit (worse if the move is a multi-hit one) -# and end-of-move stuff in def pbEffectsAfterMove. +Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:trigger_target_ability_or_item_upon_hit, + proc { |score, move, user, target, ai, battle| + if ai.trainer.high_skill? && move.damagingMove? && target.effects[PBEffects::Substitute] == 0 + if target.ability_active? + if Battle::AbilityEffects::OnBeingHit[target.ability] || + (Battle::AbilityEffects::AfterMoveUseFromTarget[target.ability] && + (!user.has_active_ability?(:SHEERFORCE) || move.move.addlEffect == 0)) + old_score = score + score += 8 + PBDebug.log_score_change(score - old_score, "can trigger the target's ability") + end + end + if target.battler.isSpecies?(:CRAMORANT) && target.ability == :GULPMISSILE && + target.battler.form > 0 && !target.effects[PBEffects::Transform] + old_score = score + score += 8 + PBDebug.log_score_change(score - old_score, "can trigger the target's ability") + end + if target.item_active? + if Battle::ItemEffects::OnBeingHit[target.item] || + (Battle::ItemEffects::AfterMoveUseFromTarget[target.item] && + (!user.has_active_ability?(:SHEERFORCE) || move.move.addlEffect == 0)) + old_score = score + score += 8 + PBDebug.log_score_change(score - old_score, "can trigger the target's item") + end + end + end + next score + } +) #=============================================================================== -# +# Prefer a damaging move if it will trigger the user's ability when used, e.g. +# Poison Touch, Magician. #=============================================================================== -# TODO: Prefer a contact move if making contact with the target could trigger -# an effect that's good for the user (Poison Touch/Pickpocket). - -#=============================================================================== -# -#=============================================================================== -# TODO: Don't prefer contact move if making contact with the target could -# trigger an effect that's bad for the user (Static, etc.). -# => Also check if target has previously used Spiky Shield/King's Shield/ -# Baneful Bunker, and don't prefer move if so +Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:trigger_user_ability_upon_hit, + proc { |score, move, user, target, ai, battle| + if ai.trainer.high_skill? && user.ability_active? && move.damagingMove? && + target.effects[PBEffects::Substitute] == 0 + # NOTE: The only ability with an OnDealingHit effect also requires the + # move to make contact. The only abilities with an OnEndOfUsingMove + # effect revolve around damaging moves. + if (Battle::AbilityEffects::OnDealingHit[user.ability] && move.move.contactMove?) || + Battle::AbilityEffects::OnEndOfUsingMove[user.ability] + old_score = score + score += 8 + PBDebug.log_score_change(score - old_score, "can trigger the user's ability") + end + end + next score + } +) #=============================================================================== # Don't prefer damaging moves that will knock out the target if they are using @@ -387,10 +453,23 @@ Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:knocking_out_a_destiny_ ) #=============================================================================== -# +# Don't prefer damaging moves if the target is using Rage, unless the move will +# deal enough damage to KO the target within two rounds. #=============================================================================== -# TODO: Don't prefer damaging moves if the target is using Rage and they benefit -# from the raised Attack. +Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:damaging_a_raging_target, + proc { |score, move, user, target, ai, battle| + if ai.trainer.medium_skill? && target.effects[PBEffects::Rage] && move.damagingMove? + # Worth damaging the target if it can be knocked out within two rounds + if ai.trainer.has_skill_flag?("HPAware") + next score if (move.rough_damage + target.rough_end_of_round_damage) * 2 > target.hp * 1.1 + end + old_score = score + score -= 10 + PBDebug.log_score_change(score - old_score, "don't want to damage a Raging target") + end + next score + } +) #=============================================================================== # Don't prefer damaging moves if the target is Biding, unless the move will deal diff --git a/Data/Scripts/011_Battle/005_AI/008_AI_Utilities.rb b/Data/Scripts/011_Battle/005_AI/008_AI_Utilities.rb index 9d552b050..4c9195986 100644 --- a/Data/Scripts/011_Battle/005_AI/008_AI_Utilities.rb +++ b/Data/Scripts/011_Battle/005_AI/008_AI_Utilities.rb @@ -38,14 +38,16 @@ class Battle::AI # Assumes that pkmn's ability is not negated by a global effect (e.g. # Neutralizing Gas). - # pkmn is either a Battle::AI::AIBattler or a Pokemon. move is a Battle::Move. + # pkmn is either a Battle::AI::AIBattler or a Pokemon. + # move is a Battle::Move or a Pokemon::Move. def pokemon_can_absorb_move?(pkmn, move, move_type) return false if pkmn.is_a?(Battle::AI::AIBattler) && !pkmn.ability_active? # Check pkmn's ability # Anything with a Battle::AbilityEffects::MoveImmunity handler case pkmn.ability_id when :BULLETPROOF - return move.bombMove? + move_data = GameData::Move.get(move.id) + return move_data.has_flag?("Bomb") when :FLASHFIRE return move_type == :FIRE when :LIGHTNINGROD, :MOTORDRIVE, :VOLTABSORB @@ -53,7 +55,8 @@ class Battle::AI when :SAPSIPPER return move_type == :GRASS when :SOUNDPROOF - return move.soundMove? + move_data = GameData::Move.get(move.id) + return move_data.has_flag?("Sound") when :STORMDRAIN, :WATERABSORB, :DRYSKIN return move_type == :WATER when :TELEPATHY @@ -62,7 +65,7 @@ class Battle::AI when :WONDERGUARD types = pkmn.types types = pkmn.pbTypes(true) if pkmn.is_a?(Battle::AI::AIBattler) - return Effectiveness.super_effective_type?(move_type, *types) + return !Effectiveness.super_effective_type?(move_type, *types) end return false end @@ -157,24 +160,72 @@ class Battle::AI #----------------------------------------------------------------------------- - # TODO: Add more items. BASE_ITEM_RATINGS = { - 4 => [:CHOICEBAND, :CHOICESCARF, :CHOICESPECS, :DEEPSEATOOTH, :LEFTOVERS, - :LIGHTBALL, :THICKCLUB], - 3 => [:ADAMANTORB, :GRISEOUSORB, :LIFEORB, :LUSTROUSORB, :SOULDEW], - 2 => [:BLACKBELT, :BLACKGLASSES, :CHARCOAL, :DRAGONFANG, :HARDSTONE, + 10 => [:EVIOLITE, :FOCUSSASH, :LIFEORB, :THICKCLUB], + 9 => [:ASSAULTVEST, :BLACKSLUDGE, :CHOICEBAND, :CHOICESCARF, :CHOICESPECS, + :DEEPSEATOOTH, :LEFTOVERS], + 8 => [:LEEK, :STICK, :THROATSPRAY, :WEAKNESSPOLICY], + 7 => [:EXPERTBELT, :LIGHTBALL, :LUMBERRY, :POWERHERB, :ROCKYHELMET, + :SITRUSBERRY], + 6 => [:KINGSROCK, :LIECHIBERRY, :LIGHTCLAY, :PETAYABERRY, :RAZORFANG, + :REDCARD, :SALACBERRY, :SHELLBELL, :WHITEHERB, + # Type-resisting berries + :BABIRIBERRY, :CHARTIBERRY, :CHILANBERRY, :CHOPLEBERRY, :COBABERRY, + :COLBURBERRY, :HABANBERRY, :KASIBBERRY, :KEBIABERRY, :OCCABERRY, + :PASSHOBERRY, :PAYAPABERRY, :RINDOBERRY, :ROSELIBERRY, :SHUCABERRY, + :TANGABERRY, :WACANBERRY, :YACHEBERRY, + # Gems + :BUGGEM, :DARKGEM, :DRAGONGEM, :ELECTRICGEM, :FAIRYGEM, :FIGHTINGGEM, + :FIREGEM, :FLYINGGEM, :GHOSTGEM, :GRASSGEM, :GROUNDGEM, :ICEGEM, + :NORMALGEM, :POISONGEM, :PSYCHICGEM, :ROCKGEM, :STEELGEM, :WATERGEM, + # Legendary Orbs + :ADAMANTORB, :GRISEOUSORB, :LUSTROUSORB, :SOULDEW, + # Berries that heal HP and may confuse + :AGUAVBERRY, :FIGYBERRY, :IAPAPABERRY, :MAGOBERRY, :WIKIBERRY], + 5 => [:CUSTAPBERRY, :DEEPSEASCALE, :EJECTBUTTON, :FOCUSBAND, :JABOCABERRY, + :KEEBERRY, :LANSATBERRY, :MARANGABERRY, :MENTALHERB, :METRONOME, + :MUSCLEBAND, :QUICKCLAW, :RAZORCLAW, :ROWAPBERRY, :SCOPELENS, + :WISEGLASSES, + # Type power boosters + :BLACKBELT, :BLACKGLASSES, :CHARCOAL, :DRAGONFANG, :HARDSTONE, :MAGNET, :METALCOAT, :MIRACLESEED, :MYSTICWATER, :NEVERMELTICE, - :POISONBARB, :SHARPBEAK, :SILKSCARF, :SILVERPOWDER, :SOFTSAND, + :POISONBARB, :SHARPBEAK, :SILKSCARF,:SILVERPOWDER, :SOFTSAND, :SPELLTAG, :TWISTEDSPOON, + :ODDINCENSE, :ROCKINCENSE, :ROSEINCENSE, :SEAINCENSE, :WAVEINCENSE, + # Plates :DRACOPLATE, :DREADPLATE, :EARTHPLATE, :FISTPLATE, :FLAMEPLATE, :ICICLEPLATE, :INSECTPLATE, :IRONPLATE, :MEADOWPLATE, :MINDPLATE, :PIXIEPLATE, :SKYPLATE, :SPLASHPLATE, :SPOOKYPLATE, :STONEPLATE, :TOXICPLATE, :ZAPPLATE, - :ODDINCENSE, :ROCKINCENSE, :ROSEINCENSE, :SEAINCENSE, :WAVEINCENSE, - :MUSCLEBAND, :WISEGLASSES], - 1 => [:METRONOME], - -2 => [:LAGGINGTAIL, :STICKYBARB], - -4 => [:BLACKSLUDGE, :FLAMEORB, :IRONBALL, :TOXICORB] + # Weather/terrain extenders + :DAMPROCK, :HEATROCK, :ICYROCK, :SMOOTHROCK, :TERRAINEXTENDER], + 4 => [:ADRENALINEORB, :APICOTBERRY, :BLUNDERPOLICY, :CHESTOBERRY, + :EJECTPACK, :ENIGMABERRY, :GANLONBERRY, :HEAVYDUTYBOOTS, + :ROOMSERVICE, :SAFETYGOGGLES, :SHEDSHELL, :STARFBERRY], + 3 => [:BIGROOT, :BRIGHTPOWDER, :LAXINCENSE, :LEPPABERRY, :PERSIMBERRY, + :PROTECTIVEPADS, :UTILITYUMBRELLA, + # Status problem-curing berries (except Chesto which is in 4) + :ASPEARBERRY, :CHERIBERRY, :PECHABERRY, :RAWSTBERRY], + 2 => [:ABSORBBULB, :BERRYJUICE, :CELLBATTERY, :GRIPCLAW, :LUMINOUSMOSS, + :MICLEBERRY, :ORANBERRY, :SNOWBALL, :WIDELENS, :ZOOMLENS, + # Terrain seeds + :ELECTRICSEED, :GRASSYSEED, :MISTYSEED, :PSYCHICSEED], + 1 => [:AIRBALLOON, :BINDINGBAND, :DESTINYKNOT, :FLOATSTONE, :LUCKYPUNCH, + :METALPOWDER, :QUICKPOWDER, + # Drives + :BURNDRIVE, :CHILLDRIVE, :DOUSEDRIVE, :SHOCKDRIVE, + # Memories + :BUGMEMORY, :DARKMEMORY, :DRAGONMEMORY, :ELECTRICMEMORY, + :FAIRYMEMORY, :FIGHTINGMEMORY, :FIREMEMORY, :FLYINGMEMORY, + :GHOSTMEMORY, :GRASSMEMORY, :GROUNDMEMORY, :ICEMEMORY, :POISONMEMORY, + :PSYCHICMEMORY, :ROCKMEMORY, :STEELMEMORY, :WATERMEMORY + ], + 0 => [:SMOKEBALL], + -5 => [:FULLINCENSE, :LAGGINGTAIL, :RINGTARGET], + -6 => [:MACHOBRACE, :POWERANKLET, :POWERBAND, :POWERBELT, :POWERBRACER, + :POWERLENS, :POWERWEIGHT], + -7 => [:FLAMEORB, :IRONBALL, :TOXICORB], + -9 => [:STICKYBARB] } end @@ -342,9 +393,71 @@ Battle::AI::Handlers::ItemRanking.add(:ADAMANTORB, } ) +Battle::AI::Handlers::ItemRanking.add(:AGUAVBERRY, + proc { |item, score, battler, ai| + if Settings::MECHANICS_GENERATION == 7 # Heals 50% + score += 2 + elsif Settings::MECHANICS_GENERATION <= 6 # Heals 12.5% + score -= 3 + end + if ai.trainer.high_skill? + if battler.battler.nature.stat_changes.any? { |val| val[0] == :SPECIAL_DEFENSE && val[1] < 0 } + score -= 2 # Will confuse + end + end + next score + } +) + +Battle::AI::Handlers::ItemRanking.add(:ASSAULTVEST, + proc { |item, score, battler, ai| + if ai.trainer.high_skill? + score += 1 if !battler.check_for_move { |m| m.statusMove? && !m.is_a?(Battle::Move::UseMoveTargetIsAboutToUse) } + end + next score + } +) + +Battle::AI::Handlers::ItemRanking.add(:BERRYJUICE, + proc { |item, score, battler, ai| + next [10 - (battler.totalhp / 15), 1].max + } +) + +Battle::AI::Handlers::ItemRanking.add(:BIGROOT, + proc { |item, score, battler, ai| + next score if battler.check_for_move do |m| + m.is_a?(Battle::Move::HealUserByHalfOfDamageDone) || + m.is_a?(Battle::Move::HealUserByHalfOfDamageDoneIfTargetAsleep) || + m.is_a?(Battle::Move::HealUserByThreeQuartersOfDamageDone) || + m.is_a?(Battle::Move::HealUserByTargetAttackLowerTargetAttack1) || + m.is_a?(Battle::Move::StartLeechSeedTarget) + end + next 0 + } +) + +Battle::AI::Handlers::ItemRanking.add(:BINDINGBAND, + proc { |item, score, battler, ai| + next score if battler.check_for_move { |m| m.is_a?(Battle::Move::BindTarget) } + next 0 + } +) + +Battle::AI::Handlers::ItemRanking.copy(:BINDINGBAND, :GRIPCLAW) + Battle::AI::Handlers::ItemRanking.add(:BLACKSLUDGE, proc { |item, score, battler, ai| - next 4 if battler.has_type?(:POISON) + next score if battler.has_type?(:POISON) + next -9 + } +) + +Battle::AI::Handlers::ItemRanking.add(:CHESTOBERRY, + proc { |item, score, battler, ai| + if ai.trainer.high_skill? + score += 1 if battler.has_move_with_function("HealUserFullyAndFallAsleep") + end next score } ) @@ -367,6 +480,20 @@ Battle::AI::Handlers::ItemRanking.add(:CHOICESPECS, Battle::AI::Handlers::ItemRanking.copy(:CHOICESPECS, :WISEGLASSES) +Battle::AI::Handlers::ItemRanking.add(:DEEPSEASCALE, + proc { |item, score, battler, ai| + next score if battler.battler.isSpecies?(:CLAMPERL) + next 0 + } +) + +Battle::AI::Handlers::ItemRanking.add(:DAMPROCK, + proc { |item, score, battler, ai| + next score if battler.check_for_move { |m| m.is_a?(Battle::Move::StartRainWeather) } + next 0 + } +) + Battle::AI::Handlers::ItemRanking.add(:DEEPSEATOOTH, proc { |item, score, battler, ai| next score if battler.battler.isSpecies?(:CLAMPERL) && @@ -375,6 +502,62 @@ Battle::AI::Handlers::ItemRanking.add(:DEEPSEATOOTH, } ) +Battle::AI::Handlers::ItemRanking.add(:ELECTRICSEED, + proc { |item, score, battler, ai| + next score if battler.check_for_move { |m| m.is_a?(Battle::Move::StartElectricTerrain) } + next 0 + } +) + +Battle::AI::Handlers::ItemRanking.add(:EVIOLITE, + proc { |item, score, battler, ai| + next score if battler.battler.pokemon.species_data.get_evolutions(true).length > 0 + next 0 + } +) + +Battle::AI::Handlers::ItemRanking.add(:FIGYBERRY, + proc { |item, score, battler, ai| + if Settings::MECHANICS_GENERATION == 7 # Heals 50% + score += 2 + elsif Settings::MECHANICS_GENERATION <= 6 # Heals 12.5% + score -= 3 + end + if ai.trainer.high_skill? + if battler.battler.nature.stat_changes.any? { |val| val[0] == :ATTACK && val[1] < 0 } + score -= 2 # Will confuse + end + end + next score + } +) + +Battle::AI::Handlers::ItemRanking.add(:FLAMEORB, + proc { |item, score, battler, ai| + next 0 if battler.status != :NONE + next 7 if battler.wants_status_problem?(:BURN) + next score + } +) + +Battle::AI::Handlers::ItemRanking.add(:FULLINCENSE, + proc { |item, score, battler, ai| + if ai.trainer.high_skill? + score = 7 if battler.has_active_ability?(:ANALYTIC) + end + next score + } +) + +Battle::AI::Handlers::ItemRanking.copy(:FULLINCENSE, :LAGGINGTAIL) + +Battle::AI::Handlers::ItemRanking.add(:GRASSYSEED, + proc { |item, score, battler, ai| + next score if battler.check_for_move { |m| m.is_a?(Battle::Move::StartGrassyTerrain) } + next 0 + } +) + Battle::AI::Handlers::ItemRanking.add(:GRISEOUSORB, proc { |item, score, battler, ai| next score if battler.battler.isSpecies?(:GIRATINA) && @@ -383,6 +566,36 @@ Battle::AI::Handlers::ItemRanking.add(:GRISEOUSORB, } ) +Battle::AI::Handlers::ItemRanking.add(:HEATROCK, + proc { |item, score, battler, ai| + next score if battler.check_for_move { |m| m.is_a?(Battle::Move::StartSunWeather) } + next 0 + } +) + +Battle::AI::Handlers::ItemRanking.add(:IAPAPABERRY, + proc { |item, score, battler, ai| + if Settings::MECHANICS_GENERATION == 7 # Heals 50% + score += 2 + elsif Settings::MECHANICS_GENERATION <= 6 # Heals 12.5% + score -= 3 + end + if ai.trainer.high_skill? + if battler.battler.nature.stat_changes.any? { |val| val[0] == :DEFENSE && val[1] < 0 } + score -= 2 # Will confuse + end + end + next score + } +) + +Battle::AI::Handlers::ItemRanking.add(:ICYROCK, + proc { |item, score, battler, ai| + next score if battler.check_for_move { |m| m.is_a?(Battle::Move::StartHailWeather) } + next 0 + } +) + Battle::AI::Handlers::ItemRanking.add(:IRONBALL, proc { |item, score, battler, ai| next 0 if battler.has_move_with_function?("ThrowUserItemAtTarget") @@ -390,6 +603,27 @@ Battle::AI::Handlers::ItemRanking.add(:IRONBALL, } ) +Battle::AI::Handlers::ItemRanking.add(:KINGSROCK, + proc { |item, score, battler, ai| + if ai.trainer.high_skill? + score += 1 if battler.check_for_move { |m| m.multiHitMove? } + end + next score + } +) + +Battle::AI::Handlers::ItemRanking.copy(:KINGSROCK, :RAZORFANG) + +Battle::AI::Handlers::ItemRanking.add(:LEEK, + proc { |item, score, battler, ai| + next score if (battler.battler.isSpecies?(:FARFETCHD) || battler.battler.isSpecies?(:SIRFETCHD)) && + battler.check_for_move { |m| m.damagingMove? } + next 0 + } +) + +Battle::AI::Handlers::ItemRanking.copy(:LEEK, :STICK) + Battle::AI::Handlers::ItemRanking.add(:LIGHTBALL, proc { |item, score, battler, ai| next score if battler.battler.isSpecies?(:PIKACHU) && @@ -398,6 +632,24 @@ Battle::AI::Handlers::ItemRanking.add(:LIGHTBALL, } ) +Battle::AI::Handlers::ItemRanking.add(:LIGHTCLAY, + proc { |item, score, battler, ai| + next score if battler.check_for_move do |m| + m.is_a?(Battle::Move::StartWeakenPhysicalDamageAgainstUserSide) || + m.is_a?(Battle::Move::StartWeakenSpecialDamageAgainstUserSide) || + m.is_a?(Battle::Move::StartWeakenDamageAgainstUserSideIfHail) + end + next 0 + } +) + +Battle::AI::Handlers::ItemRanking.add(:LUCKYPUNCH, + proc { |item, score, battler, ai| + next score if battler.battler.isSpecies?(:CHANSEY) + next 0 + } +) + Battle::AI::Handlers::ItemRanking.add(:LUSTROUSORB, proc { |item, score, battler, ai| next score if battler.battler.isSpecies?(:PALKIA) && @@ -406,18 +658,106 @@ Battle::AI::Handlers::ItemRanking.add(:LUSTROUSORB, } ) +Battle::AI::Handlers::ItemRanking.add(:MAGOBERRY, + proc { |item, score, battler, ai| + if Settings::MECHANICS_GENERATION == 7 # Heals 50% + score += 2 + elsif Settings::MECHANICS_GENERATION <= 6 # Heals 12.5% + score -= 3 + end + if ai.trainer.high_skill? + if battler.battler.nature.stat_changes.any? { |val| val[0] == :SPEED && val[1] < 0 } + score -= 2 # Will confuse + end + end + next score + } +) + +Battle::AI::Handlers::ItemRanking.add(:METALPOWDER, + proc { |item, score, battler, ai| + next score if battler.battler.isSpecies?(:DITTO) && !battler.effects[PBEffects::Transform] + next 0 + } +) + +Battle::AI::Handlers::ItemRanking.copy(:METALPOWDER, :QUICKPOWDER) + +Battle::AI::Handlers::ItemRanking.add(:MISTYSEED, + proc { |item, score, battler, ai| + next score if battler.check_for_move { |m| m.is_a?(Battle::Move::StartMistyTerrain) } + next 0 + } +) + +Battle::AI::Handlers::ItemRanking.add(:ORANBERRY, + proc { |item, score, battler, ai| + next [10 - (battler.totalhp / 8), 1].max + } +) + +Battle::AI::Handlers::ItemRanking.add(:POWERHERB, + proc { |item, score, battler, ai| + next score if battler.check_for_move do |m| + m.is_a?(Battle::Move::TwoTurnMove) && + !m.is_a?(Battle::Move::TwoTurnAttackInvulnerableInSkyTargetCannotAct) + end + next 0 + } +) + +Battle::AI::Handlers::ItemRanking.add(:PSYCHICSEED, + proc { |item, score, battler, ai| + next score if battler.check_for_move { |m| m.is_a?(Battle::Move::StartPsychicTerrain) } + next 0 + } +) + +Battle::AI::Handlers::ItemRanking.add(:RINGTARGET, + proc { |item, score, battler, ai| + has_immunity = false + battler.pbTypes(true).each do |type| + has_immunity = GameData::Type.get(type).immunities.length > 0 + break if has_immunity + end + next score if has_immunity + next 0 + } +) + +Battle::AI::Handlers::ItemRanking.add(:SMOOTHROCK, + proc { |item, score, battler, ai| + next score if battler.check_for_move { |m| m.is_a?(Battle::Move::StartSandstormWeather) } + next 0 + } +) + Battle::AI::Handlers::ItemRanking.add(:SOULDEW, proc { |item, score, battler, ai| next 0 if !battler.battler.isSpecies?(:LATIAS) && !battler.battler.isSpecies?(:LATIOS) if Settings::SOUL_DEW_POWERS_UP_TYPES next 0 if !battler.has_damaging_move_of_type?(:PSYCHIC, :DRAGON) - elsif !battler.check_for_move { |m| m.specialMove?(m.type) } - next 1 # Also boosts SpDef + elsif battler.check_for_move { |m| m.specialMove?(m.type) } + next 10 + else + next 6 # Boosts SpDef end next score } ) +Battle::AI::Handlers::ItemRanking.add(:TERRAINEXTENDER, + proc { |item, score, battler, ai| + next score if battler.check_for_move do |m| + m.is_a?(Battle::Move::StartElectricTerrain) || + m.is_a?(Battle::Move::StartGrassyTerrain) || + m.is_a?(Battle::Move::StartMistyTerrain) || + m.is_a?(Battle::Move::StartPsychicTerrain) + end + next 0 + } +) + Battle::AI::Handlers::ItemRanking.add(:THICKCLUB, proc { |item, score, battler, ai| next score if (battler.battler.isSpecies?(:CUBONE) || battler.battler.isSpecies?(:MAROWAK)) && @@ -426,6 +766,61 @@ Battle::AI::Handlers::ItemRanking.add(:THICKCLUB, } ) +Battle::AI::Handlers::ItemRanking.add(:THROATSPRAY, + proc { |item, score, battler, ai| + next score if battler.check_for_move { |m| m.soundMove? } + next 0 + } +) + +Battle::AI::Handlers::ItemRanking.add(:TOXICORB, + proc { |item, score, battler, ai| + next 0 if battler.status != :NONE + next 7 if battler.wants_status_problem?(:POISON) + next score + } +) + +Battle::AI::Handlers::ItemRanking.add(:WHITEHERB, + proc { |item, score, battler, ai| + if ai.trainer.high_skill? + score += 1 if battler.has_move_with_function("LowerUserDefSpDef1RaiseUserAtkSpAtkSpd2") + end + next score + } +) + +Battle::AI::Handlers::ItemRanking.add(:WIKIBERRY, + proc { |item, score, battler, ai| + if Settings::MECHANICS_GENERATION == 7 # Heals 50% + score += 2 + elsif Settings::MECHANICS_GENERATION <= 6 # Heals 12.5% + score -= 3 + end + if ai.trainer.high_skill? + if battler.battler.nature.stat_changes.any? { |val| val[0] == :SPECIAL_ATTACK && val[1] < 0 } + score -= 2 # Will confuse + end + end + next score + } +) + +Battle::AI::Handlers::ItemRanking.add(:ZOOMLENS, + proc { |item, score, battler, ai| + if ai.trainer.high_skill? + score += 1 if battler.stages[:ACCURACY] < 0 + battler.battler.eachMove do |m| + next if m.accuracy == 0 || m.is_a?(Battle::Move::OHKO) + next if m.accuracy > 70 + score += 1 + break + end + end + next score + } +) + Battle::AI::Handlers::ItemRanking.addIf(:type_boosting_items, proc { |item| next [:BLACKBELT, :BLACKGLASSES, :CHARCOAL, :DRAGONFANG, :HARDSTONE, @@ -469,3 +864,36 @@ Battle::AI::Handlers::ItemRanking.addIf(:type_boosting_items, next 0 } ) + +Battle::AI::Handlers::ItemRanking.addIf(:gems, + proc { |item| + next [:FIREGEM, :WATERGEM, :ELECTRICGEM, :GRASSGEM, :ICEGEM, :FIGHTINGGEM, + :POISONGEM, :GROUNDGEM, :FLYINGGEM, :PSYCHICGEM, :BUGGEM, :ROCKGEM, + :GHOSTGEM, :DRAGONGEM, :DARKGEM, :STEELGEM, :FAIRYGEM, :NORMALGEM].include?(item) + }, + proc { |item, score, battler, ai| + score += 2 if Settings::MECHANICS_GENERATION <= 5 # 1.5x boost rather than 1.3x + boosted_type = { + :BUGGEM => :BUG, + :DARKGEM => :DARK, + :DRAGONGEM => :DRAGON, + :ELECTRICGEM => :ELECTRIC, + :FAIRYGEM => :FAIRY, + :FIGHTINGGEM => :FIGHTING, + :FIREGEM => :FIRE, + :FLYINGGEM => :FLYING, + :GHOSTGEM => :GHOST, + :GRASSGEM => :GRASS, + :GROUNDGEM => :GROUND, + :ICEGEM => :ICE, + :NORMALGEM => :NORMAL, + :POISONGEM => :POISON, + :PSYCHICGEM => :PSYCHIC, + :ROCKGEM => :ROCK, + :STEELGEM => :STEEL, + :WATERGEM => :WATER, + }[item] + next score if boosted_type && battler.has_damaging_move_of_type?(boosted_type) + next 0 + } +) diff --git a/Data/Scripts/011_Battle/005_AI/009_AI_Roles.rb b/Data/Scripts/011_Battle/005_AI/009_AI_Roles.rb deleted file mode 100644 index 8072e9243..000000000 --- a/Data/Scripts/011_Battle/005_AI/009_AI_Roles.rb +++ /dev/null @@ -1,157 +0,0 @@ -#=============================================================================== -# -#=============================================================================== -class Battle::AI - # Determine the roles filled by a Pokémon on a given side at a given party - # index. - # Roles are: - # :ace - # :baton_passer - # :cleric - # :field_setter - # :lead - # :phazer - # :physical_wall - # :pivot - # :screener - # :second - # :special_wall - # :spinner - # :stall_breaker - # :status_absorber - # :sweeper - # :tank - # :trapper - # :weather_setter - # NOTE: Reborn has the REVENGEKILLER role which compares mon's speed with - # opponent (only when deciding whether to switch mon in) - this - # comparison should be calculated when needed instead of being a role. - def determine_roles(side, index) - pkmn = @battle.pbParty(side)[index] - ret = [] - return ret if !pkmn || pkmn.egg? - # Check for moves indicative of particular roles - hasHealMove = false - pkmn.moves.each do |m| - next if !m - move = Battle::Move.from_pokemon_move(@battle, m) - hasHealMove = true if !hasHealMove && move.healingMove? - case move.function - when "SleepTargetNextTurn", # Yawn - "StartPerishCountsForAllBattlers", # Perish Song - "SwitchOutTargetStatusMove", # Roar - "SwitchOutTargetDamagingMove" # Circle Throw - ret.push(:phazer) - when "CureUserPartyStatus" # Aromatherapy/Heal Bell - ret.push(:cleric) - when "DisableTargetStatusMoves" # Taunt - ret.push(:stall_breaker) - when "HealUserPositionNextTurn" # Wish - ret.push(:cleric) if pkmn.ev[:HP] == Pokemon::EV_STAT_LIMIT - when "HealUserFullyAndFallAsleep" # Rest - ret.push(:status_absorber) - when "SwitchOutUserPassOnEffects" # Baton Pass - ret.push(:baton_passer) - when "SwitchOutUserStatusMove", "SwitchOutUserDamagingMove" # Teleport (Gen 8+), U-turn - ret.push(:pivot) if hasHealMove - when "RemoveUserBindingAndEntryHazards" # Rapid Spin - ret.push(:spinner) - when "StartElectricTerrain", "StartGrassyTerrain", - "StartMistyTerrain", "StartPsychicTerrain" # Terrain moves - ret.push(:field_setter) - else - ret.push(:weather_setter) if move.is_a?(Battle::Move::WeatherMove) - end - end - # Check EVs, nature and moves for combinations indicative of particular roles - if pkmn.ev[:SPEED] == Pokemon::EV_STAT_LIMIT - if [:MODEST, :ADAMANT, # SpAtk+ Atk-, Atk+ SpAtk- - :TIMID, :JOLLY].include?(pkmn.nature) # Spd+ Atk-, Spd+ SpAtk- - ret.push(:sweeper) - end - end - if hasHealMove - if pkmn.nature.stat_changes.any? { |change| change[0] == :DEFENSE && change[1] > 0 } && - !pkmn.nature.stat_changes.any? { |change| change[0] == :DEFENSE && change[1] < 0 } - ret.push(:physical_wall) if pkmn.ev[:DEFENSE] == Pokemon::EV_STAT_LIMIT - elsif pkmn.nature.stat_changes.any? { |change| change[0] == :SPECIAL_DEFENSE && change[1] > 0 } && - !pkmn.nature.stat_changes.any? { |change| change[0] == :SPECIAL_DEFENSE && change[1] < 0 } - ret.push(:special_wall) if pkmn.ev[:SPECIAL_DEFENSE] == Pokemon::EV_STAT_LIMIT - end - else - ret.push(:tank) if pkmn.ev[:HP] == Pokemon::EV_STAT_LIMIT - end - # Check for abilities indicative of particular roles - case pkmn.ability_id - when :REGENERATOR - ret.push(:pivot) - when :GUTS, :QUICKFEET, :FLAREBOOST, :TOXICBOOST, :NATURALCURE, :MAGICGUARD, - :MAGICBOUNCE, :HYDRATION - ret.push(:status_absorber) - when :SHADOWTAG, :ARENATRAP, :MAGNETPULL - ret.push(:trapper) - when :DROUGHT, :DRIZZLE, :SANDSTREAM, :SNOWWARNING, :PRIMORDIALSEA, - :DESOLATELAND, :DELTASTREAM - ret.push(:weather_setter) - when :GRASSYSURGE, :ELECTRICSURGE, :MISTYSURGE, :PSYCHICSURGE - ret.push(:field_setter) - end - # Check for items indicative of particular roles - case pkmn.item_id - when :LIGHTCLAY - ret.push(:screener) - when :ASSAULTVEST - ret.push(:tank) - when :CHOICEBAND, :CHOICESPECS - ret.push(:stall_breaker) - ret.push(:sweeper) if pkmn.ev[:SPEED] == Pokemon::EV_STAT_LIMIT - when :CHOICESCARF - ret.push(:sweeper) if pkmn.ev[:SPEED] == Pokemon::EV_STAT_LIMIT - when :TOXICORB, :FLAMEORB - ret.push(:status_absorber) - when :TERRAINEXTENDER - ret.push(:field_setter) - end - # Check for position in team, level relative to other levels in team - partyStarts = @battle.pbPartyStarts(side) - if partyStarts.include?(index + 1) || index == @battle.pbParty(side).length - 1 - ret.push(:ace) # Last in party, assumed to be the best Pokémon - else - ret.push(:lead) if partyStarts.include?(index) # First in party - idxTrainer = @battle.pbGetOwnerIndexFromPartyIndex(side, index) - maxLevel = @battle.pbMaxLevelInTeam(side, idxTrainer) - if pkmn.level >= maxLevel - ret.push(:second) - else - secondHighest = true - seenHigherLevel = false - @battle.eachInTeam(side, @battle.pbGetOwnerIndexFromPartyIndex(side, index)) do |p| - next if p.level < pkmn.level - if seenHigherLevel - secondHighest = false - break - end - seenHigherLevel = true - end - # NOTE: There can be multiple "second"s if all their levels are equal - # and the highest in the team (and none are the ace). - ret.push(:second) if secondHighest - end - end - return ret - end - - def check_role(side, idxBattler, *roles) - role_array = @roles[side][idxBattler] - roles.each do |r| - return true if role_array.include?(r) - end - return false - end - - def check_battler_role(battler, *roles) - side = idxParty.idxOwnSide - idxParty = idxParty.pokemonIndex - return check_role(side, idxParty, *roles) - end -end diff --git a/Data/Scripts/011_Battle/005_AI/011_AIBattler.rb b/Data/Scripts/011_Battle/005_AI/011_AIBattler.rb index c8b4b0d54..62d23688c 100644 --- a/Data/Scripts/011_Battle/005_AI/011_AIBattler.rb +++ b/Data/Scripts/011_Battle/005_AI/011_AIBattler.rb @@ -16,10 +16,6 @@ class Battle::AI::AIBattler old_party_index = @party_index @battler = @ai.battle.battlers[@index] @party_index = battler.pokemonIndex - if @party_index != old_party_index - # TODO: Start of battle or Pokémon switched/shifted; recalculate roles, - # etc. What is etc.? - end end def pokemon; return battler.pokemon; end @@ -232,12 +228,7 @@ class Battle::AI::AIBattler end alias pbHasType? has_type? - # TODO: Also make a def effectiveness_of_move_against_battler which calls - # pbCalcTypeModSingle instead of effectiveness_of_type_against_single_battler_type, - # for moves with custom def pbCalcTypeMod, e.g. Freeze-Dry. When would - # that be used instead? Or rather, when would THIS method be used if - # that existed? - def effectiveness_of_type_against_battler(type, user = nil) + def effectiveness_of_type_against_battler(type, user = nil, move = nil) ret = Effectiveness::NORMAL_EFFECTIVE_MULTIPLIER return ret if !type return ret if type == :GROUND && has_type?(:FLYING) && has_active_item?(:IRONBALL) @@ -250,7 +241,16 @@ class Battle::AI::AIBattler end else battler.pbTypes(true).each do |defend_type| - ret *= effectiveness_of_type_against_single_battler_type(type, defend_type, user) + mult = effectiveness_of_type_against_single_battler_type(type, defend_type, user) + if move + case move.function + when "HitsTargetInSkyGroundsTarget" + mult = Effectiveness::NORMAL_EFFECTIVE_MULTIPLIER if type == :GROUND && defend_type == :FLYING + when "FreezeTargetSuperEffectiveAgainstWater" + mult = Effectiveness::SUPER_EFFECTIVE_MULTIPLIER if defend_type == :WATER + end + end + ret *= mult end ret *= 2 if self.effects[PBEffects::TarShot] && type == :FIRE end @@ -414,7 +414,9 @@ class Battle::AI::AIBattler break end # Modify the rating based on ability-specific contexts - ret = Battle::AI::Handlers.modify_ability_ranking(ability, ret, self, @ai) + if @ai.trainer.medium_skill? + ret = Battle::AI::Handlers.modify_ability_ranking(ability, ret, self, @ai) + end return ret end @@ -424,13 +426,12 @@ class Battle::AI::AIBattler # battler if it is holding it. # Return values are typically between -10 and +10. 0 is indifferent, positive # values mean this battler benefits, negative values mean this battler suffers. - # TODO: This method shouldn't check for item effect negation, to work the same - # as def wants_ability?. + # NOTE: This method assumes the item isn't being negated. The calculations + # that call this method separately check for it being negated, because + # they need to do something special in that case. def wants_item?(item) item = :NONE if !item item = item.id if !item.is_a?(Symbol) && item.respond_to?("id") - return 0 if has_active_ability?(:KLUTZ) - # TODO: Unnerve, other item-negating effects. # Get the base item rating ret = 0 Battle::AI::BASE_ITEM_RATINGS.each_pair do |val, items| @@ -439,7 +440,9 @@ class Battle::AI::AIBattler break end # Modify the rating based on item-specific contexts - ret = Battle::AI::Handlers.modify_item_ranking(item, ret, self, @ai) + if @ai.trainer.medium_skill? + ret = Battle::AI::Handlers.modify_item_ranking(item, ret, self, @ai) + end # Prefer if this battler knows Fling and it will do a lot of damage/have an # additional (negative) effect when flung if item != :NONE && has_move_with_function?("ThrowUserItemAtTarget") @@ -481,7 +484,18 @@ class Battle::AI::AIBattler end ret += (hp > totalhp * (1 - (1.0 / fraction_to_heal))) ? -6 : 6 ret = ret * 3 / 2 if GameData::Item.get(item).is_berry? && has_active_ability?(:RIPEN) - # TODO: Check whether the item will cause confusion? + if @ai.trainer.high_skill? + flavor_stat = { + :AGUAVBERRY => :SPECIAL_DEFENSE, + :FIGYBERRY => :ATTACK, + :IAPAPABERRY => :DEFENSE, + :MAGOBERRY => :SPEED, + :WIKIBERRY => :SPECIAL_ATTACK + }[item] + if @battler.nature.stat_changes.any? { |val| val[0] == flavor_stat && val[1] < 0 } + ret -= 3 if @battler.pbCanConfuseSelf?(false) + end + end when :ASPEARBERRY, :CHERIBERRY, :CHESTOBERRY, :PECHABERRY, :RAWSTBERRY # Status cure cured_status = { diff --git a/Data/Scripts/011_Battle/005_AI/012_AIMove.rb b/Data/Scripts/011_Battle/005_AI/012_AIMove.rb index bd6346b03..2759be870 100644 --- a/Data/Scripts/011_Battle/005_AI/012_AIMove.rb +++ b/Data/Scripts/011_Battle/005_AI/012_AIMove.rb @@ -101,7 +101,6 @@ class Battle::AI::AIMove # Get the move's type calc_type = rough_type # Decide whether the move has 50% chance of higher of being a critical hit - # TODO: Make this a gradient/probability rather than all-or-nothing? crit_stage = rough_critical_hit_stage is_critical = crit_stage >= Battle::Move::CRITICAL_HIT_RATIOS.length || Battle::Move::CRITICAL_HIT_RATIOS[crit_stage] <= 2 @@ -325,7 +324,7 @@ class Battle::AI::AIMove end end # Type effectiveness - typemod = target.effectiveness_of_type_against_battler(calc_type, user) + typemod = target.effectiveness_of_type_against_battler(calc_type, user, @move) multipliers[:final_damage_multiplier] *= typemod # Burn if @ai.trainer.high_skill? && user.status == :BURN && physicalMove?(calc_type) && diff --git a/Data/Scripts/011_Battle/006_AI MoveEffects/002_AI_MoveEffects_BattlerStats.rb b/Data/Scripts/011_Battle/006_AI MoveEffects/002_AI_MoveEffects_BattlerStats.rb index adcb16044..23dfc3bc8 100644 --- a/Data/Scripts/011_Battle/006_AI MoveEffects/002_AI_MoveEffects_BattlerStats.rb +++ b/Data/Scripts/011_Battle/006_AI MoveEffects/002_AI_MoveEffects_BattlerStats.rb @@ -494,10 +494,6 @@ Battle::AI::Handlers::MoveEffectScore.add("RaiseUserMainStats1TrapUserInBattle", next (move.damagingMove?) ? score : Battle::AI::MOVE_USELESS_SCORE end # Score for user becoming trapped in battle - # TODO: These checks are related to desire to switch, and there can be a lot - # more things to consider, e.g. effectiveness of the target's moves - # against its foes. Also applies to other code that calls - # can_become_trapped? if user.effects[PBEffects::PerishSong] > 0 || user.effects[PBEffects::Attract] >= 0 || eor_damage > 0 @@ -632,7 +628,7 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("RaiseTargetAttack2Confu ) Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("RaiseTargetAttack2ConfuseTarget", proc { |score, move, user, target, ai, battle| - if !target.has_active_ability?(:CONTRARY) || @battle.moldBreaker + if !target.has_active_ability?(:CONTRARY) || battle.moldBreaker next Battle::AI::MOVE_USELESS_SCORE if !target.battler.pbCanConfuse?(user.battler, false, move.move) end # Score for stat raise @@ -654,7 +650,7 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("RaiseTargetSpAtk1Confus ) Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("RaiseTargetSpAtk1ConfuseTarget", proc { |score, move, user, target, ai, battle| - if !target.has_active_ability?(:CONTRARY) || @battle.moldBreaker + if !target.has_active_ability?(:CONTRARY) || battle.moldBreaker next Battle::AI::MOVE_USELESS_SCORE if !target.battler.pbCanConfuse?(user.battler, false, move.move) end # Score for stat raise diff --git a/Data/Scripts/011_Battle/006_AI MoveEffects/003_AI_MoveEffects_BattlerOther.rb b/Data/Scripts/011_Battle/006_AI MoveEffects/003_AI_MoveEffects_BattlerOther.rb index c742c57fb..bdf24df0d 100644 --- a/Data/Scripts/011_Battle/006_AI MoveEffects/003_AI_MoveEffects_BattlerOther.rb +++ b/Data/Scripts/011_Battle/006_AI MoveEffects/003_AI_MoveEffects_BattlerOther.rb @@ -240,7 +240,7 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("ParalyzeTarget", #=============================================================================== Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("ParalyzeTargetIfNotTypeImmune", proc { |move, user, target, ai, battle| - eff = target.effectiveness_of_type_against_battler(move.rough_type, user) + eff = target.effectiveness_of_type_against_battler(move.rough_type, user, move) next true if Effectiveness.ineffective?(eff) next true if move.statusMove? && !target.battler.pbCanParalyze?(user.battler, false, move.move) next false diff --git a/Data/Scripts/011_Battle/006_AI MoveEffects/004_AI_MoveEffects_MoveAttributes.rb b/Data/Scripts/011_Battle/006_AI MoveEffects/004_AI_MoveEffects_MoveAttributes.rb index cd1e274db..56048e61c 100644 --- a/Data/Scripts/011_Battle/006_AI MoveEffects/004_AI_MoveEffects_MoveAttributes.rb +++ b/Data/Scripts/011_Battle/006_AI MoveEffects/004_AI_MoveEffects_MoveAttributes.rb @@ -1094,8 +1094,6 @@ Battle::AI::Handlers::MoveEffectScore.add("ProtectUserSideFromPriorityMoves", useless = true ai.each_foe_battler(user.side) do |b, i| next if !b.can_attack? - # TODO: There are more calculations that could be done to get a more - # accurate priority number. next if !b.check_for_move { |m| m.pbPriority(b.battler) > 0 && m.canProtectAgainst? } useless = false # General preference @@ -1343,9 +1341,6 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("EnsureNextMoveAlwaysHits # Prefer if the user knows moves with low accuracy user.battler.eachMove do |m| next if target.effects[PBEffects::Minimize] && m.tramplesMinimize? && Settings::MECHANICS_GENERATION >= 6 - # TODO: There are other effects that make a move certain to hit. Account - # for those as well. Score this move useless if no moves would - # benefit from locking on. acc = m.accuracy acc = m.pbBaseAccuracy(user.battler, target.battler) if ai.trainer.medium_skill? score += 5 if acc < 90 && acc != 0 @@ -1503,7 +1498,7 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TargetMovesBecomeElectri next if !m.damagingMove? m_type = m.pbCalcType(target.battler) next if m_type == :ELECTRIC - eff = user.effectiveness_of_type_against_battler(m_type, target) + eff = user.effectiveness_of_type_against_battler(m_type, target, m) eff *= 1.5 if target.has_type?(m_type) # STAB case m_type when :FIRE diff --git a/Data/Scripts/011_Battle/006_AI MoveEffects/005_AI_MoveEffects_MultiHit.rb b/Data/Scripts/011_Battle/006_AI MoveEffects/005_AI_MoveEffects_MultiHit.rb index 914788498..525a74678 100644 --- a/Data/Scripts/011_Battle/006_AI MoveEffects/005_AI_MoveEffects_MultiHit.rb +++ b/Data/Scripts/011_Battle/006_AI MoveEffects/005_AI_MoveEffects_MultiHit.rb @@ -451,6 +451,7 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("TwoTurnAttackInvulnerab proc { |move, user, target, ai, battle| next true if !target.opposes?(user) next true if target.effects[PBEffects::Substitute] > 0 && !move.move.ignoresSubstitute?(user.battler) + next true if target.has_type?(:FLYING) next true if Settings::MECHANICS_GENERATION >= 6 && target.battler.pbWeight >= 2000 # 200.0kg next true if target.battler.semiInvulnerable? || target.effects[PBEffects::SkyDrop] >= 0 next false diff --git a/Data/Scripts/011_Battle/006_AI MoveEffects/007_AI_MoveEffects_Items.rb b/Data/Scripts/011_Battle/006_AI MoveEffects/007_AI_MoveEffects_Items.rb index 99b130279..000f65216 100644 --- a/Data/Scripts/011_Battle/006_AI MoveEffects/007_AI_MoveEffects_Items.rb +++ b/Data/Scripts/011_Battle/006_AI MoveEffects/007_AI_MoveEffects_Items.rb @@ -11,10 +11,14 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("UserTakesTargetItem", # User can steal the target's item; score it user_item_preference = user.wants_item?(target.item_id) user_no_item_preference = user.wants_item?(:NONE) + user_diff = user_item_preference - user_no_item_preference + user_diff = 0 if !user.item_active? target_item_preference = target.wants_item?(target.item_id) target_no_item_preference = target.wants_item?(:NONE) - score += user_item_preference - user_no_item_preference - score += target_item_preference - target_no_item_preference + target_diff = target_no_item_preference - target_item_preference + target_diff = 0 if !target.item_active? + score += user_diff * 4 + score -= target_diff * 4 next score } ) @@ -33,10 +37,14 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TargetTakesUserItem", proc { |score, move, user, target, ai, battle| user_item_preference = user.wants_item?(user.item_id) user_no_item_preference = user.wants_item?(:NONE) + user_diff = user_no_item_preference - user_item_preference + user_diff = 0 if !user.item_active? target_item_preference = target.wants_item?(user.item_id) target_no_item_preference = target.wants_item?(:NONE) - score += user_no_item_preference - user_item_preference - score += target_no_item_preference - target_item_preference + target_diff = target_item_preference - target_no_item_preference + target_diff = 0 if !target.item_active? + score += user_diff * 4 + score -= target_diff * 4 next score } ) @@ -58,10 +66,14 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("UserTargetSwapItems", proc { |score, move, user, target, ai, battle| user_new_item_preference = user.wants_item?(target.item_id) user_old_item_preference = user.wants_item?(user.item_id) + user_diff = user_new_item_preference - user_old_item_preference + user_diff = 0 if !user.item_active? target_new_item_preference = target.wants_item?(user.item_id) target_old_item_preference = target.wants_item?(target.item_id) - score += user_new_item_preference - user_old_item_preference - score += target_old_item_preference - target_new_item_preference + target_diff = target_new_item_preference - target_old_item_preference + target_diff = 0 if !target.item_active? + score += user_diff * 4 + score -= target_diff * 4 # Don't prefer if user used this move in the last round score -= 15 if user.battler.lastMoveUsed && GameData::Move.exists?(user.battler.lastMoveUsed) && @@ -80,9 +92,10 @@ Battle::AI::Handlers::MoveFailureCheck.add("RestoreUserConsumedItem", ) Battle::AI::Handlers::MoveEffectScore.add("RestoreUserConsumedItem", proc { |score, move, user, ai, battle| - user_new_item_preference = user.wants_item?(user.battler.recycleItem) - user_old_item_preference = user.wants_item?(:NONE) - score += (user_new_item_preference - user_old_item_preference) * 2 + next Battle::AI::MOVE_USELESS_SCORE if !user.item_active? + item_preference = user.wants_item?(user.battler.recycleItem) + no_item_preference = user.wants_item?(:NONE) + score += (item_preference - no_item_preference) * 4 next score } ) @@ -101,10 +114,11 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("RemoveTargetItem", next score if !target.item || target.battler.unlosableItem?(target.item) next score if target.effects[PBEffects::Substitute] > 0 next score if target.has_active_ability?(:STICKYHOLD) && !battle.moldBreaker + next score if !target.item_active? # User can knock off the target's item; score it - target_item_preference = target.wants_item?(target.item_id) - target_no_item_preference = target.wants_item?(:NONE) - score += (target_item_preference - target_no_item_preference) * 2 + item_preference = target.wants_item?(target.item_id) + no_item_preference = target.wants_item?(:NONE) + score -= (no_item_preference - item_preference) * 4 next score } ) @@ -119,10 +133,11 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("DestroyTargetBerryOrGem" next score if user.battler.unlosableItem?(target.item) next score if target.effects[PBEffects::Substitute] > 0 next score if target.has_active_ability?(:STICKYHOLD) && !battle.moldBreaker + next score if !target.item_active? # User can incinerate the target's item; score it - target_item_preference = target.wants_item?(target.item_id) - target_no_item_preference = target.wants_item?(:NONE) - score += (target_item_preference - target_no_item_preference) * 2 + item_preference = target.wants_item?(target.item_id) + no_item_preference = target.wants_item?(:NONE) + score -= (no_item_preference - item_preference) * 4 next score } ) @@ -141,9 +156,11 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("CorrodeTargetItem", ) Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("CorrodeTargetItem", proc { |score, move, user, target, ai, battle| - target_item_preference = target.wants_item?(target.item_id) - target_no_item_preference = target.wants_item?(:NONE) - score += (target_item_preference - target_no_item_preference) * 2 + item_preference = target.wants_item?(target.item_id) + no_item_preference = target.wants_item?(:NONE) + target_diff = no_item_preference - item_preference + target_diff = 0 if !target.item_active? + score += target_diff * 4 next score } ) @@ -159,7 +176,8 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("StartTargetCannotUseIte Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("StartTargetCannotUseItem", proc { |score, move, user, target, ai, battle| next Battle::AI::MOVE_USELESS_SCORE if !target.item || !target.item_active? - # TODO: Useless if target's item cannot be negated. + # NOTE: We won't check if the item has an effect, because if a Pokémon is + # holding an item, it probably does. item_score = target.wants_item?(target.item_id) next Battle::AI::MOVE_USELESS_SCORE if item_score <= 0 # Item has no effect or is bad score += item_score * 2 @@ -179,7 +197,8 @@ Battle::AI::Handlers::MoveEffectScore.add("StartNegateHeldItems", next if !b.item # Skip b if its item is disabled if ai.trainer.medium_skill? - # TODO: Skip b if its item cannot be negated or it has no effect. + # NOTE: We won't check if the item has an effect, because if a Pokémon + # is holding an item, it probably does. if battle.field.effects[PBEffects::MagicRoom] > 0 # NOTE: Same as b.item_active? but ignoring the Magic Room part. next if b.effects[PBEffects::Embargo] > 0 @@ -297,9 +316,9 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("UserConsumeTargetBerry", score -= 5 if target.has_active_ability?(:UNBURDEN) end # Score the target no longer having the item - target_item_preference = target.wants_item?(target.item_id) - target_no_item_preference = target.wants_item?(:NONE) - score += (target_item_preference - target_no_item_preference) * 2 + item_preference = target.wants_item?(target.item_id) + no_item_preference = target.wants_item?(:NONE) + score -= (no_item_preference - item_preference) * 3 next score } ) @@ -349,9 +368,9 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("ThrowUserItemAtTarget", end # Prefer if the user doesn't want its held item/don't prefer if it wants to # keep its held item - user_item_preference = user.wants_item?(user.item_id) - user_no_item_preference = user.wants_item?(:NONE) - score += (user_item_preference - user_no_item_preference) * 2 + item_preference = user.wants_item?(user.item_id) + no_item_preference = user.wants_item?(:NONE) + score += (no_item_preference - item_preference) * 2 next score } ) diff --git a/Data/Scripts/011_Battle/006_AI MoveEffects/009_AI_MoveEffects_SwitchingActing.rb b/Data/Scripts/011_Battle/006_AI MoveEffects/009_AI_MoveEffects_SwitchingActing.rb index b1b5a79ff..6c52a3008 100644 --- a/Data/Scripts/011_Battle/006_AI MoveEffects/009_AI_MoveEffects_SwitchingActing.rb +++ b/Data/Scripts/011_Battle/006_AI MoveEffects/009_AI_MoveEffects_SwitchingActing.rb @@ -3,7 +3,7 @@ #=============================================================================== Battle::AI::Handlers::MoveFailureCheck.add("FleeFromBattle", proc { |move, user, ai, battle| - next !battle.pbCanRun?(user.index) + next !battle.pbCanRun?(user.index) || (user.wild? && user.battler.allAllies.length > 0) } ) Battle::AI::Handlers::MoveEffectScore.add("FleeFromBattle", @@ -14,35 +14,50 @@ Battle::AI::Handlers::MoveEffectScore.add("FleeFromBattle", ) #=============================================================================== -# TODO: Review score modifiers. +# #=============================================================================== Battle::AI::Handlers::MoveFailureCheck.add("SwitchOutUserStatusMove", proc { |move, user, ai, battle| - next !battle.pbCanRun?(user.index) if user.wild? + if user.wild? + next !battle.pbCanRun?(user.index) || user.battler.allAllies.length > 0 + end next !battle.pbCanChooseNonActive?(user.index) } ) Battle::AI::Handlers::MoveEffectScore.add("SwitchOutUserStatusMove", proc { |score, move, user, ai, battle| - # Wild Pokémon run from battle + # Wild Pokémon run from battle - generally don't prefer (don't want to end the battle too easily) next score - 20 if user.wild? # Trainer-owned Pokémon switch out if ai.trainer.has_skill_flag?("ReserveLastPokemon") && battle.pbTeamAbleNonActiveCount(user.index) == 1 next Battle::AI::MOVE_USELESS_SCORE # Don't switch in ace end # Prefer if the user switching out will lose a negative effect + score += 20 if user.effects[PBEffects::PerishSong] > 0 score += 10 if user.effects[PBEffects::Confusion] > 1 + score += 10 if user.effects[PBEffects::Attract] >= 0 + # Consider the user's stat stages + if user.stages.any? { |key, val| val >= 2 } + score -= 15 + elsif user.stages.any? { |key, val| val < 0 } + score += 10 + end + # Consider the user's end of round damage/healing + eor_damage = user.rough_end_of_round_damage + score += 15 if eor_damage > 0 + score -= 15 if eor_damage < 0 # Prefer if the user doesn't have any damaging moves - # TODO: Check effectiveness of moves. - score += 15 if !user.check_for_move { |m| m.damagingMove? } - # Don't prefer the more stat raises the user has - GameData::Stat.each_battle { |s| score -= user.stages[s.id] * 5 } + score += 10 if !user.check_for_move { |m| m.damagingMove? } + # Don't prefer if the user's side has entry hazards on it + score -= 10 if user.pbOwnSide.effects[PBEffects::Spikes] > 0 + score -= 10 if user.pbOwnSide.effects[PBEffects::ToxicSpikes] > 0 + score -= 10 if user.pbOwnSide.effects[PBEffects::StealthRock] next score } ) #=============================================================================== -# TODO: Review score modifiers. +# #=============================================================================== Battle::AI::Handlers::MoveEffectScore.add("SwitchOutUserDamagingMove", proc { |score, move, user, ai, battle| @@ -51,16 +66,29 @@ Battle::AI::Handlers::MoveEffectScore.add("SwitchOutUserDamagingMove", score -= 20 if ai.trainer.has_skill_flag?("ReserveLastPokemon") && battle.pbTeamAbleNonActiveCount(user.index) == 1 # Prefer if the user switching out will lose a negative effect + score += 20 if user.effects[PBEffects::PerishSong] > 0 score += 10 if user.effects[PBEffects::Confusion] > 1 - # Don't prefer the more stat raises the user has - GameData::Stat.each_battle { |s| score -= user.stages[s.id] * 5 } + score += 10 if user.effects[PBEffects::Attract] >= 0 + # Consider the user's stat stages + if user.stages.any? { |key, val| val >= 2 } + score -= 15 + elsif user.stages.any? { |key, val| val < 0 } + score += 10 + end + # Consider the user's end of round damage/healing + eor_damage = user.rough_end_of_round_damage + score += 15 if eor_damage > 0 + score -= 15 if eor_damage < 0 + # Don't prefer if the user's side has entry hazards on it + score -= 10 if user.pbOwnSide.effects[PBEffects::Spikes] > 0 + score -= 10 if user.pbOwnSide.effects[PBEffects::ToxicSpikes] > 0 + score -= 10 if user.pbOwnSide.effects[PBEffects::StealthRock] next score } ) #=============================================================================== -# TODO: Review score modifiers. -# TODO: Might need both MoveEffectScore and MoveEffectAgainstTargetScore. +# #=============================================================================== Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("LowerTargetAtkSpAtk1SwitchOutUser", proc { |move, user, target, ai, battle| @@ -75,25 +103,14 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("LowerTargetAtkSpAtk1Swi ) Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("LowerTargetAtkSpAtk1SwitchOutUser", proc { |score, move, user, target, ai, battle| - score = ai.get_score_for_target_stat_drop(score, target, move.move.statDown, false) - if battle.pbCanChooseNonActive?(user.index) - # Don't want to switch in ace - score -= 20 if ai.trainer.has_skill_flag?("ReserveLastPokemon") && - battle.pbTeamAbleNonActiveCount(user.index) == 1 - # Prefer if the user switching out will lose a negative effect - score += 10 if user.effects[PBEffects::Confusion] > 1 - # Prefer if the user doesn't have any damaging moves - # TODO: Check effectiveness of moves. - score += 15 if !user.check_for_move { |m| m.damagingMove? } - # Don't prefer the more stat raises the user has - GameData::Stat.each_battle { |s| score -= user.stages[s.id] * 5 } - end - next score + next ai.get_score_for_target_stat_drop(score, target, move.move.statDown, false) } ) +Battle::AI::Handlers::MoveEffectAgainstTargetScore.copy("SwitchOutUserDamagingMove", + "LowerTargetAtkSpAtk1SwitchOutUser") #=============================================================================== -# TODO: Review score modifiers. +# #=============================================================================== Battle::AI::Handlers::MoveFailureCheck.add("SwitchOutUserPassOnEffects", proc { |move, user, ai, battle| @@ -107,56 +124,110 @@ Battle::AI::Handlers::MoveEffectScore.add("SwitchOutUserPassOnEffects", battle.pbTeamAbleNonActiveCount(user.index) == 1 # Don't prefer if the user will pass on a negative effect score -= 10 if user.effects[PBEffects::Confusion] > 1 + score -= 15 if user.effects[PBEffects::Curse] + score -= 10 if user.effects[PBEffects::Embargo] > 1 + score -= 15 if user.effects[PBEffects::GastroAcid] + score -= 10 if user.effects[PBEffects::HealBlock] > 1 + score -= 10 if user.effects[PBEffects::LeechSeed] >= 0 + score -= 20 if user.effects[PBEffects::PerishSong] > 0 + # Prefer if the user will pass on a positive effect + score += 10 if user.effects[PBEffects::AquaRing] + score += 10 if user.effects[PBEffects::FocusEnergy] > 0 + score += 10 if user.effects[PBEffects::Ingrain] + score += 8 if user.effects[PBEffects::MagnetRise] > 1 + score += 10 if user.effects[PBEffects::Substitute] > 0 + # Consider the user's stat stages + if user.stages.any? { |key, val| val >= 4 } + score += 25 + elsif user.stages.any? { |key, val| val >= 2 } + score += 15 + elsif user.stages.any? { |key, val| val < 0 } + score -= 15 + end + # Consider the user's end of round damage/healing + eor_damage = user.rough_end_of_round_damage + score += 15 if eor_damage > 0 + score -= 15 if eor_damage < 0 # Prefer if the user doesn't have any damaging moves - # TODO: Check effectiveness of moves. score += 15 if !user.check_for_move { |m| m.damagingMove? } - # Prefer if the user will pass on good stat stages - GameData::Stat.each_battle { |s| score += user.stages[s.id] * 5 } + # Don't prefer if the user's side has entry hazards on it + score -= 10 if user.pbOwnSide.effects[PBEffects::Spikes] > 0 + score -= 10 if user.pbOwnSide.effects[PBEffects::ToxicSpikes] > 0 + score -= 10 if user.pbOwnSide.effects[PBEffects::StealthRock] next score } ) #=============================================================================== -# TODO: Review score modifiers. +# #=============================================================================== Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("SwitchOutTargetStatusMove", proc { |move, user, target, ai, battle| - next true if (!battle.moldBreaker && target.has_active_ability?(:SUCTIONCUPS)) || - target.effects[PBEffects::Ingrain] - next true if !battle.canRun - next true if battle.wildBattle? && target.level > user.level - if battle.trainerBattle? - will_fail = true - battle.eachInTeamFromBattlerIndex(target.index) do |_pkmn, i| - next if !battle.pbCanSwitchIn?(target.index, i) - will_fail = false - break - end - next will_fail - end - next false + next move.move.pbFailsAgainstTarget?(user.battler, target.battler, false) } ) Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("SwitchOutTargetStatusMove", proc { |score, move, user, target, ai, battle| - score += 15 if target.pbOwnSide.effects[PBEffects::Spikes] > 0 - score += 15 if target.pbOwnSide.effects[PBEffects::ToxicSpikes] > 0 - score += 15 if target.pbOwnSide.effects[PBEffects::StealthRock] + # Ends the battle - generally don't prefer (don't want to end the battle too easily) + next score - 10 if target.wild? + # Switches the target out + next Battle::AI::MOVE_USELESS_SCORE if target.effects[PBEffects::PerishSong] > 0 + # Don't prefer if target is at low HP and could be knocked out instead + if ai.trainer.has_skill_flag?("HPAware") + score -= 10 if target.hp <= target.totalhp / 3 + end + # Consider the target's stat stages + if target.stages.any? { |key, val| val >= 2 } + score += 15 + elsif target.stages.any? { |key, val| val < 0 } + score -= 15 + end + # Consider the target's end of round damage/healing + eor_damage = target.rough_end_of_round_damage + score -= 15 if eor_damage > 0 + score += 15 if eor_damage < 0 + # Prefer if the target's side has entry hazards on it + score += 10 if target.pbOwnSide.effects[PBEffects::Spikes] > 0 + score += 10 if target.pbOwnSide.effects[PBEffects::ToxicSpikes] > 0 + score += 10 if target.pbOwnSide.effects[PBEffects::StealthRock] next score } ) #=============================================================================== -# TODO: Review score modifiers. +# #=============================================================================== Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("SwitchOutTargetDamagingMove", proc { |score, move, user, target, ai, battle| - if (battle.moldBreaker || !target.has_active_ability?(:SUCTIONCUPS)) && - !target.effects[PBEffects::Ingrain] - score += 15 if target.pbOwnSide.effects[PBEffects::Spikes] > 0 - score += 15 if target.pbOwnSide.effects[PBEffects::ToxicSpikes] > 0 - score += 15 if target.pbOwnSide.effects[PBEffects::StealthRock] + next score if target.wild? + # No score modification if the target can't be made to switch out + next score if !battle.moldBreaker && target.has_active_ability?(:SUCTIONCUPS) + next score if target.effects[PBEffects::Ingrain] + # No score modification if the target can't be replaced + can_switch = false + battle.eachInTeamFromBattlerIndex(target.index) do |_pkmn, i| + can_switch = battle.pbCanSwitchIn?(target.index, i) + break if can_switch end + next score if !can_switch + # Not score modification if the target has a Substitute + next score if target.effects[PBEffects::Substitute] > 0 + # Don't want to switch out the target if it will faint from Perish Song + score -= 20 if target.effects[PBEffects::PerishSong] > 0 + # Consider the target's stat stages + if target.stages.any? { |key, val| val >= 2 } + score += 15 + elsif target.stages.any? { |key, val| val < 0 } + score -= 15 + end + # Consider the target's end of round damage/healing + eor_damage = target.rough_end_of_round_damage + score -= 15 if eor_damage > 0 + score += 15 if eor_damage < 0 + # Prefer if the target's side has entry hazards on it + score += 10 if target.pbOwnSide.effects[PBEffects::Spikes] > 0 + score += 10 if target.pbOwnSide.effects[PBEffects::ToxicSpikes] > 0 + score += 10 if target.pbOwnSide.effects[PBEffects::StealthRock] next score } ) @@ -242,12 +313,9 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TrapTargetInBattle", next score if add_effect == -999 # Additional effect will be negated score += add_effect # Score for target becoming trapped in battle - # TODO: These checks are related to desire to switch, and there can be a lot - # more things to consider, e.g. effectiveness of the target's moves - # against its foes. Also applies to other code that calls - # can_become_trapped? if target.effects[PBEffects::PerishSong] > 0 || target.effects[PBEffects::Attract] >= 0 || + target.effects[PBEffects::Confusion] > 0 || eor_damage > 0 score += 15 end @@ -290,12 +358,9 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TrapTargetInBattleLowerT next (move.damagingMove?) ? score : Battle::AI::MOVE_USELESS_SCORE end # Score for target becoming trapped in battle - # TODO: These checks are related to desire to switch, and there can be a lot - # more things to consider, e.g. effectiveness of the target's moves - # against its foes. Also applies to other code that calls - # can_become_trapped? if target.effects[PBEffects::PerishSong] > 0 || target.effects[PBEffects::Attract] >= 0 || + target.effects[PBEffects::Confusion] > 0 || eor_damage > 0 score += 15 end @@ -324,12 +389,9 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TrapUserAndTargetInBattl next (move.damagingMove?) ? score : Battle::AI::MOVE_USELESS_SCORE end # Score for target becoming trapped in battle - # TODO: These checks are related to desire to switch, and there can be a lot - # more things to consider, e.g. effectiveness of the target's moves - # against its foes. Also applies to other code that calls - # can_become_trapped? if target.effects[PBEffects::PerishSong] > 0 || target.effects[PBEffects::Attract] >= 0 || + target.effects[PBEffects::Confusion] > 0 || eor_damage > 0 score += 15 end @@ -339,16 +401,22 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TrapUserAndTargetInBattl ) #=============================================================================== -# TODO: Review score modifiers. +# #=============================================================================== Battle::AI::Handlers::MoveFailureCheck.add("TrapAllBattlersInBattleForOneTurn", proc { |move, user, ai, battle| next battle.field.effects[PBEffects::FairyLock] > 0 } ) +Battle::AI::Handlers::MoveEffectScore.add("TrapAllBattlersInBattleForOneTurn", + proc { |score, move, user, ai, battle| + # Trapping for just one turn isn't so significant, so generally don't prefer + next score - 10 + } +) #=============================================================================== -# TODO: Review score modifiers. +# #=============================================================================== # PursueSwitchingFoe @@ -661,7 +729,6 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("DisableTargetUsingDiffer if move_data.status? # Prefer encoring status moves if [:User, :BothSides].include?(move_data.target) - # TODO: This target distinction was in the old code. Is it appropriate? score += 10 else score += 8 diff --git a/Data/Scripts/011_Battle/007_Other battle code/009_Battle_ItemEffects.rb b/Data/Scripts/011_Battle/007_Other battle code/009_Battle_ItemEffects.rb index 5c1e69dfd..2f13adc91 100644 --- a/Data/Scripts/011_Battle/007_Other battle code/009_Battle_ItemEffects.rb +++ b/Data/Scripts/011_Battle/007_Other battle code/009_Battle_ItemEffects.rb @@ -242,8 +242,7 @@ Battle::ItemEffects::SpeedCalc.copy(:MACHOBRACE, :POWERANKLET, :POWERBAND, Battle::ItemEffects::SpeedCalc.add(:QUICKPOWDER, proc { |item, battler, mult| - next mult * 2 if battler.isSpecies?(:DITTO) && - !battler.effects[PBEffects::Transform] + next mult * 2 if battler.isSpecies?(:DITTO) && !battler.effects[PBEffects::Transform] } ) @@ -263,7 +262,7 @@ Battle::ItemEffects::WeightCalc.add(:FLOATSTONE, Battle::ItemEffects::HPHeal.add(:AGUAVBERRY, proc { |item, battler, battle, forced| - next battler.pbConfusionBerry(item, forced, 4, + next battler.pbConfusionBerry(item, forced, :SPECIAL_DEFENSE, _INTL("For {1}, the {2} was too bitter!", battler.pbThis(true), GameData::Item.get(item).name) ) } @@ -294,7 +293,7 @@ Battle::ItemEffects::HPHeal.add(:BERRYJUICE, Battle::ItemEffects::HPHeal.add(:FIGYBERRY, proc { |item, battler, battle, forced| - next battler.pbConfusionBerry(item, forced, 0, + next battler.pbConfusionBerry(item, forced, :ATTACK, _INTL("For {1}, the {2} was too spicy!", battler.pbThis(true), GameData::Item.get(item).name) ) } @@ -308,7 +307,7 @@ Battle::ItemEffects::HPHeal.add(:GANLONBERRY, Battle::ItemEffects::HPHeal.add(:IAPAPABERRY, proc { |item, battler, battle, forced| - next battler.pbConfusionBerry(item, forced, 1, + next battler.pbConfusionBerry(item, forced, :DEFENSE, _INTL("For {1}, the {2} was too sour!", battler.pbThis(true), GameData::Item.get(item).name) ) } @@ -338,7 +337,7 @@ Battle::ItemEffects::HPHeal.add(:LIECHIBERRY, Battle::ItemEffects::HPHeal.add(:MAGOBERRY, proc { |item, battler, battle, forced| - next battler.pbConfusionBerry(item, forced, 2, + next battler.pbConfusionBerry(item, forced, :SPEED, _INTL("For {1}, the {2} was too sweet!", battler.pbThis(true), GameData::Item.get(item).name) ) } @@ -436,7 +435,7 @@ Battle::ItemEffects::HPHeal.add(:STARFBERRY, Battle::ItemEffects::HPHeal.add(:WIKIBERRY, proc { |item, battler, battle, forced| - next battler.pbConfusionBerry(item, forced, 3, + next battler.pbConfusionBerry(item, forced, :SPECIAL_ATTACK, _INTL("For {1}, the {2} was too dry!", battler.pbThis(true), GameData::Item.get(item).name) ) }