mirror of
https://github.com/infinitefusion/infinitefusion-e18.git
synced 2025-12-07 21:24:59 +00:00
Waged war against TODO comments in the AI, some refactoring of AI
This commit is contained in:
@@ -94,6 +94,8 @@ module Battle::AI::Handlers
|
||||
GeneralMoveAgainstTargetScore = HandlerHash.new
|
||||
ShouldSwitch = HandlerHash.new
|
||||
ShouldNotSwitch = HandlerHash.new
|
||||
AbilityRanking = AbilityHandlerHash.new
|
||||
ItemRanking = ItemHandlerHash.new
|
||||
|
||||
def self.move_will_fail?(function_code, *args)
|
||||
return MoveFailureCheck.trigger(function_code, *args) || false
|
||||
@@ -151,4 +153,14 @@ module Battle::AI::Handlers
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
def self.modify_ability_ranking(ability, score, *args)
|
||||
ret = AbilityRanking.trigger(ability, score, *args)
|
||||
return (ret.nil?) ? score : ret
|
||||
end
|
||||
|
||||
def self.modify_item_ranking(item, score, *args)
|
||||
ret = ItemRanking.trigger(item, score, *args)
|
||||
return (ret.nil?) ? score : ret
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
#===============================================================================
|
||||
#
|
||||
#===============================================================================
|
||||
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
|
||||
@@ -63,7 +66,6 @@ class Battle::AI
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
# 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)
|
||||
@@ -111,8 +113,16 @@ class Battle::AI
|
||||
elsif entry_hazard_damage > 0
|
||||
score -= 50 * entry_hazard_damage / pkmn.hp
|
||||
end
|
||||
# TODO: Toxic Spikes.
|
||||
# TODO: Sticky Web.
|
||||
if !pkmn.hasItem?(:HEAVYDUTYBOOTS) && !pokemon_airborne?(pkmn)
|
||||
# Toxic Spikes
|
||||
if @user.pbOwnSide.effects[PBEffects::ToxicSpikes] > 0
|
||||
score -= 20 if pokemon_can_be_poisoned?(pkmn)
|
||||
end
|
||||
# Sticky Web
|
||||
if @user.pbOwnSide.effects[PBEffects::ToxicSpikes]
|
||||
score -= 15
|
||||
end
|
||||
end
|
||||
# Predict effectiveness of foe's last used move against pkmn
|
||||
each_foe_battler(@user.side) do |b, i|
|
||||
next if !b.battler.lastMoveUsed
|
||||
@@ -132,9 +142,16 @@ class Battle::AI
|
||||
score += m.power * Effectiveness.calculate(m.type, *bTypes) / 10
|
||||
end
|
||||
end
|
||||
# Prefer if pkmn has lower HP and its position will be healed by Wish
|
||||
position = @battle.positions[idxBattler]
|
||||
if position.effects[PBEffects::Wish] > 0
|
||||
amt = position.effects[PBEffects::WishAmount]
|
||||
if pkmn.totalhp - pkmn.hp > amt * 2 / 3
|
||||
score += 20 * [pkmn.totalhp - pkmn.hp, amt].min / pkmn.totalhp
|
||||
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
|
||||
|
||||
@@ -148,12 +165,9 @@ class Battle::AI
|
||||
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
|
||||
if @battle.sides[side].effects[PBEffects::Spikes] > 0 && !pokemon_airborne?(pkmn)
|
||||
spikes_div = [8, 6, 4][@battle.sides[side].effects[PBEffects::Spikes] - 1]
|
||||
ret += pkmn.totalhp / spikes_div
|
||||
end
|
||||
return ret
|
||||
end
|
||||
@@ -218,7 +232,6 @@ Battle::AI::Handlers::ShouldSwitch.add(:significant_eor_damage,
|
||||
# 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|
|
||||
@@ -250,15 +263,25 @@ Battle::AI::Handlers::ShouldSwitch.add(:cure_status_problem_by_switching_out,
|
||||
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
|
||||
if ai.pbAIRandom(100) < 70
|
||||
PBDebug.log_ai("#{battler.name} wants to switch to cure its status problem with #{battler.ability.name}")
|
||||
next true
|
||||
end
|
||||
elsif battler.ability == :REGENERATOR
|
||||
# Heals 33% HP
|
||||
# Not worth healing if battler would lose more HP from switching back in later
|
||||
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
|
||||
# Don't bother if a foe is at low HP and could be knocked out instead
|
||||
if battler.check_for_move { |m| m.damagingMove? }
|
||||
weak_foe = false
|
||||
ai.each_foe_battler(battler.side) do |b, i|
|
||||
weak_foe = true if b.hp < b.totalhp / 3
|
||||
break if weak_foe
|
||||
end
|
||||
next false if weak_foe
|
||||
end
|
||||
if ai.pbAIRandom(100) < 70
|
||||
PBDebug.log_ai("#{battler.name} wants to switch to heal with #{battler.ability.name}")
|
||||
next true
|
||||
end
|
||||
@@ -276,7 +299,7 @@ Battle::AI::Handlers::ShouldSwitch.add(:wish_healing,
|
||||
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
|
||||
next false if battler.totalhp - battler.hp >= amt * 2 / 3 # Want to heal itself instead
|
||||
reserve_wants_healing_more = false
|
||||
reserves.each do |pkmn|
|
||||
entry_hazard_damage = ai.calculate_entry_hazard_damage(pkmn, battler.index & 1)
|
||||
@@ -453,24 +476,6 @@ Battle::AI::Handlers::ShouldSwitch.add(:foe_has_wonder_guard,
|
||||
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
|
||||
@@ -500,18 +505,11 @@ Battle::AI::Handlers::ShouldSwitch.add(:foe_has_wonder_guard,
|
||||
}
|
||||
)
|
||||
|
||||
#===============================================================================
|
||||
#
|
||||
#===============================================================================
|
||||
# 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|
|
||||
@@ -525,11 +523,8 @@ Battle::AI::Handlers::ShouldSwitch.add(:absorb_foe_move,
|
||||
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_power = 100 if move.is_a?(Battle::Move::OHKO)
|
||||
m_type = move.pbCalcType(b.battler)
|
||||
foe_moves.push([m_power, m_type, move])
|
||||
end
|
||||
@@ -542,16 +537,16 @@ Battle::AI::Handlers::ShouldSwitch.add(:absorb_foe_move,
|
||||
chosen_move = foe_moves[ai.pbAIRandom(foe_moves.length)] # Random move
|
||||
end
|
||||
# Get the chosen move's information
|
||||
move_power = chosen_move[0]
|
||||
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).
|
||||
# Don't bother if the foe's best move isn't too strong
|
||||
next false if move_power < 70
|
||||
# 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) }
|
||||
next false if ai.pbAIRandom(100) < 70
|
||||
PBDebug.log_ai("#{battler.name} wants to switch because it can't absorb a foe's move but a reserve can")
|
||||
next true
|
||||
end
|
||||
@@ -559,18 +554,6 @@ Battle::AI::Handlers::ShouldSwitch.add(:absorb_foe_move,
|
||||
}
|
||||
)
|
||||
|
||||
#===============================================================================
|
||||
#
|
||||
#===============================================================================
|
||||
# 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.
|
||||
@@ -590,14 +573,6 @@ Battle::AI::Handlers::ShouldSwitch.add(:sudden_death,
|
||||
}
|
||||
)
|
||||
|
||||
#===============================================================================
|
||||
#
|
||||
#===============================================================================
|
||||
# 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.
|
||||
@@ -664,19 +639,20 @@ Battle::AI::Handlers::ShouldNotSwitch.add(:lethal_entry_hazards,
|
||||
#===============================================================================
|
||||
Battle::AI::Handlers::ShouldNotSwitch.add(:battler_has_super_effective_move,
|
||||
proc { |battler, reserves, ai, battle|
|
||||
next false if battler.effects[PBEffects::PerishSong] == 1
|
||||
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.
|
||||
# NOTE: Ideally this would ignore moves that are unusable, but that would
|
||||
# be too 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?
|
||||
# NOTE: Ideally this would ignore foes that move cannot target, but that
|
||||
# is complicated enough to implement that I'm not bothering. It's
|
||||
# also rare that it would matter.
|
||||
eff = b.effectiveness_of_type_against_battler(move_type, battler)
|
||||
has_super_effective_move = Effectiveness.super_effective?(eff)
|
||||
break if has_super_effective_move
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
#===============================================================================
|
||||
#
|
||||
#===============================================================================
|
||||
class Battle::AI
|
||||
HP_HEAL_ITEMS = {
|
||||
:POTION => 20,
|
||||
@@ -74,13 +77,6 @@ class Battle::AI
|
||||
:REVIVALHERB => 7,
|
||||
:MAXHONEY => 7
|
||||
}
|
||||
# TODO: Add more items for the AI to use from their Bag:
|
||||
# Confusion healing (Yellow Flute, Persim Berry)
|
||||
# Infatuation healing (Red Flute)
|
||||
# PP (Either, Max Ether, Leppa Berry, Elixir, Max Elixir)
|
||||
# Dire Hit (and 2 and 3)
|
||||
# Guard Spec.
|
||||
# Poké Flute (awakens all battlers)
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
@@ -175,7 +171,7 @@ class Battle::AI
|
||||
want_to_cure_status = @battlers[battler.index].wants_status_problem?(pkmn.status)
|
||||
want_to_cure_status = false if pkmn.status == :SLEEP && pkmn.statusCount <= 2
|
||||
end
|
||||
want_to_cure_status ||= (battler.effects[PBEffects::Confusion] > 0)
|
||||
want_to_cure_status ||= (battler.effects[PBEffects::Confusion] > 1)
|
||||
end
|
||||
if HP_HEAL_ITEMS.include?(item)
|
||||
if pkmn.hp < pkmn.totalhp
|
||||
@@ -1,7 +1,8 @@
|
||||
#===============================================================================
|
||||
#
|
||||
#===============================================================================
|
||||
class Battle::AI
|
||||
# Decide whether the opponent should Mega Evolve.
|
||||
# TODO: Where relevant, pretend the user is Mega Evolved if it isn't but can
|
||||
# be.
|
||||
def pbEnemyShouldMegaEvolve?
|
||||
if @battle.pbCanMegaEvolve?(@user.index) # Simple "always should if possible"
|
||||
PBDebug.log_ai("#{@user.name} will Mega Evolve")
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
#===============================================================================
|
||||
#
|
||||
#===============================================================================
|
||||
class Battle::AI
|
||||
MOVE_FAIL_SCORE = 20
|
||||
MOVE_USELESS_SCORE = 60 # Move predicted to do nothing or just be detrimental
|
||||
@@ -58,8 +61,6 @@ class Battle::AI
|
||||
@battle.allBattlers.each do |b|
|
||||
next if redirected_target && b.index != redirected_target
|
||||
next if !@battle.pbMoveCanTarget?(@user.battler.index, b.index, target_data)
|
||||
# TODO: Should this sometimes consider targeting an ally? See def
|
||||
# pbGetMoveScoreAgainstTarget for more information.
|
||||
next if target_data.targets_foe && !@user.battler.opposes?(b)
|
||||
PBDebug.log_ai("#{@user.name} is considering using #{orig_move.name} against #{b.name} (#{b.index})...")
|
||||
score = MOVE_BASE_SCORE
|
||||
@@ -199,9 +200,6 @@ class Battle::AI
|
||||
return true if @battle.field.terrain == :Psychic && @target.battler.affectedByTerrain? &&
|
||||
@target.opposes?(@user) && @move.rough_priority(@user) > 0
|
||||
# Immunity because of ability
|
||||
# TODO: If an ally has such an ability, may want to just not prefer the move
|
||||
# instead of predicting its failure, as might want to hit the ally
|
||||
# after all.
|
||||
return true if @move.move.pbImmunityByAbility(@user.battler, @target.battler, false)
|
||||
# Immunity because of Dazzling/Queenly Majesty
|
||||
if @move.rough_priority(@user) > 0 && @target.opposes?(@user)
|
||||
@@ -293,15 +291,6 @@ class Battle::AI
|
||||
# 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
|
||||
# previously been called.
|
||||
# TODO: Add something in here (I think) to specially score moves used against
|
||||
# an ally and the ally has an ability that will benefit from being hit
|
||||
# by the move.
|
||||
# TODO: The above also applies if the move is Heal Pulse or a few other moves
|
||||
# like that, which CAN target a foe but you'd never do so. Maybe use a
|
||||
# move flag to determine such moves? The implication is that such moves
|
||||
# wouldn't apply the "185 - score" bit, which would make their
|
||||
# MoveHandlers do the opposite calculations to other moves with the same
|
||||
# targets, but is this desirable?
|
||||
def pbGetMoveScoreAgainstTarget
|
||||
# Predict whether the move will fail against the target
|
||||
if @trainer.has_skill_flag?("PredictMoveFailure") && pbPredictMoveFailureAgainstTarget
|
||||
@@ -327,9 +316,8 @@ class Battle::AI
|
||||
PBDebug.log(" move is useless against #{@target.name}")
|
||||
return -1
|
||||
end
|
||||
# TODO: Is this reversal of the score okay?
|
||||
old_score = score
|
||||
score = 185 - score
|
||||
score = ((1.85 * MOVE_BASE_SCORE) - score).to_i
|
||||
PBDebug.log_score_change(score - old_score, "score inverted (move targets ally but can target foe)")
|
||||
end
|
||||
return score
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
#===============================================================================
|
||||
#
|
||||
#===============================================================================
|
||||
class Battle::AI
|
||||
# Main method for calculating the score for moves that raise a battler's
|
||||
# stat(s).
|
||||
@@ -89,14 +92,10 @@ class Battle::AI
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
# 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,
|
||||
# i.e. CategoryDependsOnHigherDamagePoisonTarget and
|
||||
# CategoryDependsOnHigherDamageIgnoreTargetAbility.
|
||||
def stat_raise_worthwhile?(target, stat, fixed_change = false)
|
||||
if !fixed_change
|
||||
return false if !target.battler.pbCanRaiseStatStage?(stat, @user.battler, @move.move)
|
||||
end
|
||||
# TODO: Not worth it if target is predicted to switch out (except via Baton Pass).
|
||||
# Check if target won't benefit from the stat being raised
|
||||
return true if target.has_move_with_function?("SwitchOutUserPassOnEffects",
|
||||
"PowerHigherWithUserPositiveStatStages")
|
||||
@@ -125,17 +124,29 @@ class Battle::AI
|
||||
"PowerHigherWithUserPositiveStatStages"
|
||||
]
|
||||
if !target.has_move_with_function?(*moves_that_prefer_high_speed)
|
||||
# TODO: Not worth it if the target is too much slower than its foe(s)
|
||||
# and can't be made fast enough.
|
||||
meaningful = false
|
||||
target_speed = target.rough_stat(:SPEED)
|
||||
each_foe_battler(target.side) do |b, i|
|
||||
return true if b.faster_than?(target)
|
||||
b_speed = b.rough_stat(:SPEED)
|
||||
meaningful = true if target_speed < b_speed && target_speed * 2.5 > b_speed
|
||||
break if meaningful
|
||||
end
|
||||
return false
|
||||
return false if !meaningful
|
||||
end
|
||||
when :ACCURACY
|
||||
# TODO: Yes if any of target's moves have lower accuracy, or target is
|
||||
# affected by accuracy-lowering effects, or if target's foe(s) have
|
||||
# increased evasion.
|
||||
min_accuracy = 100
|
||||
target.battler.moves.each do |m|
|
||||
next if m.accuracy == 0 || m.is_a?(Battle::Move::OHKO)
|
||||
min_accuracy = m.accuracy if m.accuracy < min_accuracy
|
||||
end
|
||||
if min_accuracy >= 90 && target.stages[:ACCURACY] >= 0
|
||||
meaningful = false
|
||||
each_foe_battler(target.side) do |b, i|
|
||||
meaningful = true if b.stages[:EVASION] > 0
|
||||
break if meaningful
|
||||
end
|
||||
return false if !meaningful
|
||||
end
|
||||
when :EVASION
|
||||
end
|
||||
return true
|
||||
@@ -158,7 +169,8 @@ class Battle::AI
|
||||
# Prefer if target is at high HP, don't prefer if target is at low HP
|
||||
score += total_increment * desire_mult * ((100 * target.hp / target.totalhp) - 50) / 8 # +6 to -6 per stage
|
||||
end
|
||||
# TODO: Look at abilities that trigger upon stat raise. There are none.
|
||||
# NOTE: There are no abilities that trigger upon stat raise, but this is
|
||||
# where they would be accounted for if they existed.
|
||||
return score
|
||||
end
|
||||
|
||||
@@ -230,8 +242,6 @@ class Battle::AI
|
||||
break
|
||||
end
|
||||
end
|
||||
# TODO: Prefer if the target is able to cause flinching (moves that
|
||||
# flinch, or has King's Rock/Stench).
|
||||
# Prefer if the target has Electro Ball or Power Trip/Stored Power
|
||||
moves_that_prefer_high_speed = [
|
||||
"PowerHigherWithUserFasterThanTarget",
|
||||
@@ -380,14 +390,10 @@ class Battle::AI
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
# 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,
|
||||
# i.e. CategoryDependsOnHigherDamagePoisonTarget and
|
||||
# CategoryDependsOnHigherDamageIgnoreTargetAbility.
|
||||
def stat_drop_worthwhile?(target, stat, fixed_change = false)
|
||||
if !fixed_change
|
||||
return false if !target.battler.pbCanLowerStatStage?(stat, @user.battler, @move.move)
|
||||
end
|
||||
# TODO: Not worth it if target is predicted to switch out (except via Baton Pass).
|
||||
# Check if target won't benefit from the stat being lowered
|
||||
case stat
|
||||
when :ATTACK
|
||||
@@ -414,14 +420,22 @@ class Battle::AI
|
||||
"PowerHigherWithUserPositiveStatStages"
|
||||
]
|
||||
if !target.has_move_with_function?(*moves_that_prefer_high_speed)
|
||||
# TODO: Not worth it if the target is too much faster than its foe(s)
|
||||
# and can't be brought slow enough.
|
||||
meaningful = false
|
||||
target_speed = target.rough_stat(:SPEED)
|
||||
each_foe_battler(target.side) do |b, i|
|
||||
return true if !b.faster_than?(target)
|
||||
b_speed = b.rough_stat(:SPEED)
|
||||
meaningful = true if target_speed > b_speed && target_speed < b_speed * 2.5
|
||||
break if meaningful
|
||||
end
|
||||
return false
|
||||
return false if !meaningful
|
||||
end
|
||||
when :ACCURACY
|
||||
meaningful = false
|
||||
target.battler.moves.each do |m|
|
||||
meaningful = true if m.accuracy > 0 && !m.is_a?(Battle::Move::OHKO)
|
||||
break if meaningful
|
||||
end
|
||||
return false if !meaningful
|
||||
when :EVASION
|
||||
end
|
||||
return true
|
||||
@@ -444,7 +458,11 @@ class Battle::AI
|
||||
# Prefer if target is at high HP, don't prefer if target is at low HP
|
||||
score += total_decrement * desire_mult * ((100 * target.hp / target.totalhp) - 50) / 8 # +6 to -6 per stage
|
||||
end
|
||||
# TODO: Look at abilities that trigger upon stat lowering.
|
||||
# Don't prefer if target has an ability that triggers upon stat loss
|
||||
# (Competitive, Defiant)
|
||||
if target.opposes?(@user) && Battle::AbilityEffects::OnStatLoss[target.ability]
|
||||
score -= 10
|
||||
end
|
||||
return score
|
||||
end
|
||||
|
||||
@@ -532,7 +550,6 @@ class Battle::AI
|
||||
else
|
||||
score += 10 * dec_mult
|
||||
end
|
||||
# TODO: Prefer if target is poisoned/toxiced/Leech Seeded/cursed.
|
||||
when :EVASION
|
||||
# Modify score depending on current stat stage
|
||||
if old_stage <= -2 && decrement == 1
|
||||
@@ -579,7 +596,12 @@ class Battle::AI
|
||||
if b.has_damaging_move_of_type?(:WATER)
|
||||
ret += (b.opposes?(move_user)) ? 10 : -10
|
||||
end
|
||||
# TODO: Check for freezing moves.
|
||||
# Check for moves that freeze
|
||||
if b.has_move_with_function?("FreezeTarget", "FreezeFlinchTarget") ||
|
||||
(b.has_move_with_function?("EffectDependsOnEnvironment") &&
|
||||
[:Snow, :Ice].include?(@battle.environment))
|
||||
ret += (b.opposes?(move_user)) ? 5 : -5
|
||||
end
|
||||
when :Rain
|
||||
# Check for Fire/Water moves
|
||||
if b.has_damaging_move_of_type?(:WATER)
|
||||
@@ -681,7 +703,6 @@ class Battle::AI
|
||||
case terrain
|
||||
when :Electric
|
||||
# Immunity to sleep
|
||||
# TODO: Check all battlers for sleep-inducing moves and other effects?
|
||||
if b.status == :NONE
|
||||
ret += (b.opposes?(move_user)) ? -8 : 8
|
||||
end
|
||||
@@ -701,8 +722,6 @@ class Battle::AI
|
||||
end
|
||||
when :Misty
|
||||
# Immunity to status problems/confusion
|
||||
# TODO: Check all battlers for status/confusion-inducing moves and other
|
||||
# effects?
|
||||
if b.status == :NONE || b.effects[PBEffects::Confusion] == 0
|
||||
ret += (b.opposes?(move_user)) ? -8 : 8
|
||||
end
|
||||
|
||||
@@ -49,7 +49,6 @@ Battle::AI::Handlers::GeneralMoveScore.add(:thawing_move_when_frozen,
|
||||
# Prefer using a priority move if the user is slower than the target and...
|
||||
# - the user is at low HP, or
|
||||
# - the target is predicted to be knocked out by the move.
|
||||
# TODO: Less prefer a priority move if any foe knows Quick Guard?
|
||||
#===============================================================================
|
||||
Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:priority_move_against_faster_target,
|
||||
proc { |score, move, user, target, ai, battle|
|
||||
@@ -66,6 +65,16 @@ Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:priority_move_against_f
|
||||
score += 8
|
||||
PBDebug.log_score_change(score - old_score, "target at low HP and move has priority over faster target")
|
||||
end
|
||||
# Any foe knows Quick Guard and can protect against priority moves
|
||||
old_score = score
|
||||
ai.each_foe_battler(user.side) do |b, i|
|
||||
next if !b.has_move_with_function?("ProtectUserSideFromPriorityMoves")
|
||||
next if Settings::MECHANICS_GENERATION <= 5 && b.effects[PBEffects::ProtectRate] > 1
|
||||
score -= 5
|
||||
end
|
||||
if score != old_score
|
||||
PBDebug.log_score_change(score - old_score, "a foe knows Quick Guard and may protect against priority moves")
|
||||
end
|
||||
end
|
||||
next score
|
||||
}
|
||||
@@ -332,8 +341,8 @@ Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:thawing_move_against_fr
|
||||
#
|
||||
#===============================================================================
|
||||
# TODO: Check all effects that trigger upon using a move, including per-hit
|
||||
# stuff in def pbEffectsOnMakingHit and end-of-move stuff in def
|
||||
# pbEffectsAfterMove.
|
||||
# stuff in def pbEffectsOnMakingHit (worse if the move is a multi-hit one)
|
||||
# and end-of-move stuff in def pbEffectsAfterMove.
|
||||
|
||||
#===============================================================================
|
||||
#
|
||||
|
||||
@@ -1,71 +1,9 @@
|
||||
#===============================================================================
|
||||
#
|
||||
#===============================================================================
|
||||
class Battle::AI
|
||||
def pbAIRandom(x); return rand(x); end
|
||||
|
||||
def pbStdDev(choices)
|
||||
sum = 0
|
||||
n = 0
|
||||
choices.each do |c|
|
||||
sum += c[1]
|
||||
n += 1
|
||||
end
|
||||
return 0 if n < 2
|
||||
mean = sum.to_f / n
|
||||
varianceTimesN = 0
|
||||
choices.each do |c|
|
||||
next if c[1] <= 0
|
||||
deviation = c[1].to_f - mean
|
||||
varianceTimesN += deviation * deviation
|
||||
end
|
||||
# Using population standard deviation
|
||||
# [(n-1) makes it a sample std dev, would be 0 with only 1 sample]
|
||||
return Math.sqrt(varianceTimesN / n)
|
||||
end
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
# Move's type effectiveness. For switching. Determines the effectiveness of a
|
||||
# potential switch-in against an opposing battler.
|
||||
# TODO: Unused.
|
||||
def pbCalcTypeModPokemon(pkmn, target_battler)
|
||||
ret = Effectiveness::NORMAL_EFFECTIVE_MULTIPLIER
|
||||
pkmn.types.each do |thisType|
|
||||
ret *= Effectiveness.calculate(thisType, *target_battler.types)
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
# Assumes that pkmn's ability is not negated by a global effect (e.g.
|
||||
# Neutralizing Gas).
|
||||
# pkmn is either a Battle::AI::AIBattler or a Pokemon. move is a Battle::Move.
|
||||
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
|
||||
@@ -95,4 +33,439 @@ class Battle::AI
|
||||
yield battler, i if i != index && i.even? == index.even?
|
||||
end
|
||||
end
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
# Assumes that pkmn's ability is not negated by a global effect (e.g.
|
||||
# Neutralizing Gas).
|
||||
# pkmn is either a Battle::AI::AIBattler or a Pokemon. move is a Battle::Move.
|
||||
def pokemon_can_absorb_move?(pkmn, move, move_type)
|
||||
return false if pkmn.is_a?(Battle::AI::AIBattler) && !pkmn.ability_active?
|
||||
# Check pkmn's ability
|
||||
# Anything with a Battle::AbilityEffects::MoveImmunity handler
|
||||
case pkmn.ability_id
|
||||
when :BULLETPROOF
|
||||
return move.bombMove?
|
||||
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
|
||||
|
||||
# Used by Toxic Spikes.
|
||||
def pokemon_can_be_poisoned?(pkmn)
|
||||
# Check pkmn's immunity to being poisoned
|
||||
return false if @battle.field.terrain == :Misty
|
||||
return false if pkmn.hasType?(:POISON)
|
||||
return false if pkmn.hasType?(:STEEL)
|
||||
return false if pkmn.hasAbility?(:IMMUNITY)
|
||||
return false if pkmn.hasAbility?(:PASTELVEIL)
|
||||
return false if pkmn.hasAbility?(:FLOWERVEIL) && pkmn.hasType?(:GRASS)
|
||||
return false if pkmn.hasAbility?(:LEAFGUARD) && [:Sun, :HarshSun].include?(@battle.pbWeather)
|
||||
return false if pkmn.hasAbility?(:COMATOSE) && pkmn.isSpecies?(:KOMALA)
|
||||
return false if pkmn.hasAbility?(:SHIELDSDOWN) && pkmn.isSpecies?(:MINIOR) && pkmn.form < 7
|
||||
return true
|
||||
end
|
||||
|
||||
def pokemon_airborne?(pkmn)
|
||||
return false if pkmn.hasItem?(:IRONBALL)
|
||||
return false if @battle.field.effects[PBEffects::Gravity] > 0
|
||||
return true if pkmn.hasType?(:FLYING)
|
||||
return true if pkmn.hasAbility?(:LEVITATE)
|
||||
return true if pkmn.hasItem?(:AIRBALLOON)
|
||||
return false
|
||||
end
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
# 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
|
||||
BASE_ABILITY_RATINGS = {
|
||||
10 => [:DELTASTREAM, :DESOLATELAND, :HUGEPOWER, :MOODY, :PARENTALBOND,
|
||||
:POWERCONSTRUCT, :PRIMORDIALSEA, :PUREPOWER, :SHADOWTAG,
|
||||
:STANCECHANGE, :WONDERGUARD],
|
||||
9 => [:ARENATRAP, :DRIZZLE, :DROUGHT, :IMPOSTER, :MAGICBOUNCE, :MAGICGUARD,
|
||||
:MAGNETPULL, :SANDSTREAM, :SPEEDBOOST],
|
||||
8 => [:ADAPTABILITY, :AERILATE, :CONTRARY, :DISGUISE, :DRAGONSMAW,
|
||||
:ELECTRICSURGE, :GALVANIZE, :GRASSYSURGE, :ILLUSION, :LIBERO,
|
||||
:MISTYSURGE, :MULTISCALE, :MULTITYPE, :NOGUARD, :POISONHEAL,
|
||||
:PIXILATE, :PRANKSTER, :PROTEAN, :PSYCHICSURGE, :REFRIGERATE,
|
||||
:REGENERATOR, :RKSSYSTEM, :SERENEGRACE, :SHADOWSHIELD, :SHEERFORCE,
|
||||
:SIMPLE, :SNOWWARNING, :TECHNICIAN, :TRANSISTOR, :WATERBUBBLE],
|
||||
7 => [:BEASTBOOST, :BULLETPROOF, :COMPOUNDEYES, :DOWNLOAD, :FURCOAT,
|
||||
:HUSTLE, :ICESCALES, :INTIMIDATE, :LEVITATE, :LIGHTNINGROD,
|
||||
:MEGALAUNCHER, :MOLDBREAKER, :MOXIE, :NATURALCURE, :SAPSIPPER,
|
||||
:SHEDSKIN, :SKILLLINK, :SOULHEART, :STORMDRAIN, :TERAVOLT, :THICKFAT,
|
||||
:TINTEDLENS, :TOUGHCLAWS, :TRIAGE, :TURBOBLAZE, :UNBURDEN,
|
||||
:VOLTABSORB, :WATERABSORB],
|
||||
6 => [:BATTLEBOND, :CHLOROPHYLL, :COMATOSE, :DARKAURA, :DRYSKIN,
|
||||
:FAIRYAURA, :FILTER, :FLASHFIRE, :FORECAST, :GALEWINGS, :GUTS,
|
||||
:INFILTRATOR, :IRONBARBS, :IRONFIST, :MIRRORARMOR, :MOTORDRIVE,
|
||||
:NEUROFORCE, :PRISMARMOR, :QUEENLYMAJESTY, :RECKLESS, :ROUGHSKIN,
|
||||
:SANDRUSH, :SCHOOLING, :SCRAPPY, :SHIELDSDOWN, :SOLIDROCK, :STAKEOUT,
|
||||
:STAMINA, :STEELWORKER, :STRONGJAW, :STURDY, :SWIFTSWIM, :TOXICBOOST,
|
||||
:TRACE, :UNAWARE, :VICTORYSTAR],
|
||||
5 => [:AFTERMATH, :AIRLOCK, :ANALYTIC, :BERSERK, :BLAZE, :CLOUDNINE,
|
||||
:COMPETITIVE, :CORROSION, :DANCER, :DAZZLING, :DEFIANT, :FLAREBOOST,
|
||||
:FLUFFY, :GOOEY, :HARVEST, :HEATPROOF, :INNARDSOUT, :LIQUIDVOICE,
|
||||
:MARVELSCALE, :MUMMY, :NEUTRALIZINGGAS, :OVERCOAT, :OVERGROW,
|
||||
:PRESSURE, :QUICKFEET, :ROCKHEAD, :SANDSPIT, :SHIELDDUST, :SLUSHRUSH,
|
||||
:SWARM, :TANGLINGHAIR, :TORRENT],
|
||||
4 => [:ANGERPOINT, :BADDREAMS, :CHEEKPOUCH, :CLEARBODY, :CURSEDBODY,
|
||||
:EARLYBIRD, :EFFECTSPORE, :FLAMEBODY, :FLOWERGIFT, :FULLMETALBODY,
|
||||
:GORILLATACTICS, :HYDRATION, :ICEFACE, :IMMUNITY, :INSOMNIA,
|
||||
:JUSTIFIED, :MERCILESS, :PASTELVEIL, :POISONPOINT, :POISONTOUCH,
|
||||
:RIPEN, :SANDFORCE, :SOUNDPROOF, :STATIC, :SURGESURFER, :SWEETVEIL,
|
||||
:SYNCHRONIZE, :VITALSPIRIT, :WATERCOMPACTION, :WATERVEIL,
|
||||
:WHITESMOKE, :WONDERSKIN],
|
||||
3 => [:AROMAVEIL, :AURABREAK, :COTTONDOWN, :DAUNTLESSSHIELD,
|
||||
:EMERGENCYEXIT, :GLUTTONY, :GULPMISSLE, :HYPERCUTTER, :ICEBODY,
|
||||
:INTREPIDSWORD, :LIMBER, :LIQUIDOOZE, :LONGREACH, :MAGICIAN,
|
||||
:OWNTEMPO, :PICKPOCKET, :RAINDISH, :RATTLED, :SANDVEIL,
|
||||
:SCREENCLEANER, :SNIPER, :SNOWCLOAK, :SOLARPOWER, :STEAMENGINE,
|
||||
:STICKYHOLD, :SUPERLUCK, :UNNERVE, :WIMPOUT],
|
||||
2 => [:BATTLEARMOR, :COLORCHANGE, :CUTECHARM, :DAMP, :GRASSPELT,
|
||||
:HUNGERSWITCH, :INNERFOCUS, :LEAFGUARD, :LIGHTMETAL, :MIMICRY,
|
||||
:OBLIVIOUS, :POWERSPOT, :PROPELLORTAIL, :PUNKROCK, :SHELLARMOR,
|
||||
:STALWART, :STEADFAST, :STEELYSPIRIT, :SUCTIONCUPS, :TANGLEDFEET,
|
||||
:WANDERINGSPIRIT, :WEAKARMOR],
|
||||
1 => [:BIGPECKS, :KEENEYE, :MAGMAARMOR, :PICKUP, :RIVALRY, :STENCH],
|
||||
0 => [:ANTICIPATION, :ASONECHILLINGNEIGH, :ASONEGRIMNEIGH, :BALLFETCH,
|
||||
:BATTERY, :CHILLINGNEIGH, :CURIOUSMEDICINE, :FLOWERVEIL, :FOREWARN,
|
||||
:FRIENDGUARD, :FRISK, :GRIMNEIGH, :HEALER, :HONEYGATHER, :ILLUMINATE,
|
||||
:MINUS, :PLUS, :POWEROFALCHEMY, :QUICKDRAW, :RECEIVER, :RUNAWAY,
|
||||
:SYMBIOSIS, :TELEPATHY, :UNSEENFIST],
|
||||
-1 => [:DEFEATIST, :HEAVYMETAL, :KLUTZ, :NORMALIZE, :PERISHBODY, :STALL,
|
||||
:ZENMODE],
|
||||
-2 => [:SLOWSTART, :TRUANT]
|
||||
}
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
# TODO: Add more items.
|
||||
BASE_ITEM_RATINGS = {
|
||||
4 => [:CHOICEBAND, :CHOICESCARF, :CHOICESPECS, :DEEPSEATOOTH, :LEFTOVERS,
|
||||
:LIGHTBALL, :THICKCLUB],
|
||||
3 => [:ADAMANTORB, :GRISEOUSORB, :LIFEORB, :LUSTROUSORB, :SOULDEW],
|
||||
2 => [:BLACKBELT, :BLACKGLASSES, :CHARCOAL, :DRAGONFANG, :HARDSTONE,
|
||||
:MAGNET, :METALCOAT, :MIRACLESEED, :MYSTICWATER, :NEVERMELTICE,
|
||||
:POISONBARB, :SHARPBEAK, :SILKSCARF, :SILVERPOWDER, :SOFTSAND,
|
||||
:SPELLTAG, :TWISTEDSPOON,
|
||||
:DRACOPLATE, :DREADPLATE, :EARTHPLATE, :FISTPLATE, :FLAMEPLATE,
|
||||
:ICICLEPLATE, :INSECTPLATE, :IRONPLATE, :MEADOWPLATE, :MINDPLATE,
|
||||
:PIXIEPLATE, :SKYPLATE, :SPLASHPLATE, :SPOOKYPLATE, :STONEPLATE,
|
||||
:TOXICPLATE, :ZAPPLATE,
|
||||
:ODDINCENSE, :ROCKINCENSE, :ROSEINCENSE, :SEAINCENSE, :WAVEINCENSE,
|
||||
:MUSCLEBAND, :WISEGLASSES],
|
||||
1 => [:METRONOME],
|
||||
-2 => [:LAGGINGTAIL, :STICKYBARB],
|
||||
-4 => [:BLACKSLUDGE, :FLAMEORB, :IRONBALL, :TOXICORB]
|
||||
}
|
||||
end
|
||||
|
||||
#===============================================================================
|
||||
#
|
||||
#===============================================================================
|
||||
|
||||
Battle::AI::Handlers::AbilityRanking.add(:BLAZE,
|
||||
proc { |ability, score, battler, ai|
|
||||
next score if battler.has_damaging_move_of_type?(:FIRE)
|
||||
next 0
|
||||
}
|
||||
)
|
||||
|
||||
Battle::AI::Handlers::AbilityRanking.add(:CUTECHARM,
|
||||
proc { |ability, score, battler, ai|
|
||||
next 0 if battler.gender == 2
|
||||
next score
|
||||
}
|
||||
)
|
||||
|
||||
Battle::AI::Handlers::AbilityRanking.copy(:CUTECHARM, :RIVALRY)
|
||||
|
||||
Battle::AI::Handlers::AbilityRanking.add(:FRIENDGUARD,
|
||||
proc { |ability, score, battler, ai|
|
||||
has_ally = false
|
||||
ai.each_ally(battler.side) { |b, i| has_ally = true }
|
||||
next score if has_ally
|
||||
next 0
|
||||
}
|
||||
)
|
||||
|
||||
Battle::AI::Handlers::AbilityRanking.copy(:FRIENDGUARD, :HEALER, :SYMBOISIS, :TELEPATHY)
|
||||
|
||||
Battle::AI::Handlers::AbilityRanking.add(:GALEWINGS,
|
||||
proc { |ability, score, battler, ai|
|
||||
next score if battler.check_for_move { |m| m.type == :FLYING }
|
||||
next 0
|
||||
}
|
||||
)
|
||||
|
||||
Battle::AI::Handlers::AbilityRanking.add(:HUGEPOWER,
|
||||
proc { |ability, score, battler, ai|
|
||||
next score if ai.stat_raise_worthwhile?(battler, :ATTACK, true)
|
||||
next 0
|
||||
}
|
||||
)
|
||||
|
||||
Battle::AI::Handlers::AbilityRanking.copy(:HUGEPOWER, :PUREPOWER)
|
||||
|
||||
Battle::AI::Handlers::AbilityRanking.add(:IRONFIST,
|
||||
proc { |ability, score, battler, ai|
|
||||
next score if battler.check_for_move { |m| m.punchingMove? }
|
||||
next 0
|
||||
}
|
||||
)
|
||||
|
||||
Battle::AI::Handlers::AbilityRanking.add(:LIQUIDVOICE,
|
||||
proc { |ability, score, battler, ai|
|
||||
next score if battler.check_for_move { |m| m.soundMove? }
|
||||
next 0
|
||||
}
|
||||
)
|
||||
|
||||
Battle::AI::Handlers::AbilityRanking.add(:MEGALAUNCHER,
|
||||
proc { |ability, score, battler, ai|
|
||||
next score if battler.check_for_move { |m| m.pulseMove? }
|
||||
next 0
|
||||
}
|
||||
)
|
||||
|
||||
Battle::AI::Handlers::AbilityRanking.add(:OVERGROW,
|
||||
proc { |ability, score, battler, ai|
|
||||
next score if battler.has_damaging_move_of_type?(:GRASS)
|
||||
next 0
|
||||
}
|
||||
)
|
||||
|
||||
Battle::AI::Handlers::AbilityRanking.add(:PRANKSTER,
|
||||
proc { |ability, score, battler, ai|
|
||||
next score if battler.check_for_move { |m| m.statusMove? }
|
||||
next 0
|
||||
}
|
||||
)
|
||||
|
||||
Battle::AI::Handlers::AbilityRanking.add(:PUNKROCK,
|
||||
proc { |ability, score, battler, ai|
|
||||
next score if battler.check_for_move { |m| m.damagingMove? && m.soundMove? }
|
||||
next 1
|
||||
}
|
||||
)
|
||||
|
||||
Battle::AI::Handlers::AbilityRanking.add(:RECKLESS,
|
||||
proc { |ability, score, battler, ai|
|
||||
next score if battler.check_for_move { |m| m.recoilMove? }
|
||||
next 0
|
||||
}
|
||||
)
|
||||
|
||||
Battle::AI::Handlers::AbilityRanking.add(:ROCKHEAD,
|
||||
proc { |ability, score, battler, ai|
|
||||
next score if battler.check_for_move { |m| m.recoilMove? && !m.is_a?(Battle::Move::CrashDamageIfFailsUnusableInGravity) }
|
||||
next 0
|
||||
}
|
||||
)
|
||||
|
||||
Battle::AI::Handlers::AbilityRanking.add(:RUNAWAY,
|
||||
proc { |ability, score, battler, ai|
|
||||
next 0 if battler.wild?
|
||||
next score
|
||||
}
|
||||
)
|
||||
|
||||
Battle::AI::Handlers::AbilityRanking.add(:SANDFORCE,
|
||||
proc { |ability, score, battler, ai|
|
||||
next score if battler.has_damaging_move_of_type?(:GROUND, :ROCK, :STEEL)
|
||||
next 2
|
||||
}
|
||||
)
|
||||
|
||||
Battle::AI::Handlers::AbilityRanking.add(:SKILLLINK,
|
||||
proc { |ability, score, battler, ai|
|
||||
next score if battler.check_for_move { |m| m.is_a?(Battle::Move::HitTwoToFiveTimes) }
|
||||
next 0
|
||||
}
|
||||
)
|
||||
|
||||
Battle::AI::Handlers::AbilityRanking.add(:STEELWORKER,
|
||||
proc { |ability, score, battler, ai|
|
||||
next score if battler.has_damaging_move_of_type?(:STEEL)
|
||||
next 0
|
||||
}
|
||||
)
|
||||
|
||||
Battle::AI::Handlers::AbilityRanking.add(:SWARM,
|
||||
proc { |ability, score, battler, ai|
|
||||
next score if battler.has_damaging_move_of_type?(:BUG)
|
||||
next 0
|
||||
}
|
||||
)
|
||||
|
||||
Battle::AI::Handlers::AbilityRanking.add(:TORRENT,
|
||||
proc { |ability, score, battler, ai|
|
||||
next score if battler.has_damaging_move_of_type?(:WATER)
|
||||
next 0
|
||||
}
|
||||
)
|
||||
|
||||
Battle::AI::Handlers::AbilityRanking.add(:TRIAGE,
|
||||
proc { |ability, score, battler, ai|
|
||||
next score if battler.check_for_move { |m| m.healingMove? }
|
||||
next 0
|
||||
}
|
||||
)
|
||||
|
||||
#===============================================================================
|
||||
#
|
||||
#===============================================================================
|
||||
|
||||
Battle::AI::Handlers::ItemRanking.add(:ADAMANTORB,
|
||||
proc { |item, score, battler, ai|
|
||||
next score if battler.battler.isSpecies?(:DIALGA) &&
|
||||
battler.has_damaging_move_of_type?(:DRAGON, :STEEL)
|
||||
next 0
|
||||
}
|
||||
)
|
||||
|
||||
Battle::AI::Handlers::ItemRanking.add(:BLACKSLUDGE,
|
||||
proc { |item, score, battler, ai|
|
||||
next 4 if battler.has_type?(:POISON)
|
||||
next score
|
||||
}
|
||||
)
|
||||
|
||||
Battle::AI::Handlers::ItemRanking.add(:CHOICEBAND,
|
||||
proc { |item, score, battler, ai|
|
||||
next score if battler.check_for_move { |m| m.physicalMove?(m.type) }
|
||||
next 0
|
||||
}
|
||||
)
|
||||
|
||||
Battle::AI::Handlers::ItemRanking.copy(:CHOICEBAND, :MUSCLEBAND)
|
||||
|
||||
Battle::AI::Handlers::ItemRanking.add(:CHOICESPECS,
|
||||
proc { |item, score, battler, ai|
|
||||
next score if battler.check_for_move { |m| m.specialMove?(m.type) }
|
||||
next 0
|
||||
}
|
||||
)
|
||||
|
||||
Battle::AI::Handlers::ItemRanking.copy(:CHOICESPECS, :WISEGLASSES)
|
||||
|
||||
Battle::AI::Handlers::ItemRanking.add(:DEEPSEATOOTH,
|
||||
proc { |item, score, battler, ai|
|
||||
next score if battler.battler.isSpecies?(:CLAMPERL) &&
|
||||
battler.check_for_move { |m| m.specialMove?(m.type) }
|
||||
next 0
|
||||
}
|
||||
)
|
||||
|
||||
Battle::AI::Handlers::ItemRanking.add(:GRISEOUSORB,
|
||||
proc { |item, score, battler, ai|
|
||||
next score if battler.battler.isSpecies?(:GIRATINA) &&
|
||||
battler.has_damaging_move_of_type?(:DRAGON, :GHOST)
|
||||
next 0
|
||||
}
|
||||
)
|
||||
|
||||
Battle::AI::Handlers::ItemRanking.add(:IRONBALL,
|
||||
proc { |item, score, battler, ai|
|
||||
next 0 if battler.has_move_with_function?("ThrowUserItemAtTarget")
|
||||
next score
|
||||
}
|
||||
)
|
||||
|
||||
Battle::AI::Handlers::ItemRanking.add(:LIGHTBALL,
|
||||
proc { |item, score, battler, ai|
|
||||
next score if battler.battler.isSpecies?(:PIKACHU) &&
|
||||
battler.check_for_move { |m| m.damagingMove? }
|
||||
next 0
|
||||
}
|
||||
)
|
||||
|
||||
Battle::AI::Handlers::ItemRanking.add(:LUSTROUSORB,
|
||||
proc { |item, score, battler, ai|
|
||||
next score if battler.battler.isSpecies?(:PALKIA) &&
|
||||
battler.has_damaging_move_of_type?(:DRAGON, :WATER)
|
||||
next 0
|
||||
}
|
||||
)
|
||||
|
||||
Battle::AI::Handlers::ItemRanking.add(:SOULDEW,
|
||||
proc { |item, score, battler, ai|
|
||||
next 0 if !battler.battler.isSpecies?(:LATIAS) && !battler.battler.isSpecies?(:LATIOS)
|
||||
if Settings::SOUL_DEW_POWERS_UP_TYPES
|
||||
next 0 if !battler.has_damaging_move_of_type?(:PSYCHIC, :DRAGON)
|
||||
elsif !battler.check_for_move { |m| m.specialMove?(m.type) }
|
||||
next 1 # Also boosts SpDef
|
||||
end
|
||||
next score
|
||||
}
|
||||
)
|
||||
|
||||
Battle::AI::Handlers::ItemRanking.add(:THICKCLUB,
|
||||
proc { |item, score, battler, ai|
|
||||
next score if (battler.battler.isSpecies?(:CUBONE) || battler.battler.isSpecies?(:MAROWAK)) &&
|
||||
battler.check_for_move { |m| m.physicalMove?(m.type) }
|
||||
next 0
|
||||
}
|
||||
)
|
||||
|
||||
Battle::AI::Handlers::ItemRanking.addIf(:type_boosting_items,
|
||||
proc { |item|
|
||||
next [:BLACKBELT, :BLACKGLASSES, :CHARCOAL, :DRAGONFANG, :HARDSTONE,
|
||||
:MAGNET, :METALCOAT, :MIRACLESEED, :MYSTICWATER, :NEVERMELTICE,
|
||||
:POISONBARB, :SHARPBEAK, :SILKSCARF, :SILVERPOWDER, :SOFTSAND,
|
||||
:SPELLTAG, :TWISTEDSPOON,
|
||||
:DRACOPLATE, :DREADPLATE, :EARTHPLATE, :FISTPLATE, :FLAMEPLATE,
|
||||
:ICICLEPLATE, :INSECTPLATE, :IRONPLATE, :MEADOWPLATE, :MINDPLATE,
|
||||
:PIXIEPLATE, :SKYPLATE, :SPLASHPLATE, :SPOOKYPLATE, :STONEPLATE,
|
||||
:TOXICPLATE, :ZAPPLATE,
|
||||
:ODDINCENSE, :ROCKINCENSE, :ROSEINCENSE, :SEAINCENSE, :WAVEINCENSE].include?(item)
|
||||
},
|
||||
proc { |item, score, battler, ai|
|
||||
boosters = {
|
||||
:BUG => [:SILVERPOWDER, :INSECTPLATE],
|
||||
:DARK => [:BLACKGLASSES, :DREADPLATE],
|
||||
:DRAGON => [:DRAGONFANG, :DRACOPLATE],
|
||||
:ELECTRIC => [:MAGNET, :ZAPPLATE],
|
||||
:FAIRY => [:PIXIEPLATE],
|
||||
:FIGHTING => [:BLACKBELT, :FISTPLATE],
|
||||
:FIRE => [:CHARCOAL, :FLAMEPLATE],
|
||||
:FLYING => [:SHARPBEAK, :SKYPLATE],
|
||||
:GHOST => [:SPELLTAG, :SPOOKYPLATE],
|
||||
:GRASS => [:MIRACLESEED, :MEADOWPLATE, :ROSEINCENSE],
|
||||
:GROUND => [:SOFTSAND, :EARTHPLATE],
|
||||
:ICE => [:NEVERMELTICE, :ICICLEPLATE],
|
||||
:NORMAL => [:SILKSCARF],
|
||||
:POISON => [:POISONBARB, :TOXICPLATE],
|
||||
:PSYCHIC => [:TWISTEDSPOON, :MINDPLATE, :ODDINCENSE],
|
||||
:ROCK => [:HARDSTONE, :STONEPLATE, :ROCKINCENSE],
|
||||
:STEEL => [:METALCOAT, :IRONPLATE],
|
||||
:WATER => [:MYSTICWATER, :SPLASHPLATE, :SEAINCENSE, :WAVEINCENSE],
|
||||
}
|
||||
boosted_type = nil
|
||||
boosters.each_pair do |type, items|
|
||||
next if !items.include?(item)
|
||||
boosted_type = type
|
||||
break
|
||||
end
|
||||
next score if boosted_type && battler.has_damaging_move_of_type?(boosted_type)
|
||||
next 0
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1,43 +1,37 @@
|
||||
#===============================================================================
|
||||
#
|
||||
#===============================================================================
|
||||
class Battle::AI
|
||||
#=============================================================================
|
||||
#
|
||||
#=============================================================================
|
||||
# TODO: Reborn has the REVENGEKILLER role which compares mon's speed with
|
||||
# opponent (only when deciding whether to switch mon in) - this
|
||||
# comparison should be calculated when needed instead of being a role.
|
||||
module BattleRole
|
||||
PHAZER = 0
|
||||
CLERIC = 1
|
||||
STALL_BREAKER = 2
|
||||
STATUS_ABSORBER = 3
|
||||
BATON_PASSER = 4
|
||||
SPINNER = 5
|
||||
FIELD_SETTER = 6
|
||||
WEATHER_SETTER = 7
|
||||
SWEEPER = 8
|
||||
PIVOT = 9
|
||||
PHYSICAL_WALL = 10
|
||||
SPECIAL_WALL = 11
|
||||
TANK = 12
|
||||
TRAPPER = 13
|
||||
SCREENER = 14
|
||||
ACE = 15
|
||||
LEAD = 16
|
||||
SECOND = 17
|
||||
end
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
# Determine the roles filled by a Pokémon on a given side at a given party
|
||||
# index.
|
||||
# Roles are:
|
||||
# :ace
|
||||
# :baton_passer
|
||||
# :cleric
|
||||
# :field_setter
|
||||
# :lead
|
||||
# :phazer
|
||||
# :physical_wall
|
||||
# :pivot
|
||||
# :screener
|
||||
# :second
|
||||
# :special_wall
|
||||
# :spinner
|
||||
# :stall_breaker
|
||||
# :status_absorber
|
||||
# :sweeper
|
||||
# :tank
|
||||
# :trapper
|
||||
# :weather_setter
|
||||
# NOTE: Reborn has the REVENGEKILLER role which compares mon's speed with
|
||||
# opponent (only when deciding whether to switch mon in) - this
|
||||
# comparison should be calculated when needed instead of being a role.
|
||||
def determine_roles(side, index)
|
||||
pkmn = @battle.pbParty(side)[index]
|
||||
ret = []
|
||||
return ret if !pkmn || pkmn.egg?
|
||||
|
||||
# Check for moves indicative of particular roles
|
||||
hasHealMove = false
|
||||
hasPivotMove = false
|
||||
hasHealMove = false
|
||||
pkmn.moves.each do |m|
|
||||
next if !m
|
||||
move = Battle::Move.from_pokemon_move(@battle, m)
|
||||
@@ -47,92 +41,87 @@ class Battle::AI
|
||||
"StartPerishCountsForAllBattlers", # Perish Song
|
||||
"SwitchOutTargetStatusMove", # Roar
|
||||
"SwitchOutTargetDamagingMove" # Circle Throw
|
||||
ret.push(BattleRole::PHAZER)
|
||||
ret.push(:phazer)
|
||||
when "CureUserPartyStatus" # Aromatherapy/Heal Bell
|
||||
ret.push(BattleRole::CLERIC)
|
||||
ret.push(:cleric)
|
||||
when "DisableTargetStatusMoves" # Taunt
|
||||
ret.push(BattleRole::STALL_BREAKER)
|
||||
ret.push(:stall_breaker)
|
||||
when "HealUserPositionNextTurn" # Wish
|
||||
ret.push(BattleRole::CLERIC) if pkmn.ev[:HP] == Pokemon::EV_STAT_LIMIT
|
||||
ret.push(:cleric) if pkmn.ev[:HP] == Pokemon::EV_STAT_LIMIT
|
||||
when "HealUserFullyAndFallAsleep" # Rest
|
||||
ret.push(BattleRole::STATUS_ABSORBER)
|
||||
ret.push(:status_absorber)
|
||||
when "SwitchOutUserPassOnEffects" # Baton Pass
|
||||
ret.push(BattleRole::BATON_PASSER)
|
||||
ret.push(:baton_passer)
|
||||
when "SwitchOutUserStatusMove", "SwitchOutUserDamagingMove" # Teleport (Gen 8+), U-turn
|
||||
hasPivotMove = true
|
||||
ret.push(:pivot) if hasHealMove
|
||||
when "RemoveUserBindingAndEntryHazards" # Rapid Spin
|
||||
ret.push(BattleRole::SPINNER)
|
||||
ret.push(:spinner)
|
||||
when "StartElectricTerrain", "StartGrassyTerrain",
|
||||
"StartMistyTerrain", "StartPsychicTerrain" # Terrain moves
|
||||
ret.push(BattleRole::FIELD_SETTER)
|
||||
ret.push(:field_setter)
|
||||
else
|
||||
ret.push(BattleRole::WEATHER_SETTER) if move.is_a?(Battle::Move::WeatherMove)
|
||||
ret.push(:weather_setter) if move.is_a?(Battle::Move::WeatherMove)
|
||||
end
|
||||
end
|
||||
|
||||
# Check EVs, nature and moves for combinations indicative of particular roles
|
||||
if pkmn.ev[:SPEED] == Pokemon::EV_STAT_LIMIT
|
||||
if [:MODEST, :ADAMANT, # SpAtk+ Atk-, Atk+ SpAtk-
|
||||
:TIMID, :JOLLY].include?(pkmn.nature) # Spd+ Atk-, Spd+ SpAtk-
|
||||
ret.push(BattleRole::SWEEPER)
|
||||
ret.push(:sweeper)
|
||||
end
|
||||
end
|
||||
if hasHealMove
|
||||
ret.push(BattleRole::PIVOT) if hasPivotMove
|
||||
if pkmn.nature.stat_changes.any? { |change| change[0] == :DEFENSE && change[1] > 0 } &&
|
||||
!pkmn.nature.stat_changes.any? { |change| change[0] == :DEFENSE && change[1] < 0 }
|
||||
ret.push(BattleRole::PHYSICAL_WALL) if pkmn.ev[:DEFENSE] == Pokemon::EV_STAT_LIMIT
|
||||
ret.push(:physical_wall) if pkmn.ev[:DEFENSE] == Pokemon::EV_STAT_LIMIT
|
||||
elsif pkmn.nature.stat_changes.any? { |change| change[0] == :SPECIAL_DEFENSE && change[1] > 0 } &&
|
||||
!pkmn.nature.stat_changes.any? { |change| change[0] == :SPECIAL_DEFENSE && change[1] < 0 }
|
||||
ret.push(BattleRole::SPECIAL_WALL) if pkmn.ev[:SPECIAL_DEFENSE] == Pokemon::EV_STAT_LIMIT
|
||||
ret.push(:special_wall) if pkmn.ev[:SPECIAL_DEFENSE] == Pokemon::EV_STAT_LIMIT
|
||||
end
|
||||
else
|
||||
ret.push(BattleRole::TANK) if pkmn.ev[:HP] == Pokemon::EV_STAT_LIMIT
|
||||
ret.push(:tank) if pkmn.ev[:HP] == Pokemon::EV_STAT_LIMIT
|
||||
end
|
||||
|
||||
# Check for abilities indicative of particular roles
|
||||
case pkmn.ability_id
|
||||
when :REGENERATOR
|
||||
ret.push(BattleRole::PIVOT)
|
||||
ret.push(:pivot)
|
||||
when :GUTS, :QUICKFEET, :FLAREBOOST, :TOXICBOOST, :NATURALCURE, :MAGICGUARD,
|
||||
:MAGICBOUNCE, :HYDRATION
|
||||
ret.push(BattleRole::STATUS_ABSORBER)
|
||||
ret.push(:status_absorber)
|
||||
when :SHADOWTAG, :ARENATRAP, :MAGNETPULL
|
||||
ret.push(BattleRole::TRAPPER)
|
||||
ret.push(:trapper)
|
||||
when :DROUGHT, :DRIZZLE, :SANDSTREAM, :SNOWWARNING, :PRIMORDIALSEA,
|
||||
:DESOLATELAND, :DELTASTREAM
|
||||
ret.push(BattleRole::WEATHER_SETTER)
|
||||
ret.push(:weather_setter)
|
||||
when :GRASSYSURGE, :ELECTRICSURGE, :MISTYSURGE, :PSYCHICSURGE
|
||||
ret.push(BattleRole::FIELD_SETTER)
|
||||
ret.push(:field_setter)
|
||||
end
|
||||
|
||||
# Check for items indicative of particular roles
|
||||
case pkmn.item_id
|
||||
when :LIGHTCLAY
|
||||
ret.push(BattleRole::SCREENER)
|
||||
ret.push(:screener)
|
||||
when :ASSAULTVEST
|
||||
ret.push(BattleRole::TANK)
|
||||
ret.push(:tank)
|
||||
when :CHOICEBAND, :CHOICESPECS
|
||||
ret.push(BattleRole::STALL_BREAKER)
|
||||
ret.push(BattleRole::SWEEPER) if pkmn.ev[:SPEED] == Pokemon::EV_STAT_LIMIT
|
||||
ret.push(:stall_breaker)
|
||||
ret.push(:sweeper) if pkmn.ev[:SPEED] == Pokemon::EV_STAT_LIMIT
|
||||
when :CHOICESCARF
|
||||
ret.push(BattleRole::SWEEPER) if pkmn.ev[:SPEED] == Pokemon::EV_STAT_LIMIT
|
||||
ret.push(:sweeper) if pkmn.ev[:SPEED] == Pokemon::EV_STAT_LIMIT
|
||||
when :TOXICORB, :FLAMEORB
|
||||
ret.push(BattleRole::STATUS_ABSORBER)
|
||||
ret.push(:status_absorber)
|
||||
when :TERRAINEXTENDER
|
||||
ret.push(BattleRole::FIELD_SETTER)
|
||||
ret.push(:field_setter)
|
||||
end
|
||||
|
||||
# Check for position in team, level relative to other levels in team
|
||||
partyStarts = @battle.pbPartyStarts(side)
|
||||
if partyStarts.include?(index + 1) || index == @battle.pbParty(side).length - 1
|
||||
ret.push(BattleRole::ACE)
|
||||
ret.push(:ace) # Last in party, assumed to be the best Pokémon
|
||||
else
|
||||
ret.push(BattleRole::LEAD) if partyStarts.include?(index)
|
||||
ret.push(:lead) if partyStarts.include?(index) # First in party
|
||||
idxTrainer = @battle.pbGetOwnerIndexFromPartyIndex(side, index)
|
||||
maxLevel = @battle.pbMaxLevelInTeam(side, idxTrainer)
|
||||
if pkmn.level >= maxLevel
|
||||
ret.push(BattleRole::SECOND)
|
||||
ret.push(:second)
|
||||
else
|
||||
secondHighest = true
|
||||
seenHigherLevel = false
|
||||
@@ -146,10 +135,9 @@ class Battle::AI
|
||||
end
|
||||
# NOTE: There can be multiple "second"s if all their levels are equal
|
||||
# and the highest in the team (and none are the ace).
|
||||
ret.push(BattleRole::SECOND) if secondHighest
|
||||
ret.push(:second) if secondHighest
|
||||
end
|
||||
end
|
||||
|
||||
return ret
|
||||
end
|
||||
|
||||
|
||||
@@ -18,7 +18,8 @@
|
||||
# ReserveLastPokemon (don't switch it in if possible)
|
||||
# UsePokemonInOrder (uses earliest-listed Pokémon possible)
|
||||
#
|
||||
# TODO: Add more skill flags.
|
||||
# Anti-skill flags are skill flags with "Anti" at the beginning. An "AntiXYZ"
|
||||
# flag will negate the corresponding "XYZ" flag.
|
||||
#===============================================================================
|
||||
class Battle::AI::AITrainer
|
||||
attr_reader :side, :trainer_index
|
||||
@@ -74,7 +75,11 @@ class Battle::AI::AITrainer
|
||||
def sanitize_skill_flags
|
||||
# NOTE: Any skill flag which is shorthand for multiple other skill flags
|
||||
# should be "unpacked" here.
|
||||
# TODO: Have a bunch of "AntiX" flags that negate the corresponding "X" flags.
|
||||
# Remove any skill flag "XYZ" if there is also an "AntiXYZ" skill flag
|
||||
@skill_flags.each_with_index do |flag, i|
|
||||
@skill_flags[i] = nil if @skill_flags.include?("Anti" + flag)
|
||||
end
|
||||
@skill_flags.compact!
|
||||
end
|
||||
|
||||
def has_skill_flag?(flag)
|
||||
|
||||
@@ -18,7 +18,7 @@ class Battle::AI::AIBattler
|
||||
@party_index = battler.pokemonIndex
|
||||
if @party_index != old_party_index
|
||||
# TODO: Start of battle or Pokémon switched/shifted; recalculate roles,
|
||||
# etc.
|
||||
# etc. What is etc.?
|
||||
end
|
||||
end
|
||||
|
||||
@@ -87,7 +87,7 @@ class Battle::AI::AIBattler
|
||||
ret += [self.totalhp / 8, 1].max if [:Sun, :HarshSun].include?(weather) && battler.takesIndirectDamage?
|
||||
end
|
||||
# Future Sight/Doom Desire
|
||||
# TODO
|
||||
# NOTE: Not worth estimating the damage from this.
|
||||
# Wish
|
||||
if @ai.battle.positions[@index].effects[PBEffects::Wish] == 1 && battler.canHeal?
|
||||
ret -= @ai.battle.positions[@index].effects[PBEffects::WishAmount]
|
||||
@@ -230,10 +230,13 @@ class Battle::AI::AIBattler
|
||||
active_types = pbTypes(true)
|
||||
return active_types.include?(GameData::Type.get(type).id)
|
||||
end
|
||||
alias pbHasType? has_type?
|
||||
|
||||
# TODO: Also make a def effectiveness_of_move_against_battler which calls
|
||||
# pbCalcTypeModSingle instead of effectiveness_of_type_against_single_battler_type.
|
||||
# Why?
|
||||
# pbCalcTypeModSingle instead of effectiveness_of_type_against_single_battler_type,
|
||||
# for moves with custom def pbCalcTypeMod, e.g. Freeze-Dry. When would
|
||||
# that be used instead? Or rather, when would THIS method be used if
|
||||
# that existed?
|
||||
def effectiveness_of_type_against_battler(type, user = nil)
|
||||
ret = Effectiveness::NORMAL_EFFECTIVE_MULTIPLIER
|
||||
return ret if !type
|
||||
@@ -394,164 +397,49 @@ class Battle::AI::AIBattler
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
# TODO: Add more items.
|
||||
BASE_ITEM_RATINGS = {
|
||||
:ADAMANTORB => 3,
|
||||
:BLACKBELT => 2,
|
||||
:BLACKGLASSES => 2,
|
||||
:BLACKSLUDGE => -4,
|
||||
:CHARCOAL => 2,
|
||||
:CHOICEBAND => 4,
|
||||
:CHOICESCARF => 4,
|
||||
:CHOICESPECS => 4,
|
||||
:DEEPSEATOOTH => 4,
|
||||
:DRACOPLATE => 2,
|
||||
:DRAGONFANG => 2,
|
||||
:DREADPLATE => 2,
|
||||
:EARTHPLATE => 2,
|
||||
:FISTPLATE => 2,
|
||||
:FLAMEORB => -4,
|
||||
:FLAMEPLATE => 2,
|
||||
:GRISEOUSORB => 3,
|
||||
:HARDSTONE => 2,
|
||||
:ICICLEPLATE => 2,
|
||||
:INSECTPLATE => 2,
|
||||
:IRONBALL => -4,
|
||||
:IRONPLATE => 2,
|
||||
:LAGGINGTAIL => -2,
|
||||
:LEFTOVERS => 4,
|
||||
:LIFEORB => 3,
|
||||
:LIGHTBALL => 4,
|
||||
:LUSTROUSORB => 3,
|
||||
:MAGNET => 2,
|
||||
:MEADOWPLATE => 2,
|
||||
:METALCOAT => 2,
|
||||
:METRONOME => 1,
|
||||
:MINDPLATE => 2,
|
||||
:MIRACLESEED => 2,
|
||||
:MUSCLEBAND => 2,
|
||||
:MYSTICWATER => 2,
|
||||
:NEVERMELTICE => 2,
|
||||
:ODDINCENSE => 2,
|
||||
:PIXIEPLATE => 2,
|
||||
:POISONBARB => 2,
|
||||
:ROCKINCENSE => 2,
|
||||
:ROSEINCENSE => 2,
|
||||
:SEAINCENSE => 2,
|
||||
:SHARPBEAK => 2,
|
||||
:SILKSCARF => 2,
|
||||
:SILVERPOWDER => 2,
|
||||
:SKYPLATE => 2,
|
||||
:SOFTSAND => 2,
|
||||
:SOULDEW => 3,
|
||||
:SPELLTAG => 2,
|
||||
:SPLASHPLATE => 2,
|
||||
:SPOOKYPLATE => 2,
|
||||
:STICKYBARB => -2,
|
||||
:STONEPLATE => 2,
|
||||
:THICKCLUB => 4,
|
||||
:TOXICORB => -4,
|
||||
:TOXICPLATE => 2,
|
||||
:TWISTEDSPOON => 2,
|
||||
:WAVEINCENSE => 2,
|
||||
:WISEGLASSES => 2,
|
||||
:ZAPPLATE => 2
|
||||
}
|
||||
# Returns a value indicating how beneficial the given ability will be to this
|
||||
# battler if it has it.
|
||||
# Return values are typically between -10 and +10. 0 is indifferent, positive
|
||||
# values mean this battler benefits, negative values mean this battler suffers.
|
||||
# NOTE: This method assumes the ability isn't being negated. The calculations
|
||||
# that call this method separately check for it being negated, because
|
||||
# they need to do something special in that case.
|
||||
def wants_ability?(ability = :NONE)
|
||||
ability = ability.id if !ability.is_a?(Symbol) && ability.respond_to?("id")
|
||||
# Get the base ability rating
|
||||
ret = 0
|
||||
Battle::AI::BASE_ABILITY_RATINGS.each_pair do |val, abilities|
|
||||
next if !abilities.include?(ability)
|
||||
ret = val
|
||||
break
|
||||
end
|
||||
# Modify the rating based on ability-specific contexts
|
||||
ret = Battle::AI::Handlers.modify_ability_ranking(ability, ret, self, @ai)
|
||||
return ret
|
||||
end
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
# Returns a value indicating how beneficial the given item will be to this
|
||||
# battler if it is holding it.
|
||||
# Return values are typically between -10 and +10. 0 is indifferent, positive
|
||||
# values mean this battler benefits, negative values mean this battler suffers.
|
||||
# TODO: This method shouldn't check for item effect negation, to work the same
|
||||
# as def wants_ability?.
|
||||
def wants_item?(item)
|
||||
item = :NONE if !item
|
||||
item = item.id if !item.is_a?(Symbol) && item.respond_to?("id")
|
||||
return 0 if has_active_ability?(:KLUTZ)
|
||||
# TODO: Unnerve, other item-negating effects.
|
||||
ret = BASE_ITEM_RATINGS[item] || 0
|
||||
# TODO: Add more context-sensitive modifications to the ratings from above.
|
||||
# Should they be moved into a handler?
|
||||
case item
|
||||
when :ADAMANTORB
|
||||
ret = 0 if !battler.isSpecies?(:DIALGA) || !has_damaging_move_of_type?(:DRAGON, :STEEL)
|
||||
when :BLACKBELT, :BLACKGLASSES, :CHARCOAL, :DRAGONFANG, :HARDSTONE, :MAGNET,
|
||||
:METALCOAT, :MIRACLESEED, :MYSTICWATER, :NEVERMELTICE, :POISONBARB,
|
||||
:SHARPBEAK, :SILKSCARF, :SILVERPOWDER, :SOFTSAND, :SPELLTAG,
|
||||
:TWISTEDSPOON,
|
||||
:DRACOPLATE, :DREADPLATE, :EARTHPLATE, :FISTPLATE, :FLAMEPLATE,
|
||||
:ICICLEPLATE, :INSECTPLATE, :IRONPLATE, :MEADOWPLATE, :MINDPLATE,
|
||||
:PIXIEPLATE, :SKYPLATE, :SPLASHPLATE, :SPOOKYPLATE, :STONEPLATE,
|
||||
:TOXICPLATE, :ZAPPLATE,
|
||||
:ODDINCENSE, :ROCKINCENSE, :ROSEINCENSE, :SEAINCENSE, :WAVEINCENSE
|
||||
boosted_type = {
|
||||
:BLACKBELT => :FIGHTING,
|
||||
:BLACKGLASSES => :DARK,
|
||||
:CHARCOAL => :FIRE,
|
||||
:DRAGONFANG => :DRAGON,
|
||||
:HARDSTONE => :ROCK,
|
||||
:MAGNET => :ELECTRIC,
|
||||
:METALCOAT => :STEEL,
|
||||
:MIRACLESEED => :GRASS,
|
||||
:MYSTICWATER => :WATER,
|
||||
:NEVERMELTICE => :ICE,
|
||||
:POISONBARB => :POISON,
|
||||
:SHARPBEAK => :FLYING,
|
||||
:SILKSCARF => :NORMAL,
|
||||
:SILVERPOWDER => :BUG,
|
||||
:SOFTSAND => :GROUND,
|
||||
:SPELLTAG => :GHOST,
|
||||
:TWISTEDSPOON => :PSYCHIC,
|
||||
:DRACOPLATE => :DRAGON,
|
||||
:DREADPLATE => :DARK,
|
||||
:EARTHPLATE => :GROUND,
|
||||
:FISTPLATE => :FIGHTING,
|
||||
:FLAMEPLATE => :FIRE,
|
||||
:ICICLEPLATE => :ICE,
|
||||
:INSECTPLATE => :BUG,
|
||||
:IRONPLATE => :STEEL,
|
||||
:MEADOWPLATE => :GRASS,
|
||||
:MINDPLATE => :PSYCHIC,
|
||||
:PIXIEPLATE => :FAIRY,
|
||||
:SKYPLATE => :FLYING,
|
||||
:SPLASHPLATE => :WATER,
|
||||
:SPOOKYPLATE => :GHOST,
|
||||
:STONEPLATE => :ROCK,
|
||||
:TOXICPLATE => :POISON,
|
||||
:ZAPPLATE => :ELECTRIC,
|
||||
:ODDINCENSE => :PSYCHIC,
|
||||
:ROCKINCENSE => :ROCK,
|
||||
:ROSEINCENSE => :GRASS,
|
||||
:SEAINCENSE => :WATER,
|
||||
:WAVEINCENSE => :WATER
|
||||
}[item]
|
||||
ret = 0 if !has_damaging_move_of_type?(boosted_type)
|
||||
when :BLACKSLUDGE
|
||||
ret = 4 if has_type?(:POISON)
|
||||
when :CHOICEBAND, :MUSCLEBAND
|
||||
ret = 0 if !check_for_move { |m| m.physicalMove?(m.type) }
|
||||
when :CHOICESPECS, :WISEGLASSES
|
||||
ret = 0 if !check_for_move { |m| m.specialMove?(m.type) }
|
||||
when :DEEPSEATOOTH
|
||||
ret = 0 if !battler.isSpecies?(:CLAMPERL) || !check_for_move { |m| m.specialMove?(m.type) }
|
||||
when :GRISEOUSORB
|
||||
ret = 0 if !battler.isSpecies?(:GIRATINA) || !has_damaging_move_of_type?(:DRAGON, :GHOST)
|
||||
when :IRONBALL
|
||||
ret = 0 if has_move_with_function?("ThrowUserItemAtTarget")
|
||||
when :LIGHTBALL
|
||||
ret = 0 if !battler.isSpecies?(:PIKACHU) || !check_for_move { |m| m.damagingMove? }
|
||||
when :LUSTROUSORB
|
||||
ret = 0 if !battler.isSpecies?(:PALKIA) || !has_damaging_move_of_type?(:DRAGON, :WATER)
|
||||
when :SOULDEW
|
||||
if !battler.isSpecies?(:LATIAS) && !battler.isSpecies?(:LATIOS)
|
||||
ret = 0
|
||||
elsif Settings::SOUL_DEW_POWERS_UP_TYPES
|
||||
ret = 0 if !has_damaging_move_of_type?(:PSYCHIC, :DRAGON)
|
||||
else
|
||||
ret -= 2 if !check_for_move { |m| m.specialMove?(m.type) } # Also boosts SpDef
|
||||
end
|
||||
when :THICKCLUB
|
||||
ret = 0 if (!battler.isSpecies?(:CUBONE) && !battler.isSpecies?(:MAROWAK)) ||
|
||||
!check_for_move { |m| m.physicalMove?(m.type) }
|
||||
# Get the base item rating
|
||||
ret = 0
|
||||
Battle::AI::BASE_ITEM_RATINGS.each_pair do |val, items|
|
||||
next if !items.include?(item)
|
||||
ret = val
|
||||
break
|
||||
end
|
||||
# Modify the rating based on item-specific contexts
|
||||
ret = Battle::AI::Handlers.modify_item_ranking(item, ret, self, @ai)
|
||||
# Prefer if this battler knows Fling and it will do a lot of damage/have an
|
||||
# additional (negative) effect when flung
|
||||
if item != :NONE && has_move_with_function?("ThrowUserItemAtTarget")
|
||||
@@ -660,339 +548,6 @@ class Battle::AI::AIBattler
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
# 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
|
||||
BASE_ABILITY_RATINGS = {
|
||||
:ADAPTABILITY => 8,
|
||||
:AERILATE => 8,
|
||||
:AFTERMATH => 5,
|
||||
:AIRLOCK => 5,
|
||||
:ANALYTIC => 5,
|
||||
:ANGERPOINT => 4,
|
||||
:ANTICIPATION => 0,
|
||||
:ARENATRAP => 9,
|
||||
:AROMAVEIL => 3,
|
||||
# :ASONECHILLINGNEIGH => 0,
|
||||
# :ASONEGRIMNEIGH => 0,
|
||||
:AURABREAK => 3,
|
||||
:BADDREAMS => 4,
|
||||
# :BALLFETCH => 0,
|
||||
# :BATTERY => 0,
|
||||
:BATTLEARMOR => 2,
|
||||
:BATTLEBOND => 6,
|
||||
:BEASTBOOST => 7,
|
||||
:BERSERK => 5,
|
||||
:BIGPECKS => 1,
|
||||
:BLAZE => 5,
|
||||
:BULLETPROOF => 7,
|
||||
:CHEEKPOUCH => 4,
|
||||
# :CHILLINGNEIGH => 0,
|
||||
:CHLOROPHYLL => 6,
|
||||
:CLEARBODY => 4,
|
||||
:CLOUDNINE => 5,
|
||||
:COLORCHANGE => 2,
|
||||
:COMATOSE => 6,
|
||||
:COMPETITIVE => 5,
|
||||
:COMPOUNDEYES => 7,
|
||||
:CONTRARY => 8,
|
||||
:CORROSION => 5,
|
||||
:COTTONDOWN => 3,
|
||||
# :CURIOUSMEDICINE => 0,
|
||||
:CURSEDBODY => 4,
|
||||
:CUTECHARM => 2,
|
||||
:DAMP => 2,
|
||||
:DANCER => 5,
|
||||
:DARKAURA => 6,
|
||||
:DAUNTLESSSHIELD => 3,
|
||||
:DAZZLING => 5,
|
||||
:DEFEATIST => -1,
|
||||
:DEFIANT => 5,
|
||||
:DELTASTREAM => 10,
|
||||
:DESOLATELAND => 10,
|
||||
:DISGUISE => 8,
|
||||
:DOWNLOAD => 7,
|
||||
:DRAGONSMAW => 8,
|
||||
:DRIZZLE => 9,
|
||||
:DROUGHT => 9,
|
||||
:DRYSKIN => 6,
|
||||
:EARLYBIRD => 4,
|
||||
:EFFECTSPORE => 4,
|
||||
:ELECTRICSURGE => 8,
|
||||
:EMERGENCYEXIT => 3,
|
||||
:FAIRYAURA => 6,
|
||||
:FILTER => 6,
|
||||
:FLAMEBODY => 4,
|
||||
:FLAREBOOST => 5,
|
||||
:FLASHFIRE => 6,
|
||||
:FLOWERGIFT => 4,
|
||||
# :FLOWERVEIL => 0,
|
||||
:FLUFFY => 5,
|
||||
:FORECAST => 6,
|
||||
:FOREWARN => 0,
|
||||
# :FRIENDGUARD => 0,
|
||||
:FRISK => 0,
|
||||
:FULLMETALBODY => 4,
|
||||
:FURCOAT => 7,
|
||||
:GALEWINGS => 6,
|
||||
:GALVANIZE => 8,
|
||||
:GLUTTONY => 3,
|
||||
:GOOEY => 5,
|
||||
:GORILLATACTICS => 4,
|
||||
:GRASSPELT => 2,
|
||||
:GRASSYSURGE => 8,
|
||||
# :GRIMNEIGH => 0,
|
||||
:GULPMISSLE => 3,
|
||||
:GUTS => 6,
|
||||
:HARVEST => 5,
|
||||
# :HEALER => 0,
|
||||
:HEATPROOF => 5,
|
||||
:HEAVYMETAL => -1,
|
||||
# :HONEYGATHER => 0,
|
||||
:HUGEPOWER => 10,
|
||||
:HUNGERSWITCH => 2,
|
||||
:HUSTLE => 7,
|
||||
:HYDRATION => 4,
|
||||
:HYPERCUTTER => 3,
|
||||
:ICEBODY => 3,
|
||||
:ICEFACE => 4,
|
||||
:ICESCALES => 7,
|
||||
# :ILLUMINATE => 0,
|
||||
:ILLUSION => 8,
|
||||
:IMMUNITY => 4,
|
||||
:IMPOSTER => 9,
|
||||
:INFILTRATOR => 6,
|
||||
:INNARDSOUT => 5,
|
||||
:INNERFOCUS => 2,
|
||||
:INSOMNIA => 4,
|
||||
:INTIMIDATE => 7,
|
||||
:INTREPIDSWORD => 3,
|
||||
:IRONBARBS => 6,
|
||||
:IRONFIST => 6,
|
||||
:JUSTIFIED => 4,
|
||||
:KEENEYE => 1,
|
||||
:KLUTZ => -1,
|
||||
:LEAFGUARD => 2,
|
||||
:LEVITATE => 7,
|
||||
:LIBERO => 8,
|
||||
:LIGHTMETAL => 2,
|
||||
:LIGHTNINGROD => 7,
|
||||
:LIMBER => 3,
|
||||
:LIQUIDOOZE => 3,
|
||||
:LIQUIDVOICE => 5,
|
||||
:LONGREACH => 3,
|
||||
:MAGICBOUNCE => 9,
|
||||
:MAGICGUARD => 9,
|
||||
:MAGICIAN => 3,
|
||||
:MAGMAARMOR => 1,
|
||||
:MAGNETPULL => 9,
|
||||
:MARVELSCALE => 5,
|
||||
:MEGALAUNCHER => 7,
|
||||
:MERCILESS => 4,
|
||||
:MIMICRY => 2,
|
||||
# :MINUS => 0,
|
||||
:MIRRORARMOR => 6,
|
||||
:MISTYSURGE => 8,
|
||||
:MOLDBREAKER => 7,
|
||||
:MOODY => 10,
|
||||
:MOTORDRIVE => 6,
|
||||
:MOXIE => 7,
|
||||
:MULTISCALE => 8,
|
||||
:MULTITYPE => 8,
|
||||
:MUMMY => 5,
|
||||
:NATURALCURE => 7,
|
||||
:NEUROFORCE => 6,
|
||||
:NEUTRALIZINGGAS => 5,
|
||||
:NOGUARD => 8,
|
||||
:NORMALIZE => -1,
|
||||
:OBLIVIOUS => 2,
|
||||
:OVERCOAT => 5,
|
||||
:OVERGROW => 5,
|
||||
:OWNTEMPO => 3,
|
||||
:PARENTALBOND => 10,
|
||||
:PASTELVEIL => 4,
|
||||
:PERISHBODY => -1,
|
||||
:PICKPOCKET => 3,
|
||||
:PICKUP => 1,
|
||||
:PIXILATE => 8,
|
||||
# :PLUS => 0,
|
||||
:POISONHEAL => 8,
|
||||
:POISONPOINT => 4,
|
||||
:POISONTOUCH => 4,
|
||||
:POWERCONSTRUCT => 10,
|
||||
# :POWEROFALCHEMY => 0,
|
||||
:POWERSPOT => 2,
|
||||
:PRANKSTER => 8,
|
||||
:PRESSURE => 5,
|
||||
:PRIMORDIALSEA => 10,
|
||||
:PRISMARMOR => 6,
|
||||
:PROPELLORTAIL => 2,
|
||||
:PROTEAN => 8,
|
||||
:PSYCHICSURGE => 8,
|
||||
:PUNKROCK => 2,
|
||||
:PUREPOWER => 10,
|
||||
:QUEENLYMAJESTY => 6,
|
||||
# :QUICKDRAW => 0,
|
||||
:QUICKFEET => 5,
|
||||
:RAINDISH => 3,
|
||||
:RATTLED => 3,
|
||||
# :RECEIVER => 0,
|
||||
:RECKLESS => 6,
|
||||
:REFRIGERATE => 8,
|
||||
:REGENERATOR => 8,
|
||||
:RIPEN => 4,
|
||||
:RIVALRY => 1,
|
||||
:RKSSYSTEM => 8,
|
||||
:ROCKHEAD => 5,
|
||||
:ROUGHSKIN => 6,
|
||||
# :RUNAWAY => 0,
|
||||
:SANDFORCE => 4,
|
||||
:SANDRUSH => 6,
|
||||
:SANDSPIT => 5,
|
||||
:SANDSTREAM => 9,
|
||||
:SANDVEIL => 3,
|
||||
:SAPSIPPER => 7,
|
||||
:SCHOOLING => 6,
|
||||
:SCRAPPY => 6,
|
||||
:SCREENCLEANER => 3,
|
||||
:SERENEGRACE => 8,
|
||||
:SHADOWSHIELD => 8,
|
||||
:SHADOWTAG => 10,
|
||||
:SHEDSKIN => 7,
|
||||
:SHEERFORCE => 8,
|
||||
:SHELLARMOR => 2,
|
||||
:SHIELDDUST => 5,
|
||||
:SHIELDSDOWN => 6,
|
||||
:SIMPLE => 8,
|
||||
:SKILLLINK => 7,
|
||||
:SLOWSTART => -2,
|
||||
:SLUSHRUSH => 5,
|
||||
:SNIPER => 3,
|
||||
:SNOWCLOAK => 3,
|
||||
:SNOWWARNING => 8,
|
||||
:SOLARPOWER => 3,
|
||||
:SOLIDROCK => 6,
|
||||
:SOULHEART => 7,
|
||||
:SOUNDPROOF => 4,
|
||||
:SPEEDBOOST => 9,
|
||||
:STAKEOUT => 6,
|
||||
:STALL => -1,
|
||||
:STALWART => 2,
|
||||
:STAMINA => 6,
|
||||
:STANCECHANGE => 10,
|
||||
:STATIC => 4,
|
||||
:STEADFAST => 2,
|
||||
:STEAMENGINE => 3,
|
||||
:STEELWORKER => 6,
|
||||
:STEELYSPIRIT => 2,
|
||||
:STENCH => 1,
|
||||
:STICKYHOLD => 3,
|
||||
:STORMDRAIN => 7,
|
||||
:STRONGJAW => 6,
|
||||
:STURDY => 6,
|
||||
:SUCTIONCUPS => 2,
|
||||
:SUPERLUCK => 3,
|
||||
:SURGESURFER => 4,
|
||||
:SWARM => 5,
|
||||
:SWEETVEIL => 4,
|
||||
:SWIFTSWIM => 6,
|
||||
# :SYMBIOSIS => 0,
|
||||
:SYNCHRONIZE => 4,
|
||||
:TANGLEDFEET => 2,
|
||||
:TANGLINGHAIR => 5,
|
||||
:TECHNICIAN => 8,
|
||||
# :TELEPATHY => 0,
|
||||
:TERAVOLT => 7,
|
||||
:THICKFAT => 7,
|
||||
:TINTEDLENS => 7,
|
||||
:TORRENT => 5,
|
||||
:TOUGHCLAWS => 7,
|
||||
:TOXICBOOST => 6,
|
||||
:TRACE => 6,
|
||||
:TRANSISTOR => 8,
|
||||
:TRIAGE => 7,
|
||||
:TRUANT => -2,
|
||||
:TURBOBLAZE => 7,
|
||||
:UNAWARE => 6,
|
||||
:UNBURDEN => 7,
|
||||
:UNNERVE => 3,
|
||||
# :UNSEENFIST => 0,
|
||||
:VICTORYSTAR => 6,
|
||||
:VITALSPIRIT => 4,
|
||||
:VOLTABSORB => 7,
|
||||
:WANDERINGSPIRIT => 2,
|
||||
:WATERABSORB => 7,
|
||||
:WATERBUBBLE => 8,
|
||||
:WATERCOMPACTION => 4,
|
||||
:WATERVEIL => 4,
|
||||
:WEAKARMOR => 2,
|
||||
:WHITESMOKE => 4,
|
||||
:WIMPOUT => 3,
|
||||
:WONDERGUARD => 10,
|
||||
:WONDERSKIN => 4,
|
||||
:ZENMODE => -1
|
||||
}
|
||||
|
||||
# Returns a value indicating how beneficial the given ability will be to this
|
||||
# battler if it has it.
|
||||
# Return values are typically between -10 and +10. 0 is indifferent, positive
|
||||
# values mean this battler benefits, negative values mean this battler suffers.
|
||||
# NOTE: This method assumes the ability isn't being negated. The calculations
|
||||
# that call this method separately check for it being negated, because
|
||||
# they need to do something special in that case.
|
||||
def wants_ability?(ability = :NONE)
|
||||
ability = ability.id if !ability.is_a?(Symbol) && ability.respond_to?("id")
|
||||
ret = BASE_ABILITY_RATINGS[ability] || 0
|
||||
# TODO: Add more context-sensitive modifications to the ratings from above.
|
||||
# Should they be moved into a handler?
|
||||
case ability
|
||||
when :BLAZE
|
||||
ret = 0 if !has_damaging_move_of_type?(:FIRE)
|
||||
when :CUTECHARM, :RIVALRY
|
||||
ret = 0 if gender == 2
|
||||
when :FRIENDGUARD, :HEALER, :SYMBOISIS, :TELEPATHY
|
||||
has_ally = false
|
||||
@ai.each_ally(@side) { |b, i| has_ally = true }
|
||||
ret = 0 if !has_ally
|
||||
when :GALEWINGS
|
||||
ret = 0 if !check_for_move { |m| m.type == :FLYING }
|
||||
when :HUGEPOWER, :PUREPOWER
|
||||
ret = 0 if !@ai.stat_raise_worthwhile?(self, :ATTACK, true)
|
||||
when :IRONFIST
|
||||
ret = 0 if !check_for_move { |m| m.punchingMove? }
|
||||
when :LIQUIDVOICE
|
||||
ret = 0 if !check_for_move { |m| m.soundMove? }
|
||||
when :MEGALAUNCHER
|
||||
ret = 0 if !check_for_move { |m| m.pulseMove? }
|
||||
when :OVERGROW
|
||||
ret = 0 if !has_damaging_move_of_type?(:GRASS)
|
||||
when :PRANKSTER
|
||||
ret = 0 if !check_for_move { |m| m.statusMove? }
|
||||
when :PUNKROCK
|
||||
ret = 1 if !check_for_move { |m| m.damagingMove? && m.soundMove? }
|
||||
when :RECKLESS
|
||||
ret = 0 if !check_for_move { |m| m.recoilMove? }
|
||||
when :ROCKHEAD
|
||||
ret = 0 if !check_for_move { |m| m.recoilMove? && !m.is_a?(Battle::Move::CrashDamageIfFailsUnusableInGravity) }
|
||||
when :RUNAWAY
|
||||
ret = 0 if wild?
|
||||
when :SANDFORCE
|
||||
ret = 2 if !has_damaging_move_of_type?(:GROUND, :ROCK, :STEEL)
|
||||
when :SKILLLINK
|
||||
ret = 0 if !check_for_move { |m| m.is_a?(Battle::Move::HitTwoToFiveTimes) }
|
||||
when :STEELWORKER
|
||||
ret = 0 if !has_damaging_move_of_type?(:STEEL)
|
||||
when :SWARM
|
||||
ret = 0 if !has_damaging_move_of_type?(:BUG)
|
||||
when :TORRENT
|
||||
ret = 0 if !has_damaging_move_of_type?(:WATER)
|
||||
when :TRIAGE
|
||||
ret = 0 if !check_for_move { |m| m.healingMove? }
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
private
|
||||
|
||||
def effectiveness_of_type_against_single_battler_type(type, defend_type, user = nil)
|
||||
|
||||
@@ -17,29 +17,13 @@ class Battle::AI::AIMove
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
# pp
|
||||
# totalpp
|
||||
# priority
|
||||
# usableWhenAsleep?
|
||||
# thawsUser?
|
||||
# flinchingMove?
|
||||
# tramplesMinimize?
|
||||
# hitsFlyingTargets?
|
||||
# canMagicCoat?
|
||||
# soundMove?
|
||||
# bombMove?
|
||||
# powderMove?
|
||||
# ignoresSubstitute?
|
||||
# highCriticalRate?
|
||||
# ignoresReflect?
|
||||
|
||||
def id; return @move.id; end
|
||||
def name; return @move.name; end
|
||||
def id; return @move.id; end
|
||||
def name; return @move.name; end
|
||||
def physicalMove?(thisType = nil); return @move.physicalMove?(thisType); end
|
||||
def specialMove?(thisType = nil); return @move.specialMove?(thisType); end
|
||||
def damagingMove?; return @move.damagingMove?; end
|
||||
def statusMove?; return @move.statusMove?; end
|
||||
def function; return @move.function; end
|
||||
def damagingMove?; return @move.damagingMove?; end
|
||||
def statusMove?; return @move.statusMove?; end
|
||||
def function; return @move.function; end
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
@@ -177,7 +161,7 @@ class Battle::AI::AIMove
|
||||
when :SNIPER
|
||||
multipliers[:final_damage_multiplier] *= 1.5 if is_critical
|
||||
when :STAKEOUT
|
||||
# TODO: multipliers[:attack_multiplier] *= 2 if target switches out
|
||||
# NOTE: Can't predict whether the target will switch out this round.
|
||||
when :TINTEDLENS
|
||||
if Effectiveness.resistant_type?(calc_type, *target.pbTypes(true))
|
||||
multipliers[:final_damage_multiplier] *= 2
|
||||
@@ -246,8 +230,7 @@ class Battle::AI::AIMove
|
||||
if user.has_active_ability?(:PARENTALBOND)
|
||||
multipliers[:power_multiplier] *= (Settings::MECHANICS_GENERATION >= 7) ? 1.25 : 1.5
|
||||
end
|
||||
# Me First
|
||||
# TODO
|
||||
# Me First - n/a because can't predict the move Me First will use
|
||||
# Helping Hand - n/a
|
||||
# Charge
|
||||
if @ai.trainer.medium_skill? &&
|
||||
@@ -376,10 +359,10 @@ class Battle::AI::AIMove
|
||||
if @ai.trainer.medium_skill? && target.effects[PBEffects::Minimize] && @move.tramplesMinimize?
|
||||
multipliers[:final_damage_multiplier] *= 2
|
||||
end
|
||||
# Move-specific base damage modifiers
|
||||
# TODO
|
||||
# Move-specific final damage modifiers
|
||||
# TODO
|
||||
# NOTE: No need to check pbBaseDamageMultiplier, as it's already accounted
|
||||
# for in an AI's MoveBasePower handler or can't be checked now anyway.
|
||||
# NOTE: No need to check pbModifyDamage, as it's already accounted for in an
|
||||
# AI's MoveBasePower handler.
|
||||
##### Main damage calculation #####
|
||||
base_dmg = [(base_dmg * multipliers[:power_multiplier]).round, 1].max
|
||||
atk = [(atk * multipliers[:attack_multiplier]).round, 1].max
|
||||
|
||||
@@ -138,10 +138,10 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("FailsIfUserDamagedThisTu
|
||||
#===============================================================================
|
||||
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("FailsIfTargetActed",
|
||||
proc { |score, move, user, target, ai, battle|
|
||||
# Check whether user is faster than its foe(s) and could use this move
|
||||
# Check whether user is faster than the target and could use this move
|
||||
next Battle::AI::MOVE_USELESS_SCORE if target.faster_than?(user)
|
||||
# TODO: Predict the target switching/using an item.
|
||||
# TODO: Predict the target using a damaging move or Me First.
|
||||
# Check whether the target has any damaging moves it could use
|
||||
next Battle::AI::MOVE_USELESS_SCORE if !target.check_for_move { |m| m.damagingMove? }
|
||||
# Don't risk using this move if target is weak
|
||||
if ai.trainer.has_skill_flag?("HPAware")
|
||||
score -= 10 if target.hp <= target.totalhp / 2
|
||||
@@ -157,7 +157,7 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("FailsIfTargetActed",
|
||||
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("CrashDamageIfFailsUnusableInGravity",
|
||||
proc { |score, move, user, target, ai, battle|
|
||||
if user.battler.takesIndirectDamage?
|
||||
score -= (0.4 * (100 - move.rough_accuracy)).to_i # -0 (100%) to -40 (1%)
|
||||
score -= (0.6 * (100 - move.rough_accuracy)).to_i # -0 (100%) to -60 (1%)
|
||||
end
|
||||
next score
|
||||
}
|
||||
@@ -367,17 +367,9 @@ Battle::AI::Handlers::MoveEffectScore.add("AddSpikesToFoeSide",
|
||||
battle.pbParty(user.idxOpposingSide).each_with_index do |pkmn, idxParty|
|
||||
next if !pkmn || !pkmn.able? || inBattleIndices.include?(idxParty)
|
||||
if ai.trainer.medium_skill?
|
||||
# Check affected by entry hazard
|
||||
next if pkmn.hasItem?(:HEAVYDUTYBOOTS)
|
||||
# Check can take indirect damage
|
||||
next if ai.pokemon_airborne?(pkmn)
|
||||
next if pkmn.hasAbility?(:MAGICGUARD)
|
||||
# Check airborne
|
||||
if !pkmn.hasItem?(:IRONBALL) &&
|
||||
battle.field.effects[PBEffects::Gravity] == 0
|
||||
next if pkmn.hasType?(:FLYING)
|
||||
next if pkmn.hasAbility?(:LEVITATE)
|
||||
next if pkmn.hasItem?(:AIRBALLOON)
|
||||
end
|
||||
end
|
||||
foe_reserves.push(pkmn) # pkmn will be affected by Spikes
|
||||
end
|
||||
@@ -403,25 +395,9 @@ Battle::AI::Handlers::MoveEffectScore.add("AddToxicSpikesToFoeSide",
|
||||
battle.pbParty(user.idxOpposingSide).each_with_index do |pkmn, idxParty|
|
||||
next if !pkmn || !pkmn.able? || inBattleIndices.include?(idxParty)
|
||||
if ai.trainer.medium_skill?
|
||||
# Check affected by entry hazard
|
||||
next if pkmn.hasItem?(:HEAVYDUTYBOOTS)
|
||||
# Check pkmn's immunity to being poisoned
|
||||
next if battle.field.terrain == :Misty
|
||||
next if pkmn.hasType?(:POISON)
|
||||
next if pkmn.hasType?(:STEEL)
|
||||
next if pkmn.hasAbility?(:IMMUNITY)
|
||||
next if pkmn.hasAbility?(:PASTELVEIL)
|
||||
next if pkmn.hasAbility?(:FLOWERVEIL) && pkmn.hasType?(:GRASS)
|
||||
next if pkmn.hasAbility?(:LEAFGUARD) && [:Sun, :HarshSun].include?(battle.pbWeather)
|
||||
next if pkmn.hasAbility?(:COMATOSE) && pkmn.isSpecies?(:KOMALA)
|
||||
next if pkmn.hasAbility?(:SHIELDSDOWN) && pkmn.isSpecies?(:MINIOR) && pkmn.form < 7
|
||||
# Check airborne
|
||||
if !pkmn.hasItem?(:IRONBALL) &&
|
||||
battle.field.effects[PBEffects::Gravity] == 0
|
||||
next if pkmn.hasType?(:FLYING)
|
||||
next if pkmn.hasAbility?(:LEVITATE)
|
||||
next if pkmn.hasItem?(:AIRBALLOON)
|
||||
end
|
||||
next if ai.pokemon_airborne?(pkmn)
|
||||
next if !ai.pokemon_can_be_poisoned?(pkmn)
|
||||
end
|
||||
foe_reserves.push(pkmn) # pkmn will be affected by Toxic Spikes
|
||||
end
|
||||
@@ -447,9 +423,7 @@ Battle::AI::Handlers::MoveEffectScore.add("AddStealthRocksToFoeSide",
|
||||
battle.pbParty(user.idxOpposingSide).each_with_index do |pkmn, idxParty|
|
||||
next if !pkmn || !pkmn.able? || inBattleIndices.include?(idxParty)
|
||||
if ai.trainer.medium_skill?
|
||||
# Check affected by entry hazard
|
||||
next if pkmn.hasItem?(:HEAVYDUTYBOOTS)
|
||||
# Check can take indirect damage
|
||||
next if pkmn.hasAbility?(:MAGICGUARD)
|
||||
end
|
||||
foe_reserves.push(pkmn) # pkmn will be affected by Stealth Rock
|
||||
@@ -475,15 +449,8 @@ Battle::AI::Handlers::MoveEffectScore.add("AddStickyWebToFoeSide",
|
||||
battle.pbParty(user.idxOpposingSide).each_with_index do |pkmn, idxParty|
|
||||
next if !pkmn || !pkmn.able? || inBattleIndices.include?(idxParty)
|
||||
if ai.trainer.medium_skill?
|
||||
# Check affected by entry hazard
|
||||
next if pkmn.hasItem?(:HEAVYDUTYBOOTS)
|
||||
# Check airborne
|
||||
if !pkmn.hasItem?(:IRONBALL) &&
|
||||
battle.field.effects[PBEffects::Gravity] == 0
|
||||
next if pkmn.hasType?(:FLYING)
|
||||
next if pkmn.hasAbility?(:LEVITATE)
|
||||
next if pkmn.hasItem?(:AIRBALLOON)
|
||||
end
|
||||
next if ai.pokemon_airborne?(pkmn)
|
||||
end
|
||||
foe_reserves.push(pkmn) # pkmn will be affected by Sticky Web
|
||||
end
|
||||
|
||||
@@ -516,7 +516,9 @@ Battle::AI::Handlers::MoveEffectScore.add("CureUserPartyStatus",
|
||||
proc { |score, move, user, ai, battle|
|
||||
score = Battle::AI::MOVE_BASE_SCORE # Ignore the scores for each targeted battler calculated earlier
|
||||
battle.pbParty(user.index).each do |pkmn|
|
||||
score += 12 if pkmn && pkmn.status != :NONE
|
||||
next if !pkmn || pkmn.status == :NONE
|
||||
next if pkmn.status == :SLEEP && pkmn.statusCount == 1 # About to wake up
|
||||
score += 12
|
||||
end
|
||||
next score
|
||||
}
|
||||
@@ -1071,6 +1073,7 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("NegateTargetAbility",
|
||||
)
|
||||
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("NegateTargetAbility",
|
||||
proc { |score, move, user, target, ai, battle|
|
||||
next Battle::AI::MOVE_USELESS_SCORE if !target.ability_active?
|
||||
target_ability_rating = target.wants_ability?(target.ability_id)
|
||||
side_mult = (target.opposes?(user)) ? 1 : -1
|
||||
if target_ability_rating > 0
|
||||
@@ -1087,8 +1090,8 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("NegateTargetAbility",
|
||||
#===============================================================================
|
||||
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("NegateTargetAbilityIfTargetActed",
|
||||
proc { |score, move, user, target, ai, battle|
|
||||
next score if target.effects[PBEffects::Substitute] > 0 || target.effects[PBEffects::GastroAcid]
|
||||
next score if target.battler.unstoppableAbility?
|
||||
next score if target.effects[PBEffects::Substitute] > 0
|
||||
next score if target.battler.unstoppableAbility? || !target.ability_active?
|
||||
next score if user.faster_than?(target)
|
||||
target_ability_rating = target.wants_ability?(target.ability_id)
|
||||
side_mult = (target.opposes?(user)) ? 1 : -1
|
||||
|
||||
@@ -559,7 +559,6 @@ Battle::AI::Handlers::MoveEffectScore.add("UserEnduresFaintingThisTurn",
|
||||
# Don't prefer if the user used a protection move last turn, making this one
|
||||
# less likely to work
|
||||
score -= (user.effects[PBEffects::ProtectRate] - 1) * 8
|
||||
# TODO: Check for combos with Flail/Endeavor?
|
||||
next score
|
||||
}
|
||||
)
|
||||
@@ -1353,7 +1352,8 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("EnsureNextMoveAlwaysHits
|
||||
score += 8 if acc <= 50 && acc != 0
|
||||
score += 8 if m.is_a?(Battle::Move::OHKO)
|
||||
end
|
||||
# TODO: Prefer if target has increased evasion.
|
||||
# Prefer if the target has increased evasion
|
||||
score += 5 * target.stages[:EVASION] if target.stages[:EVASION] > 0
|
||||
# Not worth it if the user or the target is at low HP
|
||||
if ai.trainer.has_skill_flag?("HPAware")
|
||||
score -= 10 if user.hp < user.totalhp / 2
|
||||
|
||||
@@ -15,7 +15,6 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("HitTwoTimes",
|
||||
num_hits = move.move.pbNumHits(user.battler, [target.battler])
|
||||
score += 10 if target.effects[PBEffects::Substitute] < dmg * (num_hits - 1) / num_hits
|
||||
end
|
||||
# TODO: Consider effects that trigger per hit.
|
||||
next score
|
||||
}
|
||||
)
|
||||
@@ -80,7 +79,6 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("HitThreeTimesPowersUpWit
|
||||
dmg = move.rough_damage
|
||||
score += 10 if target.effects[PBEffects::Substitute] < dmg / 2
|
||||
end
|
||||
# TODO: Consider effects that trigger per hit.
|
||||
next score
|
||||
}
|
||||
)
|
||||
@@ -111,7 +109,6 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("HitTwoToFiveTimes",
|
||||
num_hits = (user.has_active_ability?(:SKILLLINK)) ? 5 : 3 # 3 is about average
|
||||
score += 10 if target.effects[PBEffects::Substitute] < dmg * (num_hits - 1) / num_hits
|
||||
end
|
||||
# TODO: Consider effects that trigger per hit.
|
||||
next score
|
||||
}
|
||||
)
|
||||
@@ -183,7 +180,6 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("HitOncePerUserTeamMember
|
||||
end
|
||||
score += 10 if target.effects[PBEffects::Substitute] < dmg * (num_hits - 1) / num_hits
|
||||
end
|
||||
# TODO: Consider effects that trigger per hit.
|
||||
next score
|
||||
}
|
||||
)
|
||||
|
||||
@@ -159,8 +159,9 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("StartTargetCannotUseIte
|
||||
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("StartTargetCannotUseItem",
|
||||
proc { |score, move, user, target, ai, battle|
|
||||
next Battle::AI::MOVE_USELESS_SCORE if !target.item || !target.item_active?
|
||||
# TODO: Useless if target's item cannot be negated or it has no effect.
|
||||
# TODO: Useless if target's item cannot be negated.
|
||||
item_score = target.wants_item?(target.item_id)
|
||||
next Battle::AI::MOVE_USELESS_SCORE if item_score <= 0 # Item has no effect or is bad
|
||||
score += item_score * 2
|
||||
next score
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user