mirror of
https://github.com/infinitefusion/infinitefusion-e18.git
synced 2025-12-08 13:44:59 +00:00
More AI code for deciding when to switch
This commit is contained in:
@@ -25,6 +25,7 @@ class Battle::AI
|
|||||||
else
|
else
|
||||||
return false if !@trainer.has_skill_flag?("ConsiderSwitching")
|
return false if !@trainer.has_skill_flag?("ConsiderSwitching")
|
||||||
reserves = get_non_active_party_pokemon(@user.index)
|
reserves = get_non_active_party_pokemon(@user.index)
|
||||||
|
return false if reserves.empty?
|
||||||
should_switch = Battle::AI::Handlers.should_switch?(@user, reserves, self, @battle)
|
should_switch = Battle::AI::Handlers.should_switch?(@user, reserves, self, @battle)
|
||||||
if should_switch && @trainer.medium_skill?
|
if should_switch && @trainer.medium_skill?
|
||||||
should_switch = false if Battle::AI::Handlers.should_not_switch?(@user, reserves, self, @battle)
|
should_switch = false if Battle::AI::Handlers.should_not_switch?(@user, reserves, self, @battle)
|
||||||
@@ -63,8 +64,9 @@ class Battle::AI
|
|||||||
return ret
|
return ret
|
||||||
end
|
end
|
||||||
|
|
||||||
#=============================================================================
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# TODO: Need a way to allow a ShouldSwitch handler to recommend a replacement.
|
||||||
def choose_best_replacement_pokemon(idxBattler, mandatory = false)
|
def choose_best_replacement_pokemon(idxBattler, mandatory = false)
|
||||||
# Get all possible replacement Pokémon
|
# Get all possible replacement Pokémon
|
||||||
party = @battle.pbParty(idxBattler)
|
party = @battle.pbParty(idxBattler)
|
||||||
@@ -134,7 +136,8 @@ class Battle::AI
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
# Prefer if user is about to faint from Perish Song
|
# Prefer if user is about to faint from Perish Song
|
||||||
score += 10 if @user.effects[PBEffects::PerishSong] == 1
|
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
|
return score
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -161,7 +164,6 @@ end
|
|||||||
#===============================================================================
|
#===============================================================================
|
||||||
# Pokémon is about to faint because of Perish Song.
|
# Pokémon is about to faint because of Perish Song.
|
||||||
# TODO: Also switch to remove other negative effects like Disable, Yawn.
|
# TODO: Also switch to remove other negative effects like Disable, Yawn.
|
||||||
# TODO: Review switch deciding.
|
|
||||||
#===============================================================================
|
#===============================================================================
|
||||||
Battle::AI::Handlers::ShouldSwitch.add(:perish_song,
|
Battle::AI::Handlers::ShouldSwitch.add(:perish_song,
|
||||||
proc { |battler, reserves, ai, battle|
|
proc { |battler, reserves, ai, battle|
|
||||||
@@ -174,10 +176,9 @@ Battle::AI::Handlers::ShouldSwitch.add(:perish_song,
|
|||||||
)
|
)
|
||||||
|
|
||||||
#===============================================================================
|
#===============================================================================
|
||||||
# Pokémon will take a significant amount of damage at the end of this round.
|
# Pokémon will take a significant amount of damage at the end of this round, or
|
||||||
# Also, Pokémon has an effect that causes it damage at the end of this round,
|
# it has an effect that causes it damage at the end of this round which it can
|
||||||
# which it can remove by switching.
|
# remove by switching.
|
||||||
# TODO: Review switch deciding.
|
|
||||||
#===============================================================================
|
#===============================================================================
|
||||||
Battle::AI::Handlers::ShouldSwitch.add(:significant_eor_damage,
|
Battle::AI::Handlers::ShouldSwitch.add(:significant_eor_damage,
|
||||||
proc { |battler, reserves, ai, battle|
|
proc { |battler, reserves, ai, battle|
|
||||||
@@ -217,8 +218,9 @@ Battle::AI::Handlers::ShouldSwitch.add(:significant_eor_damage,
|
|||||||
|
|
||||||
#===============================================================================
|
#===============================================================================
|
||||||
# Pokémon can cure its status problem or heal some HP with its ability by
|
# 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.
|
# switching out. Covers all abilities with an OnSwitchOut AbilityEffects
|
||||||
# TODO: Review switch deciding. Add randomness?
|
# handler.
|
||||||
|
# TODO: Add randomness?
|
||||||
#===============================================================================
|
#===============================================================================
|
||||||
Battle::AI::Handlers::ShouldSwitch.add(:cure_status_problem_by_switching_out,
|
Battle::AI::Handlers::ShouldSwitch.add(:cure_status_problem_by_switching_out,
|
||||||
proc { |battler, reserves, ai, battle|
|
proc { |battler, reserves, ai, battle|
|
||||||
@@ -237,7 +239,7 @@ Battle::AI::Handlers::ShouldSwitch.add(:cure_status_problem_by_switching_out,
|
|||||||
:WATERBUBBLE => :BURN,
|
:WATERBUBBLE => :BURN,
|
||||||
:WATERVEIL => :BURN
|
:WATERVEIL => :BURN
|
||||||
}[battler.ability_id]
|
}[battler.ability_id]
|
||||||
if battler.ability_id == :NATURALCURE || (single_status_cure && single_status_cure == battler.status)
|
if battler.ability == :NATURALCURE || (single_status_cure && single_status_cure == battler.status)
|
||||||
# Cures status problem
|
# Cures status problem
|
||||||
next false if battler.wants_status_problem?(battler.status)
|
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 battler.status == :SLEEP && battler.statusCount == 1 # Will wake up this round anyway
|
||||||
@@ -252,7 +254,7 @@ Battle::AI::Handlers::ShouldSwitch.add(:cure_status_problem_by_switching_out,
|
|||||||
next false if battler.hp >= battler.totalhp / 2 && ![:SLEEP, :FROZEN].include?(battler.status)
|
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}")
|
PBDebug.log_ai("#{battler.name} wants to switch to cure its status problem with #{battler.ability.name}")
|
||||||
next true
|
next true
|
||||||
elsif battler.ability_id == :REGENERATOR
|
elsif battler.ability == :REGENERATOR
|
||||||
# Heals 33% HP
|
# Heals 33% HP
|
||||||
next false if entry_hazard_damage >= battler.totalhp / 3
|
next false if entry_hazard_damage >= battler.totalhp / 3
|
||||||
# Not worth healing HP if already at high HP
|
# Not worth healing HP if already at high HP
|
||||||
@@ -267,10 +269,161 @@ Battle::AI::Handlers::ShouldSwitch.add(:cure_status_problem_by_switching_out,
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
#===============================================================================
|
||||||
|
# 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 = 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.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? { |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.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? { |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.
|
# Pokémon can't do anything to a Wonder Guard foe.
|
||||||
# TODO: Check other abilities that provide immunities?
|
# TODO: Check other abilities that provide immunities?
|
||||||
# TODO: Review switch deciding.
|
|
||||||
#===============================================================================
|
#===============================================================================
|
||||||
Battle::AI::Handlers::ShouldSwitch.add(:foe_has_wonder_guard,
|
Battle::AI::Handlers::ShouldSwitch.add(:foe_has_wonder_guard,
|
||||||
proc { |battler, reserves, ai, battle|
|
proc { |battler, reserves, ai, battle|
|
||||||
@@ -350,30 +503,126 @@ Battle::AI::Handlers::ShouldSwitch.add(:foe_has_wonder_guard,
|
|||||||
)
|
)
|
||||||
|
|
||||||
#===============================================================================
|
#===============================================================================
|
||||||
# If Pokémon is within 5 levels of the foe, and foe's last move was
|
#
|
||||||
# super-effective and powerful.
|
#===============================================================================
|
||||||
|
# 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 |m|
|
||||||
|
next if m.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 = m.power
|
||||||
|
m_power = battler.hp if m.is_a?(Battle::Move::OHKO)
|
||||||
|
m_type = move.pbCalcType(b.battler)
|
||||||
|
foe_moves.push([m_power, m_type, m])
|
||||||
|
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.
|
# TODO: Review switch deciding.
|
||||||
#===============================================================================
|
#===============================================================================
|
||||||
Battle::AI::Handlers::ShouldSwitch.add(:high_damage_from_foe,
|
Battle::AI::Handlers::ShouldSwitch.add(:high_damage_from_foe,
|
||||||
proc { |battler, reserves, ai, battle|
|
proc { |battler, reserves, ai, battle|
|
||||||
next false if battler.hp >= battler.totalhp / 2
|
|
||||||
next false if !ai.trainer.high_skill?
|
next false if !ai.trainer.high_skill?
|
||||||
|
next false if battler.hp >= battler.totalhp / 2
|
||||||
big_threat = false
|
big_threat = false
|
||||||
ai.each_foe_battler(battler.side) do |b, i|
|
ai.each_foe_battler(battler.side) do |b, i|
|
||||||
next if (foe.level - battler.level).abs > 5
|
next if (b.level - battler.level).abs > 5
|
||||||
next if !b.battler.lastMoveUsed
|
next if !b.battler.lastMoveUsed
|
||||||
move_data = GameData::Move.get(b.battler.lastMoveUsed)
|
move_data = GameData::Move.get(b.battler.lastMoveUsed)
|
||||||
next if move_data.status?
|
next if move_data.status?
|
||||||
eff = battler.effectiveness_of_type_against_battler(move_data.type, b)
|
eff = battler.effectiveness_of_type_against_battler(move_data.type, b)
|
||||||
next if !Effectiveness.super_effective?(eff) || move_data.power < 60
|
next if !Effectiveness.super_effective?(eff) || move_data.power < 70
|
||||||
switch_chance = (move_data.power > 90) ? 50 : 25
|
switch_chance = (move_data.power > 90) ? 50 : 25
|
||||||
if ai.pbAIRandom(100) < switch_chance
|
big_threat = (ai.pbAIRandom(100) < switch_chance)
|
||||||
big_threat = true
|
break if big_threat
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
if big_threat
|
if big_threat
|
||||||
PBDebug.log_ai("#{battler.name} wants to switch because a foe can do a lot of damage to it")
|
PBDebug.log_ai("#{battler.name} wants to switch because a foe has a powerful super-effective move")
|
||||||
next true
|
next true
|
||||||
end
|
end
|
||||||
next false
|
next false
|
||||||
@@ -381,74 +630,84 @@ Battle::AI::Handlers::ShouldSwitch.add(:high_damage_from_foe,
|
|||||||
)
|
)
|
||||||
|
|
||||||
#===============================================================================
|
#===============================================================================
|
||||||
# Pokémon can't do anything (must have been in battle for at least 3 rounds).
|
|
||||||
# TODO: Review switch deciding.
|
|
||||||
#===============================================================================
|
#===============================================================================
|
||||||
Battle::AI::Handlers::ShouldSwitch.add(:battler_is_useless,
|
|
||||||
proc { |battler, reserves, ai, battle|
|
|
||||||
if !battle.pbCanChooseAnyMove?(battler.index) && battler.turnCount >= 3
|
|
||||||
PBDebug.log_ai("#{battler.name} wants to switch because it can't do anything")
|
|
||||||
next true
|
|
||||||
end
|
|
||||||
next false
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
#===============================================================================
|
#===============================================================================
|
||||||
# Pokémon is Encored into an unfavourable move.
|
|
||||||
# TODO: Review switch deciding.
|
|
||||||
#===============================================================================
|
|
||||||
Battle::AI::Handlers::ShouldSwitch.add(:bad_encored_move,
|
|
||||||
proc { |battler, reserves, ai, battle|
|
|
||||||
next false if battler.effects[PBEffects::Encore] == 0
|
|
||||||
idxEncoredMove = battler.battler.pbEncoredMoveIndex
|
|
||||||
next false if idxEncoredMove < 0
|
|
||||||
ai.set_up_move_check(battler.moves[idxEncoredMove])
|
|
||||||
scoreSum = 0
|
|
||||||
scoreCount = 0
|
|
||||||
battler.battler.allOpposing.each do |b|
|
|
||||||
scoreSum += ai.pbGetMoveScore([b])
|
|
||||||
scoreCount += 1
|
|
||||||
end
|
|
||||||
if scoreCount > 0 && scoreSum / scoreCount <= 20
|
|
||||||
if ai.pbAIRandom(100) < 80
|
|
||||||
PBDebug.log_ai("#{battler.name} wants to switch because of encoring a bad move")
|
|
||||||
next true
|
|
||||||
else
|
|
||||||
next false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
next false
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
#===============================================================================
|
|
||||||
# Sudden Death rule - I'm not sure what this means.
|
|
||||||
# TODO: Review switch deciding.
|
|
||||||
#===============================================================================
|
|
||||||
Battle::AI::Handlers::ShouldSwitch.add(:sudden_death,
|
|
||||||
proc { |battler, reserves, ai, battle|
|
|
||||||
if battle.rules["suddendeath"] && battler.turnCount > 0
|
|
||||||
if battler.hp <= battler.totalhp / 4 && ai.pbAIRandom(100) < 30
|
|
||||||
PBDebug.log_ai("#{battler.name} wants to switch because of the sudden death rule")
|
|
||||||
next true
|
|
||||||
elsif battler.hp <= battler.totalhp / 2 && ai.pbAIRandom(100) < 80
|
|
||||||
PBDebug.log_ai("#{battler.name} wants to switch because of the sudden death rule")
|
|
||||||
next true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
next false
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
#===============================================================================
|
#===============================================================================
|
||||||
# Don't bother switching if the battler will just faint from entry hazard damage
|
# Don't bother switching if the battler will just faint from entry hazard damage
|
||||||
# upon switching back in.
|
# upon switching back in, and if no reserve can remove the entry hazard(s).
|
||||||
# TODO: Allow it if the replacement will be able to get rid of entry hazards?
|
# 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,
|
Battle::AI::Handlers::ShouldNotSwitch.add(:lethal_entry_hazards,
|
||||||
# proc { |battler, reserves, ai, battle|
|
proc { |battler, reserves, ai, battle|
|
||||||
# entry_hazard_damage = ai.calculate_entry_hazard_damage(battler.pkmn, battler.side)
|
next false if battle.rules["suddendeath"]
|
||||||
# next entry_hazard_damage >= battler.hp
|
# Check whether battler will faint from entry hazard(s)
|
||||||
# }
|
entry_hazard_damage = ai.calculate_entry_hazard_damage(battler.pkmn, 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.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
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|||||||
@@ -9,14 +9,14 @@ class Battle::AI
|
|||||||
return 0.6 + 0.35 * (([@trainer.skill, 100].min / 100.0) ** 0.5) # 0.635 to 0.95
|
return 0.6 + 0.35 * (([@trainer.skill, 100].min / 100.0) ** 0.5) # 0.635 to 0.95
|
||||||
end
|
end
|
||||||
|
|
||||||
#=============================================================================
|
#-----------------------------------------------------------------------------
|
||||||
# Get scores for the user's moves (done before any action is assessed).
|
|
||||||
|
# Get scores for the user's moves.
|
||||||
# NOTE: For any move with a target type that can target a foe (or which
|
# NOTE: For any move with a target type that can target a foe (or which
|
||||||
# includes a foe(s) if it has multiple targets), the score calculated
|
# includes a foe(s) if it has multiple targets), the score calculated
|
||||||
# for a target ally will be inverted. The MoveHandlers for those moves
|
# for a target ally will be inverted. The MoveHandlers for those moves
|
||||||
# should therefore treat an ally as a foe when calculating a score
|
# should therefore treat an ally as a foe when calculating a score
|
||||||
# against it.
|
# against it.
|
||||||
#=============================================================================
|
|
||||||
def pbGetMoveScores
|
def pbGetMoveScores
|
||||||
choices = []
|
choices = []
|
||||||
@user.battler.eachMoveWithIndex do |orig_move, idxMove|
|
@user.battler.eachMoveWithIndex do |orig_move, idxMove|
|
||||||
@@ -85,6 +85,8 @@ class Battle::AI
|
|||||||
return choices
|
return choices
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# If the target of a move can be changed by an external effect, this method
|
||||||
|
# returns the battler index of the new target.
|
||||||
def get_redirected_target(target_data)
|
def get_redirected_target(target_data)
|
||||||
return nil if @move.move.cannotRedirect?
|
return nil if @move.move.cannotRedirect?
|
||||||
return nil if !target_data.can_target_one_foe? || target_data.num_targets != 1
|
return nil if !target_data.can_target_one_foe? || target_data.num_targets != 1
|
||||||
@@ -133,9 +135,9 @@ class Battle::AI
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
#=============================================================================
|
#-----------------------------------------------------------------------------
|
||||||
# Set some extra class variables for the move/target combo being assessed.
|
|
||||||
#=============================================================================
|
# Set some extra class variables for the move being assessed.
|
||||||
def set_up_move_check(move)
|
def set_up_move_check(move)
|
||||||
case move.function
|
case move.function
|
||||||
when "UseLastMoveUsed"
|
when "UseLastMoveUsed"
|
||||||
@@ -151,6 +153,7 @@ class Battle::AI
|
|||||||
@move.set_up(move)
|
@move.set_up(move)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Set some extra class variables for the target being assessed.
|
||||||
def set_up_move_check_target(target)
|
def set_up_move_check_target(target)
|
||||||
@target = (target) ? @battlers[target.index] : nil
|
@target = (target) ? @battlers[target.index] : nil
|
||||||
@target&.refresh_battler
|
@target&.refresh_battler
|
||||||
@@ -164,10 +167,10 @@ class Battle::AI
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
#=============================================================================
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
# Returns whether the move will definitely fail (assuming no battle conditions
|
# Returns whether the move will definitely fail (assuming no battle conditions
|
||||||
# change between now and using the move).
|
# change between now and using the move).
|
||||||
#=============================================================================
|
|
||||||
def pbPredictMoveFailure
|
def pbPredictMoveFailure
|
||||||
# User is asleep and will not wake up
|
# User is asleep and will not wake up
|
||||||
return true if @user.battler.asleep? && @user.statusCount > 1 && !@move.move.usableWhenAsleep?
|
return true if @user.battler.asleep? && @user.statusCount > 1 && !@move.move.usableWhenAsleep?
|
||||||
@@ -185,6 +188,8 @@ class Battle::AI
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Returns whether the move will definitely fail against the target (assuming
|
||||||
|
# no battle conditions change between now and using the move).
|
||||||
def pbPredictMoveFailureAgainstTarget
|
def pbPredictMoveFailureAgainstTarget
|
||||||
# Move effect-specific checks
|
# Move effect-specific checks
|
||||||
return true if Battle::AI::Handlers.move_will_fail_against_target?(@move.function, @move, @user, @target, self, @battle)
|
return true if Battle::AI::Handlers.move_will_fail_against_target?(@move.function, @move, @user, @target, self, @battle)
|
||||||
@@ -220,10 +225,10 @@ class Battle::AI
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
#=============================================================================
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
# Get a score for the given move being used against the given target.
|
# Get a score for the given move being used against the given target.
|
||||||
# Assumes def set_up_move_check has previously been called.
|
# Assumes def set_up_move_check has previously been called.
|
||||||
#=============================================================================
|
|
||||||
def pbGetMoveScore(targets = nil)
|
def pbGetMoveScore(targets = nil)
|
||||||
# Get the base score for the move
|
# Get the base score for the move
|
||||||
score = MOVE_BASE_SCORE
|
score = MOVE_BASE_SCORE
|
||||||
@@ -280,7 +285,8 @@ class Battle::AI
|
|||||||
return score
|
return score
|
||||||
end
|
end
|
||||||
|
|
||||||
#=============================================================================
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
# Returns the score of @move being used against @target. A return value of -1
|
# Returns the score of @move being used against @target. A return value of -1
|
||||||
# means the move will fail or do nothing against the target.
|
# means the move will fail or do nothing against the target.
|
||||||
# Assumes def set_up_move_check and def set_up_move_check_target have
|
# Assumes def set_up_move_check and def set_up_move_check_target have
|
||||||
@@ -294,7 +300,6 @@ class Battle::AI
|
|||||||
# wouldn't apply the "185 - score" bit, which would make their
|
# wouldn't apply the "185 - score" bit, which would make their
|
||||||
# MoveHandlers do the opposite calculations to other moves with the same
|
# MoveHandlers do the opposite calculations to other moves with the same
|
||||||
# targets, but is this desirable?
|
# targets, but is this desirable?
|
||||||
#=============================================================================
|
|
||||||
def pbGetMoveScoreAgainstTarget
|
def pbGetMoveScoreAgainstTarget
|
||||||
# Predict whether the move will fail against the target
|
# Predict whether the move will fail against the target
|
||||||
if @trainer.has_skill_flag?("PredictMoveFailure") && pbPredictMoveFailureAgainstTarget
|
if @trainer.has_skill_flag?("PredictMoveFailure") && pbPredictMoveFailureAgainstTarget
|
||||||
@@ -328,10 +333,10 @@ class Battle::AI
|
|||||||
return score
|
return score
|
||||||
end
|
end
|
||||||
|
|
||||||
#=============================================================================
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
# Make the final choice of which move to use depending on the calculated
|
# Make the final choice of which move to use depending on the calculated
|
||||||
# scores for each move. Moves with higher scores are more likely to be chosen.
|
# scores for each move. Moves with higher scores are more likely to be chosen.
|
||||||
#=============================================================================
|
|
||||||
def pbChooseMove(choices)
|
def pbChooseMove(choices)
|
||||||
user_battler = @user.battler
|
user_battler = @user.battler
|
||||||
# If no moves can be chosen, auto-choose a move or Struggle
|
# If no moves can be chosen, auto-choose a move or Struggle
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
class Battle::AI
|
class Battle::AI
|
||||||
#=============================================================================
|
|
||||||
# Decide whether the opponent should Mega Evolve.
|
# Decide whether the opponent should Mega Evolve.
|
||||||
#=============================================================================
|
|
||||||
# TODO: Where relevant, pretend the user is Mega Evolved if it isn't but can
|
# TODO: Where relevant, pretend the user is Mega Evolved if it isn't but can
|
||||||
# be.
|
# be.
|
||||||
def pbEnemyShouldMegaEvolve?
|
def pbEnemyShouldMegaEvolve?
|
||||||
|
|||||||
@@ -21,11 +21,11 @@ class Battle::AI
|
|||||||
return Math.sqrt(varianceTimesN / n)
|
return Math.sqrt(varianceTimesN / n)
|
||||||
end
|
end
|
||||||
|
|
||||||
#=============================================================================
|
#-----------------------------------------------------------------------------
|
||||||
# Move's type effectiveness
|
|
||||||
#=============================================================================
|
# Move's type effectiveness. For switching. Determines the effectiveness of a
|
||||||
# For switching. Determines the effectiveness of a potential switch-in against
|
# potential switch-in against an opposing battler.
|
||||||
# an opposing battler.
|
# TODO: Unused.
|
||||||
def pbCalcTypeModPokemon(pkmn, target_battler)
|
def pbCalcTypeModPokemon(pkmn, target_battler)
|
||||||
ret = Effectiveness::NORMAL_EFFECTIVE_MULTIPLIER
|
ret = Effectiveness::NORMAL_EFFECTIVE_MULTIPLIER
|
||||||
pkmn.types.each do |thisType|
|
pkmn.types.each do |thisType|
|
||||||
@@ -34,9 +34,40 @@ class Battle::AI
|
|||||||
return ret
|
return ret
|
||||||
end
|
end
|
||||||
|
|
||||||
#=============================================================================
|
# Assumes that pkmn's ability is not negated by a global effect (e.g.
|
||||||
# Yields certain AIBattler objects
|
# Neutralizing Gas).
|
||||||
#=============================================================================
|
# pkmn is either a Battle::AI::AIBattler or a Pokemon. move is a Battle::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
|
||||||
|
# TODO: Are there any other absorbing effects? Held item?
|
||||||
|
case pkmn.ability_id
|
||||||
|
when :BULLETPROOF
|
||||||
|
return move.bombMove?
|
||||||
|
when :FLASHFIRE
|
||||||
|
return move_type == :FIRE
|
||||||
|
when :LIGHTNINGROD, :MOTORDRIVE, :VOLTABSORB
|
||||||
|
return move_type == :ELECTRIC
|
||||||
|
when :SAPSIPPER
|
||||||
|
return move_type == :GRASS
|
||||||
|
when :SOUNDPROOF
|
||||||
|
return move.soundMove?
|
||||||
|
when :STORMDRAIN, :WATERABSORB, :DRYSKIN
|
||||||
|
return move_type == :WATER
|
||||||
|
when :TELEPATHY
|
||||||
|
# NOTE: The move is being used by a foe of pkmn.
|
||||||
|
return false
|
||||||
|
when :WONDERGUARD
|
||||||
|
types = pkmn.types
|
||||||
|
types = pkmn.pbTypes(true) if pkmn.is_a?(Battle::AI::AIBattler)
|
||||||
|
return Effectiveness.super_effective_type?(move_type, *types)
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
def each_battler
|
def each_battler
|
||||||
@battlers.each_with_index do |battler, i|
|
@battlers.each_with_index do |battler, i|
|
||||||
next if !battler || battler.fainted?
|
next if !battler || battler.fainted?
|
||||||
|
|||||||
@@ -26,10 +26,10 @@ class Battle::AI
|
|||||||
SECOND = 17
|
SECOND = 17
|
||||||
end
|
end
|
||||||
|
|
||||||
#=============================================================================
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
# Determine the roles filled by a Pokémon on a given side at a given party
|
# Determine the roles filled by a Pokémon on a given side at a given party
|
||||||
# index.
|
# index.
|
||||||
#=============================================================================
|
|
||||||
def determine_roles(side, index)
|
def determine_roles(side, index)
|
||||||
pkmn = @battle.pbParty(side)[index]
|
pkmn = @battle.pbParty(side)[index]
|
||||||
ret = []
|
ret = []
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
class Battle::AI
|
class Battle::AI
|
||||||
#=============================================================================
|
|
||||||
# Main method for calculating the score for moves that raise a battler's
|
# Main method for calculating the score for moves that raise a battler's
|
||||||
# stat(s).
|
# stat(s).
|
||||||
# By default, assumes that a stat raise is a good thing. However, this score
|
# By default, assumes that a stat raise is a good thing. However, this score
|
||||||
# is inverted (by desire_mult) if the target opposes the user. If the move
|
# is inverted (by desire_mult) if the target opposes the user. If the move
|
||||||
# could target a foe but is targeting an ally, the score is also inverted, but
|
# could target a foe but is targeting an ally, the score is also inverted, but
|
||||||
# only because it is inverted again in def pbGetMoveScoreAgainstTarget.
|
# only because it is inverted again in def pbGetMoveScoreAgainstTarget.
|
||||||
#=============================================================================
|
|
||||||
def get_score_for_target_stat_raise(score, target, stat_changes, whole_effect = true,
|
def get_score_for_target_stat_raise(score, target, stat_changes, whole_effect = true,
|
||||||
fixed_change = false, ignore_contrary = false)
|
fixed_change = false, ignore_contrary = false)
|
||||||
whole_effect = false if @move.damagingMove?
|
whole_effect = false if @move.damagingMove?
|
||||||
@@ -88,12 +86,12 @@ class Battle::AI
|
|||||||
return score
|
return score
|
||||||
end
|
end
|
||||||
|
|
||||||
#=============================================================================
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
# Returns whether the target raising the given stat will have any impact.
|
# Returns whether the target raising the given stat will have any impact.
|
||||||
# TODO: Make sure the move's actual damage category is taken into account,
|
# TODO: Make sure the move's actual damage category is taken into account,
|
||||||
# i.e. CategoryDependsOnHigherDamagePoisonTarget and
|
# i.e. CategoryDependsOnHigherDamagePoisonTarget and
|
||||||
# CategoryDependsOnHigherDamageIgnoreTargetAbility.
|
# CategoryDependsOnHigherDamageIgnoreTargetAbility.
|
||||||
#=============================================================================
|
|
||||||
def stat_raise_worthwhile?(target, stat, fixed_change = false)
|
def stat_raise_worthwhile?(target, stat, fixed_change = false)
|
||||||
if !fixed_change
|
if !fixed_change
|
||||||
return false if !target.battler.pbCanRaiseStatStage?(stat, @user.battler, @move.move)
|
return false if !target.battler.pbCanRaiseStatStage?(stat, @user.battler, @move.move)
|
||||||
@@ -143,9 +141,9 @@ class Battle::AI
|
|||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
#=============================================================================
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
# Make score changes based on the general concept of raising stats at all.
|
# Make score changes based on the general concept of raising stats at all.
|
||||||
#=============================================================================
|
|
||||||
def get_target_stat_raise_score_generic(score, target, stat_changes, desire_mult = 1)
|
def get_target_stat_raise_score_generic(score, target, stat_changes, desire_mult = 1)
|
||||||
total_increment = stat_changes.sum { |change| change[1] }
|
total_increment = stat_changes.sum { |change| change[1] }
|
||||||
# Prefer if move is a status move and it's the user's first/second turn
|
# Prefer if move is a status move and it's the user's first/second turn
|
||||||
@@ -164,9 +162,7 @@ class Battle::AI
|
|||||||
return score
|
return score
|
||||||
end
|
end
|
||||||
|
|
||||||
#=============================================================================
|
|
||||||
# Make score changes based on the raising of a specific stat.
|
# Make score changes based on the raising of a specific stat.
|
||||||
#=============================================================================
|
|
||||||
def get_target_stat_raise_score_one(score, target, stat, increment, desire_mult = 1)
|
def get_target_stat_raise_score_one(score, target, stat, increment, desire_mult = 1)
|
||||||
# Figure out how much the stat will actually change by
|
# Figure out how much the stat will actually change by
|
||||||
max_stage = Battle::Battler::STAT_STAGE_MAXIMUM
|
max_stage = Battle::Battler::STAT_STAGE_MAXIMUM
|
||||||
@@ -293,7 +289,8 @@ class Battle::AI
|
|||||||
return score
|
return score
|
||||||
end
|
end
|
||||||
|
|
||||||
#=============================================================================
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
# Main method for calculating the score for moves that lower a battler's
|
# Main method for calculating the score for moves that lower a battler's
|
||||||
# stat(s).
|
# stat(s).
|
||||||
# By default, assumes that a stat drop is a good thing. However, this score
|
# By default, assumes that a stat drop is a good thing. However, this score
|
||||||
@@ -301,7 +298,6 @@ class Battle::AI
|
|||||||
# inversion does not happen if the move could target a foe but is targeting an
|
# inversion does not happen if the move could target a foe but is targeting an
|
||||||
# ally, but only because it is inverted in def pbGetMoveScoreAgainstTarget
|
# ally, but only because it is inverted in def pbGetMoveScoreAgainstTarget
|
||||||
# instead.
|
# instead.
|
||||||
#=============================================================================
|
|
||||||
def get_score_for_target_stat_drop(score, target, stat_changes, whole_effect = true,
|
def get_score_for_target_stat_drop(score, target, stat_changes, whole_effect = true,
|
||||||
fixed_change = false, ignore_contrary = false)
|
fixed_change = false, ignore_contrary = false)
|
||||||
whole_effect = false if @move.damagingMove?
|
whole_effect = false if @move.damagingMove?
|
||||||
@@ -381,12 +377,12 @@ class Battle::AI
|
|||||||
return score
|
return score
|
||||||
end
|
end
|
||||||
|
|
||||||
#=============================================================================
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
# Returns whether the target lowering the given stat will have any impact.
|
# Returns whether the target lowering the given stat will have any impact.
|
||||||
# TODO: Make sure the move's actual damage category is taken into account,
|
# TODO: Make sure the move's actual damage category is taken into account,
|
||||||
# i.e. CategoryDependsOnHigherDamagePoisonTarget and
|
# i.e. CategoryDependsOnHigherDamagePoisonTarget and
|
||||||
# CategoryDependsOnHigherDamageIgnoreTargetAbility.
|
# CategoryDependsOnHigherDamageIgnoreTargetAbility.
|
||||||
#=============================================================================
|
|
||||||
def stat_drop_worthwhile?(target, stat, fixed_change = false)
|
def stat_drop_worthwhile?(target, stat, fixed_change = false)
|
||||||
if !fixed_change
|
if !fixed_change
|
||||||
return false if !target.battler.pbCanLowerStatStage?(stat, @user.battler, @move.move)
|
return false if !target.battler.pbCanLowerStatStage?(stat, @user.battler, @move.move)
|
||||||
@@ -431,9 +427,9 @@ class Battle::AI
|
|||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
#=============================================================================
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
# Make score changes based on the general concept of lowering stats at all.
|
# Make score changes based on the general concept of lowering stats at all.
|
||||||
#=============================================================================
|
|
||||||
def get_target_stat_drop_score_generic(score, target, stat_changes, desire_mult = 1)
|
def get_target_stat_drop_score_generic(score, target, stat_changes, desire_mult = 1)
|
||||||
total_decrement = stat_changes.sum { |change| change[1] }
|
total_decrement = stat_changes.sum { |change| change[1] }
|
||||||
# Prefer if move is a status move and it's the user's first/second turn
|
# Prefer if move is a status move and it's the user's first/second turn
|
||||||
@@ -452,9 +448,7 @@ class Battle::AI
|
|||||||
return score
|
return score
|
||||||
end
|
end
|
||||||
|
|
||||||
#=============================================================================
|
|
||||||
# Make score changes based on the lowering of a specific stat.
|
# Make score changes based on the lowering of a specific stat.
|
||||||
#=============================================================================
|
|
||||||
def get_target_stat_drop_score_one(score, target, stat, decrement, desire_mult = 1)
|
def get_target_stat_drop_score_one(score, target, stat, decrement, desire_mult = 1)
|
||||||
# Figure out how much the stat will actually change by
|
# Figure out how much the stat will actually change by
|
||||||
max_stage = Battle::Battler::STAT_STAGE_MAXIMUM
|
max_stage = Battle::Battler::STAT_STAGE_MAXIMUM
|
||||||
@@ -559,9 +553,8 @@ class Battle::AI
|
|||||||
return score
|
return score
|
||||||
end
|
end
|
||||||
|
|
||||||
#=============================================================================
|
#-----------------------------------------------------------------------------
|
||||||
#
|
|
||||||
#=============================================================================
|
|
||||||
def get_score_for_weather(weather, move_user, starting = false)
|
def get_score_for_weather(weather, move_user, starting = false)
|
||||||
return 0 if @battle.pbCheckGlobalAbility(:AIRLOCK) ||
|
return 0 if @battle.pbCheckGlobalAbility(:AIRLOCK) ||
|
||||||
@battle.pbCheckGlobalAbility(:CLOUDNINE)
|
@battle.pbCheckGlobalAbility(:CLOUDNINE)
|
||||||
@@ -677,9 +670,8 @@ class Battle::AI
|
|||||||
return ret
|
return ret
|
||||||
end
|
end
|
||||||
|
|
||||||
#=============================================================================
|
#-----------------------------------------------------------------------------
|
||||||
#
|
|
||||||
#=============================================================================
|
|
||||||
def get_score_for_terrain(terrain, move_user, starting = false)
|
def get_score_for_terrain(terrain, move_user, starting = false)
|
||||||
ret = 0
|
ret = 0
|
||||||
ret += 4 if starting && terrain != :None && move_user.has_active_item?(:TERRAINEXTENDER)
|
ret += 4 if starting && terrain != :None && move_user.has_active_item?(:TERRAINEXTENDER)
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ class Battle::AI::AIBattler
|
|||||||
def idxOpposingSide; return battler.idxOpposingSide; end
|
def idxOpposingSide; return battler.idxOpposingSide; end
|
||||||
def pbOpposingSide; return battler.pbOpposingSide; end
|
def pbOpposingSide; return battler.pbOpposingSide; end
|
||||||
|
|
||||||
#=============================================================================
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
# Returns how much damage this battler will take at the end of this round.
|
# Returns how much damage this battler will take at the end of this round.
|
||||||
def rough_end_of_round_damage
|
def rough_end_of_round_damage
|
||||||
@@ -160,7 +160,7 @@ class Battle::AI::AIBattler
|
|||||||
return ret
|
return ret
|
||||||
end
|
end
|
||||||
|
|
||||||
#=============================================================================
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
def base_stat(stat)
|
def base_stat(stat)
|
||||||
ret = 0
|
ret = 0
|
||||||
@@ -194,7 +194,7 @@ class Battle::AI::AIBattler
|
|||||||
return (this_speed > other_speed) ^ (@ai.battle.field.effects[PBEffects::TrickRoom] > 0)
|
return (this_speed > other_speed) ^ (@ai.battle.field.effects[PBEffects::TrickRoom] > 0)
|
||||||
end
|
end
|
||||||
|
|
||||||
#=============================================================================
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
def types; return battler.types; end
|
def types; return battler.types; end
|
||||||
def pbTypes(withExtraType = false); return battler.pbTypes(withExtraType); end
|
def pbTypes(withExtraType = false); return battler.pbTypes(withExtraType); end
|
||||||
@@ -228,7 +228,7 @@ class Battle::AI::AIBattler
|
|||||||
return ret
|
return ret
|
||||||
end
|
end
|
||||||
|
|
||||||
#=============================================================================
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
def ability_id; return battler.ability_id; end
|
def ability_id; return battler.ability_id; end
|
||||||
def ability; return battler.ability; end
|
def ability; return battler.ability; end
|
||||||
@@ -245,7 +245,7 @@ class Battle::AI::AIBattler
|
|||||||
return @ai.move.function == "IgnoreTargetAbility" || battler.hasMoldBreaker?
|
return @ai.move.function == "IgnoreTargetAbility" || battler.hasMoldBreaker?
|
||||||
end
|
end
|
||||||
|
|
||||||
#=============================================================================
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
def item_id; return battler.item_id; end
|
def item_id; return battler.item_id; end
|
||||||
def item; return battler.item; end
|
def item; return battler.item; end
|
||||||
@@ -258,7 +258,7 @@ class Battle::AI::AIBattler
|
|||||||
return battler.hasActiveItem?(item)
|
return battler.hasActiveItem?(item)
|
||||||
end
|
end
|
||||||
|
|
||||||
#=============================================================================
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
def check_for_move
|
def check_for_move
|
||||||
ret = false
|
ret = false
|
||||||
@@ -283,7 +283,7 @@ class Battle::AI::AIBattler
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
#=============================================================================
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
def can_attack?
|
def can_attack?
|
||||||
return false if self.effects[PBEffects::HyperBeam] > 0
|
return false if self.effects[PBEffects::HyperBeam] > 0
|
||||||
@@ -334,18 +334,20 @@ class Battle::AI::AIBattler
|
|||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
#=============================================================================
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
def wants_status_problem?(new_status)
|
def wants_status_problem?(new_status)
|
||||||
return true if new_status == :NONE
|
return true if new_status == :NONE
|
||||||
if ability_active?
|
if ability_active?
|
||||||
case ability_id
|
case ability_id
|
||||||
when :GUTS
|
when :GUTS
|
||||||
return true if stat_raise_worthwhile?(self, :ATTACK, true)
|
return true if ![:SLEEP, :FROZEN].include?(new_status) &&
|
||||||
|
stat_raise_worthwhile?(self, :ATTACK, true)
|
||||||
when :MARVELSCALE
|
when :MARVELSCALE
|
||||||
return true if stat_raise_worthwhile?(self, :DEFENSE, true)
|
return true if stat_raise_worthwhile?(self, :DEFENSE, true)
|
||||||
when :QUICKFEET
|
when :QUICKFEET
|
||||||
return true if stat_raise_worthwhile?(self, :SPEED, true)
|
return true if ![:SLEEP, :FROZEN].include?(new_status) &&
|
||||||
|
stat_raise_worthwhile?(self, :SPEED, true)
|
||||||
when :FLAREBOOST
|
when :FLAREBOOST
|
||||||
return true if new_status == :BURN && stat_raise_worthwhile?(self, :SPECIAL_ATTACK, true)
|
return true if new_status == :BURN && stat_raise_worthwhile?(self, :SPECIAL_ATTACK, true)
|
||||||
when :TOXICBOOST
|
when :TOXICBOOST
|
||||||
@@ -363,7 +365,7 @@ class Battle::AI::AIBattler
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
#=============================================================================
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
# TODO: Add more items.
|
# TODO: Add more items.
|
||||||
BASE_ITEM_RATINGS = {
|
BASE_ITEM_RATINGS = {
|
||||||
@@ -544,7 +546,7 @@ class Battle::AI::AIBattler
|
|||||||
return ret
|
return ret
|
||||||
end
|
end
|
||||||
|
|
||||||
#=============================================================================
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
# Items can be consumed by Stuff Cheeks, Teatime, Bug Bite/Pluck and Fling.
|
# Items can be consumed by Stuff Cheeks, Teatime, Bug Bite/Pluck and Fling.
|
||||||
def get_score_change_for_consuming_item(item, try_preserving_item = false)
|
def get_score_change_for_consuming_item(item, try_preserving_item = false)
|
||||||
@@ -629,7 +631,7 @@ class Battle::AI::AIBattler
|
|||||||
return ret
|
return ret
|
||||||
end
|
end
|
||||||
|
|
||||||
#=============================================================================
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
# These values are taken from the Complete-Fire-Red-Upgrade decomp here:
|
# These values are taken from the Complete-Fire-Red-Upgrade decomp here:
|
||||||
# https://github.com/Skeli789/Complete-Fire-Red-Upgrade/blob/f7f35becbd111c7e936b126f6328fc52d9af68c8/src/ability_battle_effects.c#L41
|
# https://github.com/Skeli789/Complete-Fire-Red-Upgrade/blob/f7f35becbd111c7e936b126f6328fc52d9af68c8/src/ability_battle_effects.c#L41
|
||||||
@@ -962,7 +964,7 @@ class Battle::AI::AIBattler
|
|||||||
return ret
|
return ret
|
||||||
end
|
end
|
||||||
|
|
||||||
#=============================================================================
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ class Battle::AI::AIMove
|
|||||||
"CategoryDependsOnHigherDamageIgnoreTargetAbility"].include?(function)
|
"CategoryDependsOnHigherDamageIgnoreTargetAbility"].include?(function)
|
||||||
end
|
end
|
||||||
|
|
||||||
#=============================================================================
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
# pp
|
# pp
|
||||||
# totalpp
|
# totalpp
|
||||||
@@ -41,7 +41,16 @@ class Battle::AI::AIMove
|
|||||||
def statusMove?; return @move.statusMove?; end
|
def statusMove?; return @move.statusMove?; end
|
||||||
def function; return @move.function; end
|
def function; return @move.function; end
|
||||||
|
|
||||||
#=============================================================================
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def type; return @move.type; end
|
||||||
|
|
||||||
|
def rough_type
|
||||||
|
return @move.pbCalcType(@ai.user.battler) if @ai.trainer.medium_skill?
|
||||||
|
return @move.type
|
||||||
|
end
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
def pbTarget(user)
|
def pbTarget(user)
|
||||||
return @move.pbTarget((user.is_a?(Battle::AI::AIBattler)) ? user.battler : user)
|
return @move.pbTarget((user.is_a?(Battle::AI::AIBattler)) ? user.battler : user)
|
||||||
@@ -70,7 +79,7 @@ class Battle::AI::AIMove
|
|||||||
return num_targets > 1
|
return num_targets > 1
|
||||||
end
|
end
|
||||||
|
|
||||||
#=============================================================================
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
def rough_priority(user)
|
def rough_priority(user)
|
||||||
ret = @move.pbPriority(user.battler)
|
ret = @move.pbPriority(user.battler)
|
||||||
@@ -81,16 +90,7 @@ class Battle::AI::AIMove
|
|||||||
return ret
|
return ret
|
||||||
end
|
end
|
||||||
|
|
||||||
#=============================================================================
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
def type; return @move.type; end
|
|
||||||
|
|
||||||
def rough_type
|
|
||||||
return @move.pbCalcType(@ai.user.battler) if @ai.trainer.medium_skill?
|
|
||||||
return @move.type
|
|
||||||
end
|
|
||||||
|
|
||||||
#=============================================================================
|
|
||||||
|
|
||||||
# Returns this move's base power, taking into account various effects that
|
# Returns this move's base power, taking into account various effects that
|
||||||
# modify it.
|
# modify it.
|
||||||
@@ -102,6 +102,7 @@ class Battle::AI::AIMove
|
|||||||
ret, self, @ai.user, @ai.target, @ai, @ai.battle)
|
ret, self, @ai.user, @ai.target, @ai, @ai.battle)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Full damage calculation.
|
||||||
def rough_damage
|
def rough_damage
|
||||||
base_dmg = base_power
|
base_dmg = base_power
|
||||||
return base_dmg if @move.is_a?(Battle::Move::FixedDamageMove)
|
return base_dmg if @move.is_a?(Battle::Move::FixedDamageMove)
|
||||||
@@ -363,13 +364,14 @@ class Battle::AI::AIMove
|
|||||||
return ret
|
return ret
|
||||||
end
|
end
|
||||||
|
|
||||||
#=============================================================================
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
def accuracy
|
def accuracy
|
||||||
return @move.pbBaseAccuracy(@ai.user.battler, @ai.target.battler) if @ai.trainer.medium_skill?
|
return @move.pbBaseAccuracy(@ai.user.battler, @ai.target.battler) if @ai.trainer.medium_skill?
|
||||||
return @move.accuracy
|
return @move.accuracy
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Full accuracy calculation.
|
||||||
def rough_accuracy
|
def rough_accuracy
|
||||||
# Determine user and target
|
# Determine user and target
|
||||||
user = @ai.user
|
user = @ai.user
|
||||||
@@ -479,8 +481,10 @@ class Battle::AI::AIMove
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
#=============================================================================
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Full critical hit chance calculation (returns the determined critical hit
|
||||||
|
# stage).
|
||||||
def rough_critical_hit_stage
|
def rough_critical_hit_stage
|
||||||
user = @ai.user
|
user = @ai.user
|
||||||
user_battler = user.battler
|
user_battler = user.battler
|
||||||
@@ -524,7 +528,7 @@ class Battle::AI::AIMove
|
|||||||
return crit_stage
|
return crit_stage
|
||||||
end
|
end
|
||||||
|
|
||||||
#=============================================================================
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
# Return values:
|
# Return values:
|
||||||
# 0: Regular additional effect chance or isn't an additional effect
|
# 0: Regular additional effect chance or isn't an additional effect
|
||||||
|
|||||||
@@ -589,9 +589,9 @@ Battle::AI::Handlers::MoveEffectScore.copy("UserFaintsHealAndCureReplacement",
|
|||||||
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("StartPerishCountsForAllBattlers",
|
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("StartPerishCountsForAllBattlers",
|
||||||
proc { |move, user, target, ai, battle|
|
proc { |move, user, target, ai, battle|
|
||||||
next true if target.effects[PBEffects::PerishSong] > 0
|
next true if target.effects[PBEffects::PerishSong] > 0
|
||||||
next true if Battle::AbilityEffects.triggerMoveImmunity(target.ability, user.battler, target.battler,
|
next false if !target.ability_active?
|
||||||
|
next Battle::AbilityEffects.triggerMoveImmunity(target.ability, user.battler, target.battler,
|
||||||
move.move, move.rough_type, battle, false)
|
move.move, move.rough_type, battle, false)
|
||||||
next false
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
Battle::AI::Handlers::MoveEffectScore.add("StartPerishCountsForAllBattlers",
|
Battle::AI::Handlers::MoveEffectScore.add("StartPerishCountsForAllBattlers",
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
# NOTE: The following clauses have battle code implementing them, but no class
|
||||||
|
# below to apply them:
|
||||||
|
# "drawclause"
|
||||||
|
# "modifiedselfdestructclause"
|
||||||
|
# "suddendeath"
|
||||||
|
|
||||||
#===============================================================================
|
#===============================================================================
|
||||||
#
|
#
|
||||||
#===============================================================================
|
#===============================================================================
|
||||||
@@ -58,7 +64,7 @@ end
|
|||||||
#
|
#
|
||||||
#===============================================================================
|
#===============================================================================
|
||||||
class PerishSongClause < BattleRule
|
class PerishSongClause < BattleRule
|
||||||
def setRule(battle); battle.rules["perishsong"] = true; end
|
def setRule(battle); battle.rules["perishsongclause"] = true; end
|
||||||
end
|
end
|
||||||
|
|
||||||
#===============================================================================
|
#===============================================================================
|
||||||
|
|||||||
Reference in New Issue
Block a user