class Battle::AI # Called by the AI's def pbDefaultChooseEnemyCommand, and by def pbChooseMove # if the only moves known are bad ones (the latter forces a switch). Also # aliased by the Battle Palace and Battle Arena. def pbChooseToSwitchOut(force_switch = false) return false if @user.wild? return false if !@battle.pbCanSwitchOut?(@user.index) # Don't switch if all foes are unable to do anything, e.g. resting after # Hyper Beam, will Truant (i.e. free turn) if @trainer.high_skill? foe_can_act = false each_foe_battler(@user.side) do |b, i| next if !b.can_attack? foe_can_act = true break end return false if !foe_can_act end # Various calculations to decide whether to switch if force_switch PBDebug.log_ai("#{@user.name} is being forced to switch out") else return false if !@trainer.has_skill_flag?("ConsiderSwitching") reserves = get_non_active_party_pokemon(@user.index) return false if reserves.empty? should_switch = Battle::AI::Handlers.should_switch?(@user, reserves, self, @battle) if should_switch && @trainer.medium_skill? should_switch = false if Battle::AI::Handlers.should_not_switch?(@user, reserves, self, @battle) end return false if !should_switch end # Want to switch; find the best replacement Pokémon idxParty = choose_best_replacement_pokemon(@user.index, force_switch) if idxParty < 0 # No good replacement Pokémon found PBDebug.log(" => no good replacement Pokémon, will not switch after all") return false end # Prefer using Baton Pass instead of switching baton_pass = -1 @user.battler.eachMoveWithIndex do |m, i| next if m.function != "SwitchOutUserPassOnEffects" # Baton Pass next if !@battle.pbCanChooseMove?(@user.index, i, false) baton_pass = i break end if baton_pass >= 0 && @battle.pbRegisterMove(@user.index, baton_pass, false) PBDebug.log(" => will use Baton Pass to switch out") return true elsif @battle.pbRegisterSwitch(@user.index, idxParty) PBDebug.log(" => will switch with #{@battle.pbParty(@user.index)[idxParty].name}") return true end return false end def get_non_active_party_pokemon(idxBattler) ret = [] @battle.pbParty(idxBattler).each_with_index do |pkmn, i| ret.push(pkmn) if @battle.pbCanSwitchIn?(idxBattler, i) end return ret end #----------------------------------------------------------------------------- # TODO: Need a way to allow a ShouldSwitch handler to recommend a replacement. def choose_best_replacement_pokemon(idxBattler, mandatory = false) # Get all possible replacement Pokémon party = @battle.pbParty(idxBattler) idxPartyStart, idxPartyEnd = @battle.pbTeamIndexRangeFromBattlerIndex(idxBattler) reserves = [] party.each_with_index do |_pkmn, i| next if !@battle.pbCanSwitchIn?(idxBattler, i) if !mandatory # Not mandatory means choosing an action for the round ally_will_switch_with_i = false @battle.allSameSideBattlers(idxBattler).each do |b| next if @battle.choices[b.index][0] != :SwitchOut || @battle.choices[b.index][1] != i ally_will_switch_with_i = true break end next if ally_will_switch_with_i end # Ignore ace if possible if @trainer.has_skill_flag?("ReserveLastPokemon") && i == idxPartyEnd - 1 next if !mandatory || reserves.length > 0 end reserves.push([i, 100]) break if @trainer.has_skill_flag?("UsePokemonInOrder") && reserves.length > 0 end return -1 if reserves.length == 0 # Rate each possible replacement Pokémon reserves.each_with_index do |reserve, i| reserves[i][1] = rate_replacement_pokemon(idxBattler, party[reserve[0]], reserve[1]) end 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 end # Return the party index of the best rated replacement Pokémon return reserves[0][0] end def rate_replacement_pokemon(idxBattler, pkmn, score) pkmn_types = pkmn.types entry_hazard_damage = calculate_entry_hazard_damage(pkmn, idxBattler & 1) if entry_hazard_damage >= pkmn.hp score -= 50 # pkmn will just faint elsif entry_hazard_damage > 0 score -= 50 * entry_hazard_damage / pkmn.hp end # TODO: Toxic Spikes. # TODO: Sticky Web. # Predict effectiveness of foe's last used move against pkmn each_foe_battler(@user.side) do |b, i| next if !b.battler.lastMoveUsed move_data = GameData::Move.try_get(b.battler.lastMoveUsed) next if !move_data || move_data.status? move_type = move_data.type eff = Effectiveness.calculate(move_type, *pkmn_types) score -= move_data.power * eff / 5 end # Add power * effectiveness / 10 of all pkmn's moves to score pkmn.moves.each do |m| next if m.power == 0 || (m.pp == 0 && m.total_pp > 0) @battle.battlers[idxBattler].allOpposing.each do |b| 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 # Prefer if user is about to faint from Perish Song score += 20 if @user.effects[PBEffects::PerishSong] == 1 # TODO: Prefer if pkmn has lower HP and its position will be healed by Wish. return score end def calculate_entry_hazard_damage(pkmn, side) return 0 if pkmn.hasAbility?(:MAGICGUARD) || pkmn.hasItem?(:HEAVYDUTYBOOTS) ret = 0 # Stealth Rock if @battle.sides[side].effects[PBEffects::StealthRock] && GameData::Type.exists?(:ROCK) pkmn_types = pkmn.types eff = Effectiveness.calculate(:ROCK, *pkmn_types) ret += pkmn.totalhp * eff / 8 if !Effectiveness.ineffective?(eff) end # Spikes if @battle.sides[side].effects[PBEffects::Spikes] > 0 if @battle.field.effects[PBEffects::Gravity] > 0 || pkmn.hasItem?(:IRONBALL) || !(pkmn.hasType?(:FLYING) || pkmn.hasItem?(:LEVITATE) || pkmn.hasItem?(:AIRBALLOON)) spikes_div = [8, 6, 4][@battle.sides[side].effects[PBEffects::Spikes] - 1] ret += pkmn.totalhp / spikes_div end end return ret end 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| if battler.effects[PBEffects::PerishSong] == 1 PBDebug.log_ai("#{battler.name} wants to switch because it is about to faint from Perish Song") next true end next false } ) #=============================================================================== # Pokémon will take a significant amount of damage at the end of this round, or # it has an effect that causes it damage at the end of this round which it can # remove by switching. #=============================================================================== Battle::AI::Handlers::ShouldSwitch.add(:significant_eor_damage, proc { |battler, reserves, ai, battle| eor_damage = battler.rough_end_of_round_damage # Switch if battler will take significant EOR damage if eor_damage >= battler.hp / 2 || eor_damage >= battler.totalhp / 4 PBDebug.log_ai("#{battler.name} wants to switch because it will take a lot of EOR damage") next true end # Switch to remove certain effects that cause the battler EOR damage if ai.trainer.high_skill? && eor_damage > 0 if battler.effects[PBEffects::LeechSeed] >= 0 && ai.pbAIRandom(100) < 50 PBDebug.log_ai("#{battler.name} wants to switch to get rid of its Leech Seed") next true end if battler.effects[PBEffects::Nightmare] PBDebug.log_ai("#{battler.name} wants to switch to get rid of its Nightmare") next true end if battler.effects[PBEffects::Curse] PBDebug.log_ai("#{battler.name} wants to switch to get rid of its Curse") next true end if battler.status == :POISON && battler.statusCount > 0 && !battler.has_active_ability?(:POISONHEAL) poison_damage = battler.totalhp / 8 next_toxic_damage = battler.totalhp * (battler.effects[PBEffects::Toxic] + 1) / 16 if (battler.hp <= next_toxic_damage && battler.hp > poison_damage) || next_toxic_damage > poison_damage * 2 PBDebug.log_ai("#{battler.name} wants to switch to reduce toxic to regular poisoning") next true end end end next false } ) #=============================================================================== # Pokémon can cure its status problem or heal some HP with its ability by # switching out. Covers all abilities with an OnSwitchOut AbilityEffects # handler. # TODO: Add randomness? #=============================================================================== Battle::AI::Handlers::ShouldSwitch.add(:cure_status_problem_by_switching_out, proc { |battler, reserves, ai, battle| next false if !battler.ability_active? # Don't try to cure a status problem/heal a bit of HP if entry hazards will # KO the battler if it switches back in entry_hazard_damage = ai.calculate_entry_hazard_damage(battler.pokemon, battler.side) next false if entry_hazard_damage >= battler.hp # Check specific abilities single_status_cure = { :IMMUNITY => :POISON, :INSOMNIA => :SLEEP, :LIMBER => :PARALYSIS, :MAGMAARMOR => :FROZEN, :VITALSPIRIT => :SLEEP, :WATERBUBBLE => :BURN, :WATERVEIL => :BURN }[battler.ability_id] if battler.ability == :NATURALCURE || (single_status_cure && single_status_cure == battler.status) # Cures status problem next false if battler.wants_status_problem?(battler.status) next false if battler.status == :SLEEP && battler.statusCount == 1 # Will wake up this round anyway next false if entry_hazard_damage >= battler.totalhp / 4 # Don't bother curing a poisoning if Toxic Spikes will just re-poison the # battler when it switches back in if battler.status == :POISON && reserves.none? { |pkmn| pkmn.hasType?(:POISON) } next false if battle.field.effects[PBEffects::ToxicSpikes] == 2 next false if battle.field.effects[PBEffects::ToxicSpikes] == 1 && battler.statusCount == 0 end # Not worth curing status problems that still allow actions if at high HP next false if battler.hp >= battler.totalhp / 2 && ![:SLEEP, :FROZEN].include?(battler.status) PBDebug.log_ai("#{battler.name} wants to switch to cure its status problem with #{battler.ability.name}") next true elsif battler.ability == :REGENERATOR # Heals 33% HP next false if entry_hazard_damage >= battler.totalhp / 3 # Not worth healing HP if already at high HP next false if battler.hp >= battler.totalhp / 2 # TODO: Don't bother if user can do decent damage. if ai.pbAIRandom(100) < 50 PBDebug.log_ai("#{battler.name} wants to switch to heal with #{battler.ability.name}") next true end end next false } ) #=============================================================================== # Pokémon's position is about to be healed by Wish, and a reserve can benefit # more from that healing than the Pokémon can. #=============================================================================== Battle::AI::Handlers::ShouldSwitch.add(:wish_healing, proc { |battler, reserves, ai, battle| position = battle.positions[battler.index] next false if position.effects[PBEffects::Wish] == 0 amt = position.effects[PBEffects::WishAmount] next false if battler.totalhp - battler.hp >= amt * 2 / 3 reserve_wants_healing_more = false reserves.each do |pkmn| entry_hazard_damage = ai.calculate_entry_hazard_damage(pkmn, battler.index & 1) next if entry_hazard_damage >= pkmn.hp reserve_wants_healing_more = (pkmn.totalhp - pkmn.hp - entry_hazard_damage >= amt * 2 / 3) break if reserve_wants_healing_more end if reserve_wants_healing_more PBDebug.log_ai("#{battler.name} wants to switch because Wish can heal a reserve more") next true end next false } ) #=============================================================================== # Pokémon is yawning and can't do anything while asleep. #=============================================================================== Battle::AI::Handlers::ShouldSwitch.add(:yawning, proc { |battler, reserves, ai, battle| # Yawning and can fall asleep because of it next false if battler.effects[PBEffects::Yawn] == 0 || !battler.battler.pbCanSleepYawn? # Doesn't want to be asleep (includes checking for moves usable while asleep) next false if battler.wants_status_problem?(:SLEEP) # Can't cure itself of sleep if battler.ability_active? next false if [:INSOMNIA, :NATURALCURE, :REGENERATOR, :SHEDSKIN].include?(battler.ability_id) next false if battler.ability_id == :HYDRATION && [:Rain, :HeavyRain].include?(battler.battler.effectiveWeather) end next false if battler.has_active_item?([:CHESTOBERRY, :LUMBERRY]) && battler.battler.canConsumeBerry? # Ally can't cure sleep ally_can_heal = false ai.each_ally(battler.index) do |b, i| ally_can_heal = b.has_active_ability?(:HEALER) break if ally_can_heal end next false if ally_can_heal # Doesn't benefit from being asleep/isn't less affected by sleep next false if battler.has_active_ability?([:EARLYBIRD, :MARVELSCALE]) # Not trapping another battler in battle if ai.trainer.high_skill? next false if ai.battlers.any? do |b| b.effects[PBEffects::JawLock] == battler.index || b.effects[PBEffects::MeanLook] == battler.index || b.effects[PBEffects::Octolock] == battler.index || b.effects[PBEffects::TrappingUser] == battler.index end trapping = false ai.each_foe_battler(battler.side) do |b, i| next if b.ability_active? && Battle::AbilityEffects.triggerCertainSwitching(b.ability, b, battle) next if b.item_active? && Battle::ItemEffects.triggerCertainSwitching(b.item, b, battle) next if Settings::MORE_TYPE_EFFECTS && b.has_type?(:GHOST) next if b.battler.trappedInBattle? # Relevant trapping effects are checked above if battler.ability_active? trapping = Battle::AbilityEffects.triggerTrappingByTarget(battler.ability, b, battler.battler, battle) break if trapping end if battler.item_active? trapping = Battle::ItemEffects.triggerTrappingByTarget(battler.item, b, battler.battler, battle) break if trapping end end next false if trapping end # Doesn't have sufficiently raised stats that would be lost by switching next false if battler.stages.any? { |key, val| val >= 2 } PBDebug.log_ai("#{battler.name} wants to switch because it is yawning and can't do anything while asleep") next true } ) #=============================================================================== # Pokémon is asleep, won't wake up soon and can't do anything while asleep. #=============================================================================== Battle::AI::Handlers::ShouldSwitch.add(:asleep, proc { |battler, reserves, ai, battle| # Asleep and won't wake up this round or next round next false if battler.status != :SLEEP || battler.statusCount <= 2 # Doesn't want to be asleep (includes checking for moves usable while asleep) next false if battler.wants_status_problem?(:SLEEP) # Doesn't benefit from being asleep next false if battler.has_active_ability?(:MARVELSCALE) # Doesn't know Rest (if it does, sleep is expected, so don't apply this check) next false if battler.check_for_move { |m| m.function == "HealUserFullyAndFallAsleep" } # Not trapping another battler in battle if ai.trainer.high_skill? next false if ai.battlers.any? do |b| b.effects[PBEffects::JawLock] == battler.index || b.effects[PBEffects::MeanLook] == battler.index || b.effects[PBEffects::Octolock] == battler.index || b.effects[PBEffects::TrappingUser] == battler.index end trapping = false ai.each_foe_battler(battler.side) do |b, i| next if b.ability_active? && Battle::AbilityEffects.triggerCertainSwitching(b.ability, b, battle) next if b.item_active? && Battle::ItemEffects.triggerCertainSwitching(b.item, b, battle) next if Settings::MORE_TYPE_EFFECTS && b.has_type?(:GHOST) next if b.battler.trappedInBattle? # Relevant trapping effects are checked above if battler.ability_active? trapping = Battle::AbilityEffects.triggerTrappingByTarget(battler.ability, b, battler.battler, battle) break if trapping end if battler.item_active? trapping = Battle::ItemEffects.triggerTrappingByTarget(battler.item, b, battler.battler, battle) break if trapping end end next false if trapping end # Doesn't have sufficiently raised stats that would be lost by switching next false if battler.stages.any? { |key, val| val >= 2 } # 50% chance to not bother next false if ai.pbAIRandom(100) < 50 PBDebug.log_ai("#{battler.name} wants to switch because it is asleep and can't do anything") next true } ) #=============================================================================== # Pokémon can't use any moves and isn't Destiny Bonding/Grudging/hiding behind a # Substitute. #=============================================================================== Battle::AI::Handlers::ShouldSwitch.add(:battler_is_useless, proc { |battler, reserves, ai, battle| next false if !ai.trainer.medium_skill? next false if battler.turnCount < 2 # Just switched in, give it a chance next false if battle.pbCanChooseAnyMove?(battler.index) next false if battler.effects[PBEffects::DestinyBond] || battler.effects[PBEffects::Grudge] if battler.effects[PBEffects::Substitute] hidden_behind_substitute = true ai.each_foe_battler(battler.side) do |b, i| next if !b.check_for_move { |m| m.ignoresSubstitute?(b.battler) } hidden_behind_substitute = false break end next false if hidden_behind_substitute end PBDebug.log_ai("#{battler.name} wants to switch because it can't do anything") next true } ) #=============================================================================== # Pokémon can't do anything to a Wonder Guard foe. # TODO: Check other abilities that provide immunities? #=============================================================================== Battle::AI::Handlers::ShouldSwitch.add(:foe_has_wonder_guard, proc { |battler, reserves, ai, battle| 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 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 break end # Check for super-effective damaging moves battler.battler.eachMove do |move| next if move.statusMove? if ["IgnoreTargetAbility", "CategoryDependsOnHigherDamageIgnoreTargetAbility"].include?(move.function) has_super_effective_move = true break end eff = Effectiveness.calculate(move.pbCalcType(battler.battler), *foe_types) if Effectiveness.super_effective?(eff) has_super_effective_move = true break end end # TODO: Check if battler has other useful status moves. CFRU considers # these (and makes sure they're usable; also ensure they're not # stopped by a substitute): # - Inflict sleep/poison/burn/paralysis (not freeze) # - Inflict confusion (inc. Swagger/Flatter) # - Start damaging weather (sandstorm/hail) # - Trick (to give foe an item with EOR damage/status infliction) # - Other EOR damage moves (Leech Seed, Nightmare, Curse) # - Perish Song # - Add third type to target (Trick-or-Treat, Forest's Curse) # - Worry Seed, Gastro Acid, Entrainment, Simple Beam, Core Enforcer # - Roar # - Baton Pass, Teleport # - Memento (why?) # - Entry hazards (not sure why; just to stack them up?) # - Wish (just to set it up?) # - Tailwind (just to set it up?) # - Lucky Chant (just to set it up?) break if has_super_effective_move 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| pkmn.moves.each do |move| next if move.status_move? if ["IgnoreTargetAbility", "CategoryDependsOnHigherDamageIgnoreTargetAbility"].include?(move.function_code) reserve_has_super_effective_move = true break end eff = Effectiveness.calculate(move.type, *foe_types) if Effectiveness.super_effective?(eff) reserve_has_super_effective_move = true break end end break if reserve_has_super_effective_move 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 end next false } ) #=============================================================================== # #=============================================================================== # TODO: Switch if battler's offensive stats are sufficiently low and it wants to # use damaging moves (CFRU applies this only to a sweeper). #=============================================================================== # Pokémon doesn't have an ability that makes it immune to a foe's move, but a # reserve does (see def pokemon_can_absorb_move?). The foe's move is chosen # randomly, or is their most powerful move if the trainer's skill level is good # enough. # TODO: Add randomness? #=============================================================================== Battle::AI::Handlers::ShouldSwitch.add(:absorb_foe_move, proc { |battler, reserves, ai, battle| next false if !ai.trainer.medium_skill? # Not worth it if the battler is evasive enough next false if battler.stages[:EVASION] >= 3 # Not worth it if abilities are being negated next false if battle.pbCheckGlobalAbility(:NEUTRALIZINGGAS) # Get the foe move with the highest power (or a random damaging move) foe_moves = [] ai.each_foe_battler(battler.side) do |b, i| b.moves.each do |move| next if move.statusMove? # TODO: Improve on m_power with STAB and attack stat/stages and certain # other damage-altering effects, including base power calculations # for moves with variable power. m_power = move.power m_power = battler.hp if move.is_a?(Battle::Move::OHKO) m_type = move.pbCalcType(b.battler) foe_moves.push([m_power, m_type, move]) end end next false if foe_moves.empty? if ai.trainer.high_skill? foe_moves.sort! { |a, b| a[0] <=> b[0] } # Highest power move chosen_move = foe_moves.last else chosen_move = foe_moves[ai.pbAIRandom(foe_moves.length)] # Random move end # Get the chosen move's information move_type = chosen_move[1] move = chosen_move[2] # TODO: Don't bother if the move's power isn't particularly high? Would need # to figure out what "particularly high" means, probably involving the # battler's defences in a rough damage calculation (the attacking part # of which is above). # Check battler for absorbing ability next false if ai.pokemon_can_absorb_move?(battler, move, move_type) # battler can't absorb move; find a party Pokémon that can if reserves.any? { |pkmn| ai.pokemon_can_absorb_move?(pkmn, move, move_type) } PBDebug.log_ai("#{battler.name} wants to switch because it can't absorb a foe's move but a reserve can") next true end next false } ) #=============================================================================== # #=============================================================================== # TODO: Switch if foe is locked into using a single move and a reserve can # resist/no sell it. #=============================================================================== # #=============================================================================== # TODO: Switch if a foe is using a damaging two-turn attack and a reserve can # resist/no sell its damage. #=============================================================================== # Sudden Death rule (at the end of each round, if one side has more able Pokémon # than the other side, that side wins). Avoid fainting at all costs. # NOTE: This rule isn't used anywhere. #=============================================================================== Battle::AI::Handlers::ShouldSwitch.add(:sudden_death, proc { |battler, reserves, ai, battle| next false if !battle.rules["suddendeath"] || battler.turnCount == 0 if battler.hp <= battler.totalhp / 2 threshold = 100 * (battler.totalhp - battler.hp) / battler.totalhp if ai.pbAIRandom(100) < threshold PBDebug.log_ai("#{battler.name} wants to switch to avoid being KO'd and losing because of the sudden death rule") next true end end next false } ) #=============================================================================== # #=============================================================================== # TODO: Switch if battler is at risk of being KO'd (unless it's at low HP and # paralysed and can't cure itself/benefit from paralysis, as it'll # probably not survive anyway). Don't bother if battler is Aegislash and # could go into Shield Form instead. #=============================================================================== # 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| next false if !ai.trainer.high_skill? next false if battler.hp >= battler.totalhp / 2 big_threat = false ai.each_foe_battler(battler.side) do |b, i| next if (b.level - battler.level).abs > 5 next if !b.battler.lastMoveUsed || !GameData::Move.exists?(b.battler.lastMoveUsed) move_data = GameData::Move.get(b.battler.lastMoveUsed) next if move_data.status? eff = battler.effectiveness_of_type_against_battler(move_data.type, b) next if !Effectiveness.super_effective?(eff) || move_data.power < 70 switch_chance = (move_data.power > 90) ? 50 : 25 big_threat = (ai.pbAIRandom(100) < switch_chance) break if big_threat end if big_threat PBDebug.log_ai("#{battler.name} wants to switch because a foe has a powerful super-effective move") next true end next false } ) #=============================================================================== #=============================================================================== #=============================================================================== #=============================================================================== # Don't bother switching if the battler will just faint from entry hazard damage # upon switching back in, and if no reserve can remove the entry hazard(s). # Switching out in this case means the battler becomes unusable, so it might as # well stick around instead and do as much as it can. #=============================================================================== Battle::AI::Handlers::ShouldNotSwitch.add(:lethal_entry_hazards, proc { |battler, reserves, ai, battle| next false if battle.rules["suddendeath"] # Check whether battler will faint from entry hazard(s) entry_hazard_damage = ai.calculate_entry_hazard_damage(battler.pokemon, battler.side) next false if entry_hazard_damage < battler.hp # Check for Rapid Spin reserve_can_remove_hazards = false reserves.each do |pkmn| pkmn.moves.each do |move| reserve_can_remove_hazards = (move.function_code == "RemoveUserBindingAndEntryHazards") break if reserve_can_remove_hazards end break if reserve_can_remove_hazards end next false if reserve_can_remove_hazards PBDebug.log_ai("#{battler.name} won't switch after all because it will faint from entry hazards if it switches back in") next true } ) #=============================================================================== # Don't bother switching (50% chance) if the battler knows a super-effective # move. #=============================================================================== Battle::AI::Handlers::ShouldNotSwitch.add(:battler_has_super_effective_move, proc { |battler, reserves, ai, battle| next false if battle.rules["suddendeath"] has_super_effective_move = false battler.battler.eachMove do |move| next if move.pp == 0 && move.total_pp > 0 next if move.statusMove? # TODO: next if move is unusable? This would be complicated to implement. move_type = move.type move_type = move.pbCalcType(battler.battler) if ai.trainer.medium_skill? ai.each_foe_battler(battler.side) do |b| # TODO: next if move can't target b? This would be complicated to # implement. # TODO: Check the move's power as well? Do a (rough) damage calculation # for it and come up with a threshold % HP? eff = b.effectiveness_of_type_against_battler(move_type, battler) has_super_effective_move = Effectiveness.super_effective?(eff) break if has_super_effective_move end break if has_super_effective_move end if has_super_effective_move && ai.pbAIRandom(100) < 50 PBDebug.log_ai("#{battler.name} won't switch after all because it has a super-effective move") next true end next false } ) #=============================================================================== # 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| next false if battle.rules["suddendeath"] stat_raises = 0 battler.stages.each_value { |val| stat_raises += val if val > 0 } if stat_raises >= 4 PBDebug.log_ai("#{battler.name} won't switch after all because it has a lot of raised stats") next true end next false } )