Tackling of various AI "TODO" comments, a little tidying

This commit is contained in:
Maruno17
2023-02-17 21:36:08 +00:00
parent 81d069eef1
commit 0e4053f837
14 changed files with 275 additions and 549 deletions

View File

@@ -91,13 +91,6 @@ module Battle::AI::Handlers
MoveBasePower = HandlerHash.new
GeneralMoveScore = HandlerHash.new
GeneralMoveAgainstTargetScore = HandlerHash.new
# TODO: Make HandlerHashes for these?
# Move type - uses main battle code via rough_type
# Move accuracy - uses main battle code via rough_accuracy
# Move target
# Move additional effect chance
# Move unselectable check
# Move failure check
ShouldSwitch = HandlerHash.new
def self.move_will_fail?(function_code, *args)

View File

@@ -33,32 +33,35 @@ class Battle::AI
set_up_move_check(move)
# Predict whether the move will fail (generally)
if @trainer.has_skill_flag?("PredictMoveFailure") && pbPredictMoveFailure
PBDebug.log_ai("#{@user.name} is considering using #{move.name}...")
PBDebug.log_ai("#{@user.name} is considering using #{@move.name}...")
PBDebug.log_score_change(MOVE_FAIL_SCORE - MOVE_BASE_SCORE, "move will fail")
add_move_to_choices(choices, idxMove, MOVE_FAIL_SCORE)
next
end
# Get the move's target type
target_data = @move.pbTarget(@user.battler)
# TODO: Alter target_data if user has Protean and move is Curse.
if @move.function == "CurseTargetOrLowerUserSpd1RaiseUserAtkDef1" &&
@move.rough_type == :GHOST && @user.has_active_ability?([:LIBERO, :PROTEAN])
target_data = GameData::Target.get((Settings::MECHANICS_GENERATION >= 8) ? :RandomNearFoe : :NearFoe)
end
case target_data.num_targets
when 0 # No targets, affects the user or a side or the whole field
# Includes: BothSides, FoeSide, None, User, UserSide
PBDebug.log_ai("#{@user.name} is considering using #{move.name}...")
PBDebug.log_ai("#{@user.name} is considering using #{@move.name}...")
score = MOVE_BASE_SCORE
PBDebug.logonerr { score = pbGetMoveScore }
add_move_to_choices(choices, idxMove, score)
when 1 # One target to be chosen by the trainer
# Includes: Foe, NearAlly, NearFoe, NearOther, Other, RandomNearFoe, UserOrNearAlly
# TODO: Figure out first which targets are valid. Includes the call to
# pbMoveCanTarget?, but also includes move-redirecting effects
# like Lightning Rod. Skip any battlers that can't be targeted.
redirected_target = get_redirected_target(target_data)
num_targets = 0
@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 #{move.name} against #{b.name} (#{b.index})...")
PBDebug.log_ai("#{@user.name} is considering using #{@move.name} against #{b.name} (#{b.index})...")
score = MOVE_BASE_SCORE
PBDebug.logonerr { score = pbGetMoveScore([b]) }
add_move_to_choices(choices, idxMove, score, b.index)
@@ -72,7 +75,7 @@ class Battle::AI
next if !@battle.pbMoveCanTarget?(@user.battler.index, b.index, target_data)
targets.push(b)
end
PBDebug.log_ai("#{@user.name} is considering using #{move.name}...")
PBDebug.log_ai("#{@user.name} is considering using #{@move.name}...")
score = MOVE_BASE_SCORE
PBDebug.logonerr { score = pbGetMoveScore(targets) }
add_move_to_choices(choices, idxMove, score)
@@ -82,6 +85,44 @@ class Battle::AI
return choices
end
def get_redirected_target(target_data)
return nil if @move.move.cannotRedirect?
return nil if !target_data.can_target_one_foe? || target_data.num_targets != 1
return nil if @user.has_active_ability?([:PROPELLERTAIL, :STALWART])
priority = @battle.pbPriority(true)
near_only = !target_data.can_choose_distant_target?
# Spotlight, Follow Me/Rage Powder
new_target = -1
strength = 100 # Lower strength takes priority
priority.each do |b|
next if b.fainted? || b.effects[PBEffects::SkyDrop] >= 0
next if !b.opposes?(@user.battler)
next if near_only && !b.near?(@user.battler)
if b.effects[PBEffects::Spotlight] > 0 && b.effects[PBEffects::Spotlight] - 50 < strength
new_target = b.index
strength = b.effects[PBEffects::Spotlight] - 50 # Spotlight takes priority
elsif (b.effects[PBEffects::RagePowder] && @user.battler.affectedByPowder?) ||
(b.effects[PBEffects::FollowMe] > 0 && b.effects[PBEffects::FollowMe] < strength)
new_target = b.index
strength = b.effects[PBEffects::FollowMe]
end
end
return new_target if new_target
calc_type = @move.rough_type
priority.each do |b|
next if b.index == @user.index
next if near_only && !b.near?(@user.battler)
case calc_type
when :ELECTRIC
new_target = b.index if b.hasActiveAbility?(:LIGHTNINGROD)
when :WATER
new_target = b.index if b.hasActiveAbility?(:STORMDRAIN)
end
break if new_target >= 0
end
return new_target
end
def add_move_to_choices(choices, idxMove, score, idxTarget = -1)
choices.push([idxMove, score, idxTarget])
# If the user is a wild Pokémon, doubly prefer one of its moves (the choice
@@ -123,19 +164,16 @@ class Battle::AI
#=============================================================================
# Returns whether the move will definitely fail (assuming no battle conditions
# change between now and using the move).
# TODO: Add skill checks in here for particular calculations?
#=============================================================================
def pbPredictMoveFailure
# TODO: Something involving user.battler.usingMultiTurnAttack? (perhaps
# earlier than this?).
# User is asleep and will not wake up
return true if @user.battler.asleep? && @user.statusCount > 1 && !@move.move.usableWhenAsleep?
# User is awake and can't use moves that are only usable when asleep
return true if !@user.battler.asleep? && @move.move.usableWhenAsleep?
# User will be truanting
# TODO: Should Truanting treat all moves as failing? If it does, it will
# trigger switching due to terrible moves.
# return true if @user.has_active_ability?(:TRUANT) && @user.effects[PBEffects::Truant]
# NOTE: Truanting is not considered, because if it is, a Pokémon with Truant
# will want to switch due to terrible moves every other round (because
# all of its moves will fail), and this is disruptive and shouldn't be
# how such Pokémon behave.
# Primal weather
return true if @battle.pbWeather == :HeavyRain && @move.rough_type == :FIRE
return true if @battle.pbWeather == :HarshSun && @move.rough_type == :WATER
@@ -151,8 +189,7 @@ 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: Check for target-redirecting abilities that also provide immunity.
# If an ally has such an ability, may want to just not prefer the move
# 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)
@@ -212,7 +249,6 @@ class Battle::AI
return MOVE_FAIL_SCORE
end
else
# TODO: Can this accounting for multiple targets be improved somehow?
score /= affected_targets if affected_targets > 1 # Average the score against multiple targets
# Bonus for affecting multiple targets
if @trainer.has_skill_flag?("PreferMultiTargetMoves") && affected_targets > 1

View File

@@ -7,7 +7,8 @@ class Battle::AI
# could target a foe but is targeting an ally, the score is also inverted, but
# only because it is inverted again in def pbGetMoveScoreAgainstTarget.
#=============================================================================
def get_score_for_target_stat_raise(score, target, stat_changes, whole_effect = true, fixed_change = false)
def get_score_for_target_stat_raise(score, target, stat_changes, whole_effect = true,
fixed_change = false, ignore_contrary = false)
whole_effect = false if @move.damagingMove?
# Decide whether the target raising its stat(s) is a good thing
desire_mult = 1
@@ -15,13 +16,13 @@ class Battle::AI
(@move.pbTarget(@user.battler).targets_foe && target.index != @user.index)
desire_mult = -1
end
# Discard status move/don't prefer damaging move if target has Contrary
# TODO: Maybe this should return get_score_for_target_stat_drop if Contrary
# applies and desire_mult < 1.
if !fixed_change && !@battle.moldBreaker && target.has_active_ability?(:CONTRARY) && desire_mult > 0
ret = (whole_effect) ? MOVE_USELESS_SCORE : score - 20
PBDebug.log_score_change(ret - score, "don't prefer raising target's stats (it has Contrary)")
return ret
# If target has Contrary, use different calculations to score the stat change
if !ignore_contrary && !fixed_change && !@battle.moldBreaker && target.has_active_ability?(:CONTRARY)
if desire_mult > 0 && whole_effect
PBDebug.log_score_change(MOVE_USELESS_SCORE - score, "don't prefer raising target's stats (it has Contrary)")
return MOVE_USELESS_SCORE
end
return get_score_for_target_stat_drop(score, target, stat_changes, whole_effect, fixed_change, true)
end
# Don't make score changes if target will faint from EOR damage
if target.rough_end_of_round_damage >= target.hp
@@ -42,7 +43,6 @@ class Battle::AI
return ret
end
end
# Figure out which stat raises can happen
real_stat_changes = []
stat_changes.each_with_index do |stat, idx|
@@ -66,10 +66,8 @@ class Battle::AI
if real_stat_changes.length == 0
return (whole_effect) ? MOVE_USELESS_SCORE : score
end
# Make score changes based on the general concept of raising stats at all
score = get_target_stat_raise_score_generic(score, target, real_stat_changes, desire_mult)
# Make score changes based on the specific changes to each stat that will be
# raised
real_stat_changes.each do |change|
@@ -81,7 +79,6 @@ class Battle::AI
PBDebug.log_score_change(score - old_score, "raising the target's #{GameData::Stat.get(change[0]).name} by #{change[1]}")
end
end
return score
end
@@ -146,12 +143,10 @@ class Battle::AI
# able to 2HKO the target.
# TODO: Prefer if foe is semi-invulnerable and target is faster (can't hit
# the foe anyway).
# Prefer if move is a status move and it's the user's first/second turn
if @user.turnCount < 2 && @move.statusMove?
score += total_increment * desire_mult * 4
end
# Prefer if user is at high HP, don't prefer if user is at low HP
if target.index != @user.index
if @user.hp >= @user.totalhp * 0.7
@@ -166,97 +161,8 @@ class Battle::AI
else
score += total_increment * desire_mult * ((100 * target.hp / target.totalhp) - 50) / 4 # +5 to -12 per stage
end
# TODO: Look at abilities that trigger upon stat raise. There are none.
return score
=begin
mini_score = 1.0
# Determine whether the move boosts Attack, Special Attack or Speed (Bulk Up
# is sometimes not considered a sweeping move)
sweeping_stat = false
offensive_stat = false
stat_changes.each do |change|
next if ![:ATTACK, :SPECIAL_ATTACK, :SPEED].include?(change[0])
sweeping_stat = true
next if @move.function == "RaiseUserAtkDef1" # Bulk Up (+Atk +Def)
offensive_stat = true
break
end
# TODO: Prefer if user's moves won't do much damage.
# Prefer if user has something that will limit damage taken
mini_score *= 1.3 if @user.effects[PBEffects::Substitute] > 0 ||
(@user.form == 0 && @user.ability_id == :DISGUISE)
# Don't prefer if user is badly poisoned
mini_score *= 0.2 if @user.effects[PBEffects::Toxic] > 0 && !offensive_stat
# Don't prefer if user is confused
if @user.effects[PBEffects::Confusion] > 0
# TODO: Especially don't prefer if the move raises Atk. Even more so if
# the move raises the stat by 2+. Not quite so much if the move also
# raises Def.
mini_score *= 0.5
end
# Don't prefer if user is infatuated or Leech Seeded
if @user.effects[PBEffects::Attract] >= 0 || @user.effects[PBEffects::LeechSeed] >= 0
mini_score *= (offensive_stat) ? 0.6 : 0.3
end
# Don't prefer if user has an ability or item that will force it to switch
# out
if @user.hp < @user.totalhp * 3 / 4
mini_score *= 0.3 if @user.hasActiveAbility?([:EMERGENCYEXIT, :WIMPOUT])
mini_score *= 0.3 if @user.hasActiveItem?(:EJECTBUTTON)
end
# Prefer if target has a status problem
if @target.status != :NONE
mini_score *= (sweeping_stat) ? 1.2 : 1.1
case @target.status
when :SLEEP, :FROZEN
mini_score *= 1.3
when :BURN
# TODO: Prefer if the move boosts Sp Def.
mini_score *= 1.1 if !offensive_stat
end
end
# Prefer if target is yawning
if @target.effects[PBEffects::Yawn] > 0
mini_score *= (sweeping_stat) ? 1.7 : 1.3
end
# Prefer if target is recovering after Hyper Beam
if @target.effects[PBEffects::HyperBeam] > 0
mini_score *= (sweeping_stat) ? 1.3 : 1.2
end
# Prefer if target is Encored into a status move
if @target.effects[PBEffects::Encore] > 0 &&
GameData::Move.get(@target.effects[PBEffects::EncoreMove]).category == 2 # Status move
# TODO: Why should this check greatly prefer raising both the user's defences?
if sweeping_stat || @move.function == "RaiseUserDefSpDef1" # +Def +SpDef
mini_score *= 1.5
else
mini_score *= 1.3
end
end
# TODO: Don't prefer if target has previously used a move that would force
# the user to switch (or Yawn/Perish Song which encourage it). Prefer
# instead if the move raises evasion. Note this comes after the
# dissociation of Bulk Up from sweeping_stat.
if @trainer.medium_skill?
# TODO: Prefer if the maximum damage the target has dealt wouldn't hurt
# the user much.
end
# Don't prefer if it's not a single battle
if !@battle.singleBattle?
mini_score *= (offensive_stat) ? 0.25 : 0.5
end
return mini_score
=end
end
#=============================================================================
@@ -287,7 +193,6 @@ class Battle::AI
inc = (has_special_moves) ? 10 : 20
score += inc * inc_mult
end
when :DEFENSE
# Modify score depending on current stat stage
if old_stage >= 2 && increment == 1
@@ -295,7 +200,6 @@ class Battle::AI
else
score += 10 * inc_mult
end
when :SPECIAL_ATTACK
# Modify score depending on current stat stage
# More strongly prefer if the target has no physical moves
@@ -308,7 +212,6 @@ class Battle::AI
inc = (has_physical_moves) ? 10 : 20
score += inc * inc_mult
end
when :SPECIAL_DEFENSE
# Modify score depending on current stat stage
if old_stage >= 2 && increment == 1
@@ -316,7 +219,6 @@ class Battle::AI
else
score += 10 * inc_mult
end
when :SPEED
# Prefer if target is slower than a foe
# TODO: Don't prefer if the target is too much slower than any foe that it
@@ -345,7 +247,6 @@ class Battle::AI
if target.has_active_ability?(:SPEEDBOOST)
score -= 20 * ((target.opposes?(@user)) ? 1 : desire_mult)
end
when :ACCURACY
# Modify score depending on current stat stage
if old_stage >= 2 && increment == 1
@@ -361,15 +262,11 @@ class Battle::AI
score += 10 * inc_mult
end
end
when :EVASION
# Prefer if a foe will (probably) take damage at the end of the round
# TODO: Should this take into account EOR healing, one-off damage and
# damage-causing effects that wear off naturally (like Sea of Fire)?
# TODO: Emerald AI also prefers if target is rooted via Ingrain.
# Prefer if a foe of the target will take damage at the end of the round
each_foe_battler(target.side) do |b, i|
eor_damage = b.rough_end_of_round_damage
next if eor_damage <= 0
score += 4 * inc_mult if eor_damage > 0
end
# Modify score depending on current stat stage
if old_stage >= 2 && increment == 1
@@ -377,9 +274,7 @@ class Battle::AI
else
score += 10 * inc_mult
end
end
# Prefer if target has Stored Power
if target.has_move_with_function?("PowerHigherWithUserPositiveStatStages")
score += 5 * increment * desire_mult
@@ -389,212 +284,7 @@ class Battle::AI
next if !b.has_move_with_function?("PowerHigherWithTargetPositiveStatStages")
score -= 5 * increment * desire_mult
end
return score
=begin
mini_score = 1.0
case stat
when :ATTACK
# Prefer if user can definitely survive a hit no matter how powerful, and
# it won't be hurt by weather
# if @user.hp == @user.totalhp &&
# (@user.hasActiveItem?(:FOCUSSASH) || (!@battle.moldBreaker && @user.hasActiveAbility?(:STURDY)))
# if !(@battle.pbWeather == :Sandstorm && @user.takesSandstormDamage?) &&
# !(@battle.pbWeather == :Hail && @user.takesHailDamage?) &&
# !(@battle.pbWeather == :ShadowSky && @user.takesShadowSkyDamage?)
# mini_score *= 1.4
# end
# end
# Prefer if user has the Sweeper role
# TODO: Is 1.1x for RaiseUserAtkDefAcc1 Coil (+Atk, +Def, +acc).
# mini_score *= 1.3 if check_battler_role(@user, BattleRole::SWEEPER)
# # Don't prefer if user is burned or paralysed
# mini_score *= 0.5 if @user.status == :BURN || @user.status == :PARALYSIS
# # Don't prefer if user's Speed stat is lowered
# sum_stages = @user.stages[:SPEED]
# mini_score *= 1 + sum_stages * 0.05 if sum_stages < 0
# # TODO: Prefer if target has previously used a HP-restoring move.
# # TODO: Don't prefer if some of foes' stats are raised.
# sum_stages = 0
# [:ATTACK, :SPECIAL_ATTACK, :SPEED].each do |s|
# sum_stages += @target.stages[s]
# end
# mini_score *= 1 - sum_stages * 0.05 if sum_stages > 0
# # TODO: Don't prefer if target has Speed Boost (+Spd at end of each round).
# mini_score *= 0.6 if @target.hasActiveAbility?(:SPEEDBOOST)
# # TODO: Don't prefer if the target has previously used a priority move.
when :DEFENSE
# Prefer if user has a healing item
# TODO: Is 1.1x for RaiseUserAtkDefAcc1 Coil (+Atk, +Def, +acc).
# mini_score *= 1.2 if @user.hasActiveItem?(:LEFTOVERS) ||
# (@user.hasActiveItem?(:BLACKSLUDGE) && @user.pbHasType?(:POISON))
# Prefer if user knows any healing moves
# # TODO: Is 1.2x for RaiseUserAtkDefAcc1 Coil (+Atk, +Def, +acc).
# mini_score *= 1.3 if check_for_move(@user) { |m| m.healingMove? }
# Prefer if user knows Pain Split or Leech Seed
# # TODO: Leech Seed is 1.2x for RaiseUserAtkDefAcc1 Coil (+Atk, +Def, +acc).
# mini_score *= 1.2 if @user.pbHasMoveFunction?("UserTargetAverageHP") # Pain Split
# mini_score *= 1.3 if @user.pbHasMoveFunction?("StartLeechSeedTarget") # Leech Seed
# Prefer if user has certain roles
# # TODO: Is 1.1x for RaiseUserAtkDefAcc1 Coil (+Atk, +Def, +acc).
# mini_score *= 1.3 if check_battler_role(@user, BattleRole::PHYSICALWALL, BattleRole::SPECIALWALL)
# Don't prefer if user is badly poisoned
mini_score *= 0.2 if @user.effects[PBEffects::Toxic] > 0
# # TODO: Prefer if foes have higher Attack than Special Attack, and user
# # doesn't have a wall role, user is faster and user has at least 75%
# # HP. Don't prefer instead if user is slower (ignore HP).
# TODO: Don't prefer if previous damage done by foes wouldn't hurt the
# user much.
when :SPEED
# Don't prefer if user has Speed Boost
# mini_score *= 0.6 if @user.hasActiveAbility?(:SPEEDBOOST)
# Prefer if user can definitely survive a hit no matter how powerful, and
# it won't be hurt by weather
# if @user.hp == @user.totalhp &&
# (@user.hasActiveItem?(:FOCUSSASH) || (!@battle.moldBreaker && @user.hasActiveAbility?(:STURDY)))
# if !(@battle.pbWeather == :Sandstorm && @user.takesSandstormDamage?) &&
# !(@battle.pbWeather == :Hail && @user.takesHailDamage?) &&
# !(@battle.pbWeather == :ShadowSky && @user.takesShadowSkyDamage?)
# mini_score *= 1.4
# end
# end
# Prefer if user has the Sweeper role
# mini_score *= 1.3 if check_battler_role(@user, BattleRole::SWEEPER)
# TODO: Don't prefer if Trick Room applies or any foe has previously used
# Trick Room.
# mini_score *= 0.2 if @battle.field.effects[PBEffects::TrickRoom] > 0
# # Prefer if user's Attack/SpAtk stat (whichever is higher) is lowered
# # TODO: Why?
# if @user.attack > @user.spatk
# sum_stages = @user.stages[:ATTACK]
# mini_score *= 1 - sum_stages * 0.05 if sum_stages < 0
# else
# sum_stages = @user.stages[:SPATK]
# mini_score *= 1 - sum_stages * 0.05 if sum_stages < 0
# end
# # Prefer if user has Moxie
# mini_score *= 1.3 if @user.hasActiveAbility?(:MOXIE)
# # Don't prefer if user is burned or paralysed
# mini_score *= 0.2 if @user.status == :PARALYSIS
# # TODO: Don't prefer if target has raised defenses.
# sum_stages = 0
# [:DEFENSE, :SPECIAL_DEFENSE].each { |s| sum_stages += @target.stages[s] }
# mini_score *= 1 - sum_stages * 0.05 if sum_stages > 0
# # TODO: Don't prefer if the target has previously used a priority move.
# # TODO: Don't prefer if user is already faster than the target and there's
# # only 1 unfainted foe (this check is done by Agility/Autotomize
# # (both +2 Spd) only in Reborn.)
when :SPECIAL_ATTACK
# Prefer if user can definitely survive a hit no matter how powerful, and
# it won't be hurt by weather
# if @user.hp == @user.totalhp &&
# (@user.hasActiveItem?(:FOCUSSASH) || (!@battle.moldBreaker && @user.hasActiveAbility?(:STURDY)))
# if !(@battle.pbWeather == :Sandstorm && @user.takesSandstormDamage?) &&
# !(@battle.pbWeather == :Hail && @user.takesHailDamage?) &&
# !(@battle.pbWeather == :ShadowSky && @user.takesShadowSkyDamage?)
# mini_score *= 1.4
# end
# end
# Prefer if user has the Sweeper role
# mini_score *= 1.3 if check_battler_role(@user, BattleRole::SWEEPER)
# # Don't prefer if user's Speed stat is lowered
# sum_stages = @user.stages[:SPEED]
# mini_score *= 1 + sum_stages * 0.05 if sum_stages < 0
# # TODO: Prefer if target has previously used a HP-restoring move.
# # TODO: Don't prefer if some of foes' stats are raised
# sum_stages = 0
# [:ATTACK, :SPECIAL_ATTACK, :SPEED].each do |s|
# sum_stages += @target.stages[s]
# end
# mini_score *= 1 - sum_stages * 0.05 if sum_stages > 0
# # TODO: Don't prefer if target has Speed Boost (+Spd at end of each round)
# mini_score *= 0.6 if @target.hasActiveAbility?(:SPEEDBOOST)
# # TODO: Don't prefer if the target has previously used a priority move.
when :SPECIAL_DEFENSE
# Prefer if user has a healing item
# mini_score *= 1.2 if @user.hasActiveItem?(:LEFTOVERS) ||
# (@user.hasActiveItem?(:BLACKSLUDGE) && @user.pbHasType?(:POISON))
# Prefer if user knows any healing moves
# mini_score *= 1.3 if check_for_move(@user) { |m| m.healingMove? }
# Prefer if user knows Pain Split or Leech Seed
# mini_score *= 1.2 if @user.pbHasMoveFunction?("UserTargetAverageHP") # Pain Split
# mini_score *= 1.3 if @user.pbHasMoveFunction?("StartLeechSeedTarget") # Leech Seed
# Prefer if user has certain roles
# mini_score *= 1.3 if check_battler_role(@user, BattleRole::PHYSICALWALL, BattleRole::SPECIALWALL)
# # TODO: Prefer if foes have higher Special Attack than Attack.
# TODO: Don't prefer if previous damage done by foes wouldn't hurt the
# user much.
when :ACCURACY
# Prefer if user knows any weaker moves
mini_score *= 1.1 if check_for_move(@user) { |m| m.damagingMove? && m.power < 95 }
# Prefer if target has a raised evasion
sum_stages = @target.stages[:EVASION]
mini_score *= 1 + sum_stages * 0.05 if sum_stages > 0
# Prefer if target has an item that lowers foes' accuracy
mini_score *= 1.1 if @target.hasActiveItem?([:BRIGHTPOWDER, :LAXINCENSE])
# Prefer if target has an ability that lowers foes' accuracy
# TODO: Tangled Feet while user is confused?
if (@battle.pbWeather == :Sandstorm && @target.hasActiveAbility?(:SANDVEIL)) ||
(@battle.pbWeather == :Hail && @target.hasActiveAbility?(:SNOWCLOAK))
mini_score *= 1.1
end
when :EVASION
# Prefer if user has a healing item
mini_score *= 1.2 if @user.hasActiveItem?(:LEFTOVERS) ||
(@user.hasActiveItem?(:BLACKSLUDGE) && @user.pbHasType?(:POISON))
# Prefer if user has an item that lowers foes' accuracy
mini_score *= 1.3 if @user.hasActiveItem?([:BRIGHTPOWDER, :LAXINCENSE])
# Prefer if user has an ability that lowers foes' accuracy
# TODO: Tangled Feet while user is confused?
if (@battle.pbWeather == :Sandstorm && @user.hasActiveAbility?(:SANDVEIL)) ||
(@battle.pbWeather == :Hail && @user.hasActiveAbility?(:SNOWCLOAK))
mini_score *= 1.3
end
# Prefer if user knows any healing moves
mini_score *= 1.3 if check_for_move(@user) { |m| move.healingMove? }
# Prefer if user knows Pain Split or Leech Seed
mini_score *= 1.2 if @user.pbHasMoveFunction?("UserTargetAverageHP") # Pain Split
mini_score *= 1.3 if @user.pbHasMoveFunction?("StartLeechSeedTarget") # Leech Seed
# Prefer if user has certain roles
mini_score *= 1.3 if check_battler_role(@user, BattleRole::PHYSICALWALL, BattleRole::SPECIALWALL)
# TODO: Don't prefer if user's evasion stat is raised
# TODO: Don't prefer if target has No Guard.
mini_score *= 0.2 if @target.hasActiveAbility?(:NOGUARD)
# TODO: Don't prefer if target has previously used any moves that never miss.
end
# TODO: Don't prefer if any foe has previously used a stat stage-clearing
# move (Clear Smog/Haze).
mini_score *= 0.3 if check_for_move(@target) { |m|
["ResetTargetStatStages", "ResetAllBattlersStatStages"].include?(m.function)
} # Clear Smog, Haze
# TODO: Prefer if user is faster than the target.
# TODO: Is 1.3x for RaiseUserAtkDefAcc1 Coil (+Atk, +Def, +acc).
mini_score *= 1.5 if @user.faster_than?(@target)
# TODO: Don't prefer if target is a higher level than the user
if @target.level > @user.level + 5
mini_score *= 0.6
if @target.level > @user.level + 10
mini_score *= 0.2
end
end
return mini_score
=end
end
#=============================================================================
@@ -606,10 +296,9 @@ class Battle::AI
# ally, but only because it is inverted in def pbGetMoveScoreAgainstTarget
# instead.
# TODO: Revisit this method as parts may need rewriting.
# TODO: fixed_change should make this ignore Mist/Clear Body/other effects
# that prevent increments/decrements to stat stages.
#=============================================================================
def get_score_for_target_stat_drop(score, target, stat_changes, whole_effect = true, fixed_change = false)
def get_score_for_target_stat_drop(score, target, stat_changes, whole_effect = true,
fixed_change = false, ignore_contrary = false)
whole_effect = false if @move.damagingMove?
# Decide whether the target lowering its stat(s) is a good thing
desire_mult = -1
@@ -617,13 +306,13 @@ class Battle::AI
(@move.pbTarget(@user.battler).targets_foe && target.index != @user.index)
desire_mult = 1
end
# Discard status move/don't prefer damaging move if target has Contrary
# TODO: Maybe this should return get_score_for_target_stat_raise if Contrary
# applies and desire_mult < 0.
if !fixed_change && !@battle.moldBreaker && target.has_active_ability?(:CONTRARY) && desire_mult > 0
ret = (whole_effect) ? MOVE_USELESS_SCORE : score - 20
PBDebug.log_score_change(ret - score, "don't prefer lowering target's stats (it has Contrary)")
return ret
# If target has Contrary, use different calculations to score the stat change
if !ignore_contrary && !fixed_change && !@battle.moldBreaker && target.has_active_ability?(:CONTRARY)
if desire_mult > 0 && whole_effect
PBDebug.log_score_change(MOVE_USELESS_SCORE - score, "don't prefer lowering target's stats (it has Contrary)")
return MOVE_USELESS_SCORE
end
return get_score_for_target_stat_raise(score, target, stat_changes, whole_effect, fixed_change, true)
end
# Don't make score changes if target will faint from EOR damage
if target.rough_end_of_round_damage >= target.hp
@@ -642,7 +331,6 @@ class Battle::AI
PBDebug.log(" ignore stat change (target's foes have Unaware)")
return ret
end
# Figure out which stat drops can happen
real_stat_changes = []
stat_changes.each_with_index do |stat, idx|
@@ -666,10 +354,8 @@ class Battle::AI
if real_stat_changes.length == 0
return (whole_effect) ? MOVE_USELESS_SCORE : score
end
# Make score changes based on the general concept of lowering stats at all
score = get_target_stat_drop_score_generic(score, target, real_stat_changes, desire_mult)
# Make score changes based on the specific changes to each stat that will be
# lowered
real_stat_changes.each do |change|
@@ -681,7 +367,6 @@ class Battle::AI
PBDebug.log_score_change(score - old_score, "lowering the target's #{GameData::Stat.get(change[0]).name} by #{change[1]}")
end
end
return score
end
@@ -746,12 +431,10 @@ class Battle::AI
# TODO: Don't prefer if target is slower than user but is predicted to be able
# to 2HKO user.
# TODO: Don't prefer if target is semi-invulnerable and user is faster.
# Prefer if move is a status move and it's the user's first/second turn
if @user.turnCount < 2 && @move.statusMove?
score += total_decrement * desire_mult * 4
end
# Prefer if user is at high HP, don't prefer if user is at low HP
if target.index != @user.index
if @user.hp >= @user.totalhp * 0.7
@@ -766,9 +449,7 @@ class Battle::AI
else
score += total_decrement * desire_mult * ((100 * target.hp / target.totalhp) - 50) / 6 # +3 to -8 per stage
end
# TODO: Look at abilities that trigger upon stat lowering.
return score
end
@@ -801,7 +482,6 @@ class Battle::AI
dec = (has_special_moves) ? 5 : 10
score += dec * dec_mult
end
when :DEFENSE
# Modify score depending on current stat stage
if old_stage <= -2 && decrement == 1
@@ -809,7 +489,6 @@ class Battle::AI
else
score += 5 * dec_mult
end
when :SPECIAL_ATTACK
# Modify score depending on current stat stage
# More strongly prefer if the target has no physical moves
@@ -822,7 +501,6 @@ class Battle::AI
dec = (has_physical_moves) ? 5 : 10
score += dec * dec_mult
end
when :SPECIAL_DEFENSE
# Modify score depending on current stat stage
if old_stage <= -2 && decrement == 1
@@ -830,7 +508,6 @@ class Battle::AI
else
score += 5 * dec_mult
end
when :SPEED
# Prefer if target is faster than an ally
# TODO: Don't prefer if the target is too much faster than any ally and
@@ -849,7 +526,6 @@ class Battle::AI
if target.has_active_ability?(:SPEEDBOOST)
score -= 20 * ((target.opposes?(@user)) ? 1 : desire_mult)
end
when :ACCURACY
# Modify score depending on current stat stage
if old_stage <= -2 && decrement == 1
@@ -858,7 +534,6 @@ class Battle::AI
score += 5 * 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
@@ -866,9 +541,7 @@ class Battle::AI
else
score += 5 * dec_mult
end
end
# Prefer if target has Stored Power
if target.has_move_with_function?("PowerHigherWithUserPositiveStatStages")
score += 5 * decrement * desire_mult
@@ -878,7 +551,6 @@ class Battle::AI
next if !b.has_move_with_function?("PowerHigherWithTargetPositiveStatStages")
score -= 5 * decrement * desire_mult
end
return score
end

View File

@@ -2,7 +2,7 @@
# because of them.
#===============================================================================
# TODO: Review score modifier.
#
#===============================================================================
# TODO:
# => Don't prefer damaging move if it won't KO, user has Stance Change and
@@ -24,13 +24,13 @@
#===============================================================================
# Don't prefer hitting a wild shiny Pokémon.
# TODO: Review score modifier.
#===============================================================================
Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:shiny_target,
proc { |score, move, user, target, ai, battle|
if target.wild? && target.battler.shiny?
PBDebug.log_score_change(-40, "avoid attacking a shiny wild Pokémon")
score -= 40
old_score = score
score -= 15
PBDebug.log_score_change(score - old_score, "avoid attacking a shiny wild Pokémon")
end
next score
}
@@ -51,7 +51,7 @@ Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:add_predicted_damage,
if move.damagingMove?
dmg = move.rough_damage
old_score = score
score += ([30.0 * dmg / target.hp, 35].min).to_i
score += ([25.0 * dmg / target.hp, 30].min).to_i
PBDebug.log_score_change(score - old_score, "damaging move (predicted damage #{dmg} = #{100 * dmg / target.hp}% of target's HP)")
if dmg > target.hp * 1.1 # Predicted to KO the target
old_score = score
@@ -115,14 +115,13 @@ Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:target_semi_invulnerabl
)
#===============================================================================
# TODO: Review score modifier.
#
#===============================================================================
# TODO: Less prefer two-turn moves, as the foe can see it coming and prepare for
# it.
#===============================================================================
# If target is frozen, don't prefer moves that could thaw them.
# TODO: Review score modifier.
#===============================================================================
Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:thawing_move_against_frozen_target,
proc { |score, move, user, target, ai, battle|
@@ -138,7 +137,7 @@ Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:thawing_move_against_fr
)
#===============================================================================
# TODO: Review score modifier.
#
#===============================================================================
# TODO: Prefer move if it has a high critical hit rate, critical hits are
# possible but not certain, and target has raised defences/user has
@@ -169,7 +168,7 @@ Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:flinching_effects,
)
#===============================================================================
# TODO: Review score modifier.
#
#===============================================================================
# TODO: Don't prefer contact move if making contact with the target could
# trigger an effect that's bad for the user (Static, etc.).
@@ -177,29 +176,13 @@ Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:flinching_effects,
# Baneful Bunker, and don't prefer move if so
#===============================================================================
# TODO: Review score modifier.
#
#===============================================================================
# TODO: Prefer a contact move if making contact with the target could trigger
# an effect that's good for the user (Poison Touch/Pickpocket).
#===============================================================================
# Don't prefer a dancing move if the target has the Dancer ability.
# TODO: Review score modifier.
# TODO: Check all battlers, not just the target.
#===============================================================================
Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:dance_move_against_dancer,
proc { |score, move, user, target, ai, battle|
if move.move.danceMove? && target.has_active_ability?(:DANCER)
old_score = score
score -= 12
PBDebug.log_score_change(score - old_score, "don't want to use a dance move on a target with Dancer")
end
next score
}
)
#===============================================================================
# TODO: Review score modifier.
#
#===============================================================================
# TODO: Prefer a higher priority move if the user is slower than the foe(s) and
# the user is at risk of being knocked out. Consider whether the foe(s)
@@ -207,12 +190,32 @@ Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:dance_move_against_danc
# moves?
#===============================================================================
# TODO: Review score modifier.
# Don't prefer damaging moves if the target is Biding, unless the move will deal
# enough damage to KO the target before it retaliates (assuming the move is used
# repeatedly until the target retaliates). Doesn't do a score change if the user
# will be immune to Bide's damage.
#===============================================================================
# TODO: Don't prefer damaging moves if the target is Biding, unless the move
# will deal enough damage to KO the target before it retaliates (assuming
# the move is used repeatedly until the target retaliates). Don't worry
# about the target's Bide if the user will be immune to it.
Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:avoid_damaging_a_biding_target,
proc { |score, move, user, target, ai, battle|
if ai.trainer.medium_skill? && target.effects[PBEffects::Bide] > 0 && move.damagingMove?
eff = user.effectiveness_of_type_against_battler(:NORMAL, target) # Bide is Normal type
if !Effectiveness.ineffective?(eff)
dmg = move.rough_damage
eor_dmg = target.rough_end_of_round_damage
hits_possible = target.effects[PBEffects::Bide] - 1
eor_dmg *= hits_possible
hits_possible += 1 if user.faster_than?(target)
if dmg * hits_possible + eor_dmg < target.hp * 1.05
old_score = score
score -= 15
PBDebug.log_score_change(score - old_score, "don't want to damage the Biding target")
end
end
end
next score
}
)
#===============================================================================
# Don't prefer damaging moves that will knock out the target if they are using
@@ -237,22 +240,53 @@ Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:avoid_knocking_out_dest
)
#===============================================================================
# TODO: Review score modifier.
#
#===============================================================================
# TODO: Don't prefer Fire-type moves if target has previously used Powder.
# TODO: Don't prefer Fire-type moves if target has previously used Powder and is
# faster than the user.
#===============================================================================
# TODO: Review score modifier.
#
#===============================================================================
# TODO: Check memory for whether target has previously used Ion Deluge, and
# don't prefer move if it's Normal-type and target is immune because
# of its ability (Lightning Rod, etc.).
#===============================================================================
# TODO: Review score modifier.
# Don't prefer a move that can be Magic Coated if the target (or any foe if the
# move doesn't have a target) has Magic Bounce.
#===============================================================================
# TODO: Don't prefer a move that can be Magic Coated if the target (or any foe
# if the move doesn't have a target) has Magic Bounce.
Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:avoid_targeting_bouncable_move_against_Magic_Bouncer,
proc { |score, move, user, target, ai, battle|
if move.statusMove? && move.canMagicCoat? &&
!battle.moldBreaker && target.has_active_ability?(:MAGICBOUNCE)
old_score = score
score = Battle::AI::MOVE_USELESS_SCORE
PBDebug.log_score_change(score - old_score, "useless because target will Magic Bounce it")
end
next score
}
)
Battle::AI::Handlers::GeneralMoveScore.add(:avoid_bouncable_move_with_foe_Magic_Bouncer,
proc { |score, move, user, ai, battle|
if move.statusMove? && move.canMagicCoat? &&
move.pbTarget(user.battler).num_targets == 0 && !battle.moldBreaker
has_magic_bounce = false
ai.each_foe_battler(user.side) do |b, i|
next if !b.has_active_ability?(:MAGICBOUNCE)
has_magic_bounce = true
break
end
if has_magic_bounce
old_score = score
score = Battle::AI::MOVE_USELESS_SCORE
PBDebug.log_score_change(score - old_score, "useless because a foe will Magic Bounce it")
end
end
next score
}
)
#===============================================================================
#===============================================================================
@@ -261,13 +295,12 @@ Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:avoid_knocking_out_dest
#===============================================================================
#===============================================================================
# TODO: Review score modifier.
#
#===============================================================================
# TODO: Prefer Shadow moves.
# TODO: Prefer Shadow moves (for flavour).
#===============================================================================
# If user is frozen, prefer a move that can thaw the user.
# TODO: Review score modifier.
#===============================================================================
Battle::AI::Handlers::GeneralMoveScore.add(:thawing_move_when_frozen,
proc { |score, move, user, ai, battle|
@@ -285,6 +318,22 @@ Battle::AI::Handlers::GeneralMoveScore.add(:thawing_move_when_frozen,
}
)
#===============================================================================
# Don't prefer a dancing move if the target has the Dancer ability.
#===============================================================================
Battle::AI::Handlers::GeneralMoveScore.add(:dance_move_against_dancer,
proc { |score, move, user, ai, battle|
if move.move.danceMove?
old_score = score
ai.each_foe_battler(user.side) do |b, i|
score -= 12 if b.has_active_ability?(:DANCER)
end
PBDebug.log_score_change(score - old_score, "don't want to use a dance move because a foe has Dancer")
end
next score
}
)
#===============================================================================
# Pick a good move for the Choice items.
# TODO: Review score modifier.
@@ -298,26 +347,18 @@ Battle::AI::Handlers::GeneralMoveScore.add(:good_move_for_choice_item,
if move.statusMove? && move.function != "UserTargetSwapItems"
old_score = score
score -= 25
PBDebug.log_score_change(score - old_score, "move is not suitable to be Choiced into")
PBDebug.log_score_change(score - old_score, "don't want to be Choiced into a status move")
next score
end
# Don't prefer moves of certain types
# Don't prefer moves which are 0x against at least one type
move_type = move.rough_type
# Most unpreferred types are 0x effective against another type, except
# Fire/Water/Grass
# TODO: Actually check through the types for 0x instead of hardcoding
# them.
# TODO: Reborn separately doesn't prefer Fire/Water/Grass/Electric, also
# with a 0.95x score, meaning Electric can be 0.95x twice. Why are
# these four types not preferred? Maybe because they're all not
# very effective against Dragon.
unpreferred_types = [:NORMAL, :FIGHTING, :POISON, :GROUND, :GHOST,
:FIRE, :WATER, :GRASS, :ELECTRIC, :PSYCHIC, :DRAGON]
old_score = score
score -= 5 if unpreferred_types.include?(move_type)
GameData::Type.each do |type_data|
score -= 5 if type_data.immunities.include?(move_type)
end
# Don't prefer moves with lower accuracy
score = score * move.accuracy / 100 if move.accuracy > 0
# Don't prefer moves with low PP
score -= 10 if move.move.pp < 6
score -= 10 if move.move.pp <= 5
PBDebug.log_score_change(score - old_score, "move is less suitable to be Choiced into")
end
end
@@ -353,13 +394,13 @@ Battle::AI::Handlers::GeneralMoveScore.add(:prefer_damaging_moves_if_last_pokemo
)
#===============================================================================
# TODO: Review score modifier.
#
#===============================================================================
# TODO: Don't prefer a move that is stopped by Wide Guard if any foe has
# previously used Wide Guard.
#===============================================================================
# TODO: Review score modifier.
#
#===============================================================================
# TODO: Don't prefer sound move if user hasn't been Throat Chopped but a foe has
# previously used Throat Chop.

View File

@@ -181,7 +181,6 @@ class Battle::AI::AIBattler
return ret
end
# TODO: Cache calculated rough stats? Forget them in def refresh_battler.
def rough_stat(stat)
return @battler.pbSpeed if stat == :SPEED && @ai.trainer.high_skill?
stageMul = [2, 2, 2, 2, 2, 2, 2, 3, 4, 5, 6, 7, 8]
@@ -215,7 +214,8 @@ class Battle::AI::AIBattler
end
else
@battler.pbTypes(true).each do |defend_type|
# TODO: Need to check the move's pbCalcTypeModSingle.
# TODO: Need to check the move's pbCalcTypeModSingle because particular
# moves can modify that method to give different effectivenesses.
ret *= effectiveness_of_type_against_single_battler_type(type, defend_type, user)
end
ret *= 2 if self.effects[PBEffects::TarShot] && type == :FIRE
@@ -229,14 +229,10 @@ class Battle::AI::AIBattler
def ability; return @battler.ability; end
def ability_active?
# Only a high skill AI knows what an opponent's ability is
# return false if @ai.trainer.side != @side && !@ai.trainer.high_skill?
return @battler.abilityActive?
end
def has_active_ability?(ability, ignore_fainted = false)
# Only a high skill AI knows what an opponent's ability is
# return false if @ai.trainer.side != @side && !@ai.trainer.high_skill?
return @battler.hasActiveAbility?(ability, ignore_fainted)
end
@@ -250,14 +246,10 @@ class Battle::AI::AIBattler
def item; return @battler.item; end
def item_active?
# Only a high skill AI knows what an opponent's held item is
# return false if @ai.trainer.side != @side && !@ai.trainer.high_skill?
return @battler.itemActive?
end
def has_active_item?(item)
# Only a high skill AI knows what an opponent's held item is
# return false if @ai.trainer.side != @side && !@ai.trainer.high_skill?
return @battler.hasActiveItem?(item)
end
@@ -266,6 +258,7 @@ class Battle::AI::AIBattler
def check_for_move
ret = false
@battler.eachMove do |move|
next if move.pp == 0 && move.total_pp > 0
next unless yield move
ret = true
break
@@ -708,10 +701,9 @@ class Battle::AI::AIBattler
# 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.
# TODO: This method assumes the ability isn't being negated. Should it return
# 0 if it is? The calculations that call this method separately check
# for it being negated, because they need to do something special in
# that case, so I think it's okay for this method to ignore negation.
# 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")
# TODO: Ideally replace the above list of ratings with context-sensitive
@@ -729,9 +721,7 @@ class Battle::AI::AIBattler
when :GALEWINGS
return 0 if !check_for_move { |m| m.type == :FLYING }
when :HUGEPOWER, :PUREPOWER
return 0 if !check_for_move { |m| m.physicalMove?(m.type) &&
m.function != "UseUserDefenseInsteadOfUserAttack" &&
m.function != "UseTargetAttackInsteadOfUserAttack" }
return 0 if !ai.stat_raise_worthwhile?(self, :ATTACK, true)
when :IRONFIST
return 0 if !check_for_move { |m| m.punchingMove? }
when :LIQUIDVOICE

View File

@@ -40,14 +40,14 @@ class Battle::AI::AIMove
#=============================================================================
def pbTarget(_user)
return @move.pbTarget(_user)
def pbTarget(user)
return @move.pbTarget((user.is_a?(Battle::AI::AIBattler)) ? user.battler : user)
end
# Returns whether this move targets multiple battlers.
def targets_multiple_battlers?
user_battler = @ai.user.battler
target_data = @move.pbTarget(user_battler)
target_data = pbTarget(user_battler)
return false if target_data.num_targets <= 1
num_targets = 0
case target_data.id
@@ -70,8 +70,12 @@ class Battle::AI::AIMove
#=============================================================================
def rough_priority(user)
# TODO: More calculations here.
return @move.priority
ret = @move.pbPriority(user.battler)
if user.ability_active?
ret = Battle::AbilityEffects.triggerPriorityChange(user.ability, user.battler, @move, ret)
user.battler.effects[PBEffects::Prankster] = false # Untrigger this
end
return ret
end
#=============================================================================
@@ -205,7 +209,6 @@ class Battle::AI::AIMove
)
user.effects[PBEffects::GemConsumed] = nil # Untrigger consuming of Gems
end
# TODO: Prefer (1.5x?) if item will be consumed and user has Unburden.
end
if target.item_active? && target.item && !target.item.is_berry?
Battle::ItemEffects.triggerDamageCalcFromTarget(
@@ -456,11 +459,13 @@ class Battle::AI::AIMove
end
# Item effects that alter accuracy calculation
if user.item_active?
# TODO: Zoom Lens needs to be checked differently (compare speeds of
# user and target).
Battle::ItemEffects.triggerAccuracyCalcFromUser(
user.item, modifiers, user_battler, target_battler, @move, calc_type
)
if user.item == :ZOOMLENS
mods[:accuracy_multiplier] *= 1.2 if target.faster_than?(user)
else
Battle::ItemEffects.triggerAccuracyCalcFromUser(
user.item, modifiers, user_battler, target_battler, @move, calc_type
)
end
end
if target.item_active?
Battle::ItemEffects.triggerAccuracyCalcFromTarget(
@@ -553,10 +558,4 @@ class Battle::AI::AIMove
(user.has_active_ability?(:SERENEGRACE) || user.pbOwnSide.effects[PBEffects::Rainbow] > 0)
return 2
end
#=============================================================================
# TODO:
# pbBaseAccuracy(@ai.user.battler, @ai.target.battler) if @ai.trainer.medium_skill?
# pbCriticalOverride(@ai.user.battler, @ai.target.battler)
end

View File

@@ -102,7 +102,7 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("FailsUnlessTargetShares
)
#===============================================================================
# TODO: Split some of this into a MoveEffectScore?
#
#===============================================================================
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("FailsIfUserDamagedThisTurn",
proc { |score, move, user, target, ai, battle|
@@ -475,10 +475,16 @@ Battle::AI::Handlers::MoveEffectScore.add("AddToxicSpikesToFoeSide",
if ai.trainer.medium_skill?
# Check affected by entry hazard
next if pkmn.hasItem?(:HEAVYDUTYBOOTS)
# TODO: Check pkmn's immunity to being poisoned.
# 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

View File

@@ -1,6 +1,5 @@
#===============================================================================
# TODO: This code can be called with a single target and with no targets. Make
# sure it doesn't assume that there is a target.
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("RaiseUserAttack1",
proc { |move, user, ai, battle|
@@ -67,8 +66,7 @@ Battle::AI::Handlers::MoveEffectScore.add("MaxUserAttackLoseHalfOfTotalHP",
)
#===============================================================================
# TODO: This code can be called with a single target and with no targets. Make
# sure it doesn't assume that there is a target.
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.copy("RaiseUserAttack1",
"RaiseUserDefense1")
@@ -92,8 +90,7 @@ Battle::AI::Handlers::MoveEffectScore.add("RaiseUserDefense1CurlUpUser",
)
#===============================================================================
# TODO: This code can be called with multiple targets and with no targets. Make
# sure it doesn't assume that there is a target.
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.copy("RaiseUserDefense1",
"RaiseUserDefense2")
@@ -196,23 +193,17 @@ Battle::AI::Handlers::MoveEffectScore.add("RaiseUserSpeed2LowerUserWeight",
proc { |score, move, user, ai, battle|
score = ai.get_score_for_target_stat_raise(score, user, move.move.statUp)
if ai.trainer.medium_skill?
# TODO: Take into account weight-modifying items/abilities? This "> 1"
# line can probably ignore them, but these moves' powers will change
# because of those modifiers, and the score changes may need to be
# different accordingly.
if user.battler.pokemon.weight - user.effects[PBEffects::WeightChange] > 1
if user.has_move_with_function?("PowerHigherWithUserHeavierThanTarget")
score -= 10
end
current_weight = user.battler.pbWeight
if current_weight > 1
score -= 5 if user.has_move_with_function?("PowerHigherWithUserHeavierThanTarget")
ai.each_foe_battler(user.side) do |b, i|
if b.has_move_with_function?("PowerHigherWithUserHeavierThanTarget")
score -= 10
score -= 5 if b.has_move_with_function?("PowerHigherWithUserHeavierThanTarget")
score += 5 if b.has_move_with_function?("PowerHigherWithTargetWeight")
# User will become susceptible to Sky Drop
if b.has_move_with_function?("TwoTurnAttackInvulnerableInSkyTargetCannotAct") &&
Settings::MECHANICS_GENERATION >= 6
score -= 7 if current_weight >= 2000 && current_weight < 3000
end
if b.has_move_with_function?("PowerHigherWithTargetWeight")
score += 10
end
# TODO: Check foes for Sky Drop and whether the user is too heavy for it
# but the weight reduction will make it susceptible.
end
end
end
@@ -1323,9 +1314,10 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("UserCopyTargetStatStages
)
#===============================================================================
# TODO: Account for stat theft before damage calculation. This would be complex,
# involving pbCanRaiseStatStage? and Contrary and Simple; do I want to
# account for all that or simplify things?
# NOTE: Accounting for the stat theft before damage calculation, to calculate a
# more accurate predicted damage, would be complex, involving
# pbCanRaiseStatStage? and Contrary and Simple; I'm not bothering with
# that.
#===============================================================================
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("UserStealTargetPositiveStatStages",
proc { |score, move, user, target, ai, battle|

View File

@@ -530,9 +530,9 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("CureTargetBurn",
proc { |score, move, user, target, ai, battle|
if target.status == :BURN
if target.opposes?(user)
score -= 40
score -= 10
else
score += 40
score += 10
end
end
next score
@@ -1228,9 +1228,6 @@ Battle::AI::Handlers::MoveFailureCheck.add("StartGravity",
)
Battle::AI::Handlers::MoveEffectScore.add("StartGravity",
proc { |score, move, user, ai, battle|
# TODO: Gravity increases accuracy of all moves. Prefer if user/ally has low
# accuracy moves, don't prefer if foes have them. Should "low
# accuracy" mean anything below 85%?
ai.each_battler do |b, i|
# Prefer grounding airborne foes, don't prefer grounding airborne allies
# Prefer making allies affected by terrain, don't prefer making foes
@@ -1252,6 +1249,11 @@ Battle::AI::Handlers::MoveEffectScore.add("StartGravity",
if b.effects[PBEffects::SkyDrop] >= 0
score += (user.opposes?(b)) ? -5 : 5
end
# Gravity raises accuracy of all moves; prefer if the user/ally has low
# accuracy moves, don't prefer if foes have any
if b.check_for_move { |m| m.accuracy < 85 }
score += (user.opposes?(b)) ? -5 : 5
end
# Prefer stopping foes' sky-based attacks, don't prefer stopping allies'
# sky-based attacks
if user.faster_than?(b) &&

View File

@@ -431,9 +431,20 @@ Battle::AI::Handlers::MoveEffectScore.add("EnsureNextCriticalHit",
next Battle::AI::MOVE_USELESS_SCORE if user.effects[PBEffects::LaserFocus] > 0
# Useless if the user's critical hit stage ensures critical hits already, or
# critical hits are impossible (e.g. via Lucky Chant)
# TODO: Critical hit rate is calculated using a target, but this move
# doesn't have a target. An error is shown when trying it.
crit_stage = move.rough_critical_hit_stage
crit_stage = 0
crit_stage = -1 if user.battler.pbOwnSide.effects[PBEffects::LuckyChant] > 0
if crit_stage >= 0 && user.ability_active? && ![:MERCILESS].include?(user.ability)
crit_stage = Battle::AbilityEffects.triggerCriticalCalcFromUser(user.battler.ability,
user.battler, user.battler, crit_stage)
end
if crit_stage >= 0 && user.item_active?
crit_stage = Battle::ItemEffects.triggerCriticalCalcFromUser(user.battler.item,
user.battler, user.battler, crit_stage)
end
if crit_stage >= 0 && crit_stage < 50
crit_stage += user.effects[PBEffects::FocusEnergy]
crit_stage = [crit_stage, Battle::Move::CRITICAL_HIT_RATIOS.length - 1].min
end
if crit_stage < 0 ||
crit_stage >= Battle::Move::CRITICAL_HIT_RATIOS.length ||
Battle::Move::CRITICAL_HIT_RATIOS[crit_stage] == 1
@@ -482,20 +493,22 @@ Battle::AI::Handlers::MoveEffectScore.add("StartPreventCriticalHitsAgainstUserSi
# make critical hits more likely
ai.each_foe_battler(user.side) do |b, i|
crit_stage = 0
if b.ability_active?
if crit_stage >= 0 && b.ability_active?
crit_stage = Battle::AbilityEffects.triggerCriticalCalcFromUser(b.battler.ability,
b.battler, user.battler, crit_stage)
next if crit_stage < 0
end
if b.item_active?
if crit_stage >= 0 && b.item_active?
crit_stage = Battle::ItemEffects.triggerCriticalCalcFromUser(b.battler.item,
b.battler, user.battler, crit_stage)
next if crit_stage < 0
end
crit_stage += b.effects[PBEffects::FocusEnergy]
crit_stage += 1 if b.check_for_move { |m| m.highCriticalRate? }
crit_stage = [crit_stage, Battle::Move::CRITICAL_HIT_RATIOS.length - 1].min
crit_stage = 3 if crit_stage < 3 && m.check_for_move { |m| m.pbCritialOverride(b.battler, user.battler) > 0 }
if crit_stage >= 0 && crit_stage < 50
crit_stage += b.effects[PBEffects::FocusEnergy]
crit_stage += 1 if b.check_for_move { |m| m.highCriticalRate? }
crit_stage = 99 if m.check_for_move { |m| m.pbCritialOverride(b.battler, user.battler) > 0 }
crit_stage = [crit_stage, Battle::Move::CRITICAL_HIT_RATIOS.length - 1].min
end
# TODO: Change the score depending on how much of an effect a critical hit
# will have? Critical hits ignore the user's offensive stat drops
# and the target's defensive stat raises, and multiply the damage.
@@ -1064,7 +1077,7 @@ Battle::AI::Handlers::MoveEffectScore.add("ProtectUserSideFromPriorityMoves",
next if !b.can_attack?
# TODO: There are more calculations that could be done to get a more
# accurate priority number.
next if !b.check_for_move { |m| m.priority > 0 && m.canProtectAgainst? }
next if !b.check_for_move { |m| m.pbPriority(b.battler) > 0 && m.canProtectAgainst? }
useless = false
# General preference
score += 4

View File

@@ -192,7 +192,6 @@ Battle::AI::Handlers::MoveEffectScore.add("AttackAndSkipNextTurn",
score -= 10 if !user.has_active_ability?(:TRUANT)
# Don't prefer if user is at a high HP (treat this move as a last resort)
score -= 10 if user.hp >= user.totalhp / 2
# TODO: Don't prefer if another of the user's moves could KO the target.
next score
}
)
@@ -215,7 +214,7 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TwoTurnAttack",
# Don't prefer if target has a protecting move
if ai.trainer.high_skill? && !(user.has_active_ability?(:UNSEENFIST) && move.move.contactMove?)
has_protect_move = false
if move.move.pbTarget(user).num_targets > 1 &&
if move.pbTarget(user).num_targets > 1 &&
(Settings::MECHANICS_GENERATION >= 7 || move.damagingMove?)
if target.has_move_with_function?("ProtectUserSideFromMultiTargetDamagingMoves")
has_protect_move = true

View File

@@ -116,7 +116,7 @@ Battle::AI::Handlers::MoveEffectScore.add("HealUserHalfOfTotalHPLoseFlyingTypeTh
)
#===============================================================================
#
# TODO: Review score modifiers.
#===============================================================================
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("CureTargetStatusHealUserHalfOfTotalHP",
proc { |move, user, target, ai, battle|
@@ -585,8 +585,7 @@ Battle::AI::Handlers::MoveEffectScore.copy("UserFaintsHealAndCureReplacement",
"UserFaintsHealAndCureReplacementRestorePP")
#===============================================================================
# TODO: This code should be for a single battler (each is checked in turn).
# Should have a MoveEffectAgainstTargetScore instead.
#
#===============================================================================
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("StartPerishCountsForAllBattlers",
proc { |move, user, target, ai, battle|
@@ -604,11 +603,10 @@ Battle::AI::Handlers::MoveEffectScore.add("StartPerishCountsForAllBattlers",
allies_affected = 0
foes_affected = 0
foes_with_high_hp = 0
battle.allBattlers.each do |b|
next if b.effects[PBEffects::PerishSong] > 0
next if Battle::AbilityEffects.triggerMoveImmunity(b.ability, user.battler, b,
move.move, move.rough_type, battle, false)
if b.opposes?(user.index)
ai.each_battler do |b|
next if Battle::AI::Handlers.move_will_fail_against_target?("StartPerishCountsForAllBattlers",
move, user, b, ai, battle)
if b.opposes?(user)
foes_affected += 1
foes_with_high_hp += 1 if b.hp >= b.totalhp * 0.75
else

View File

@@ -78,7 +78,8 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("HealAllyOrDamageFoe",
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("CurseTargetOrLowerUserSpd1RaiseUserAtkDef1",
proc { |move, user, ai, battle|
next false if user.has_type?(:GHOST)
next false if user.has_type?(:GHOST) ||
(move.rough_type == :GHOST && user.has_active_ability?([:LIBERO, :PROTEAN]))
will_fail = true
(move.move.statUp.length / 2).times do |i|
next if !user.battler.pbCanRaiseStatStage?(move.move.statUp[i * 2], user.battler, move.move)
@@ -95,14 +96,16 @@ Battle::AI::Handlers::MoveFailureCheck.add("CurseTargetOrLowerUserSpd1RaiseUserA
)
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("CurseTargetOrLowerUserSpd1RaiseUserAtkDef1",
proc { |move, user, target, ai, battle|
next false if !user.has_type?(:GHOST)
next false if !user.has_type?(:GHOST) &&
!(move.rough_type == :GHOST && user.has_active_ability?([:LIBERO, :PROTEAN]))
next true if target.effects[PBEffects::Curse] || !target.battler.takesIndirectDamage?
next false
}
)
Battle::AI::Handlers::MoveEffectScore.add("CurseTargetOrLowerUserSpd1RaiseUserAtkDef1",
proc { |score, move, user, ai, battle|
next score if user.has_type?(:GHOST)
next score if user.has_type?(:GHOST) ||
(move.rough_type == :GHOST && user.has_active_ability?([:LIBERO, :PROTEAN]))
score = ai.get_score_for_target_stat_raise(score, user, move.move.statUp)
next score if score == Battle::AI::MOVE_USELESS_SCORE
next ai.get_score_for_target_stat_drop(score, user, move.move.statDown, false)
@@ -110,7 +113,8 @@ Battle::AI::Handlers::MoveEffectScore.add("CurseTargetOrLowerUserSpd1RaiseUserAt
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("CurseTargetOrLowerUserSpd1RaiseUserAtkDef1",
proc { |score, move, user, target, ai, battle|
next score if !user.has_type?(:GHOST)
next score if !user.has_type?(:GHOST) &&
!(move.rough_type == :GHOST && user.has_active_ability?([:LIBERO, :PROTEAN]))
# Don't prefer if user will faint because of using this move
next Battle::AI::MOVE_USELESS_SCORE if user.hp <= user.totalhp / 2
# Prefer early on

View File

@@ -477,8 +477,7 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("LowerPPOfTargetLastMoveB
#===============================================================================
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("LowerPPOfTargetLastMoveBy4",
proc { |move, user, target, ai, battle|
last_move = target.battler.pbGetMoveWithID(target.battler.lastRegularMoveUsed)
next !last_move || last_move.pp == 0 || last_move.total_pp <= 0
next !target.check_for_move { |m| m.id == target.battler.lastRegularMoveUsed }
}
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("LowerPPOfTargetLastMoveBy4",
@@ -500,14 +499,7 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("DisableTargetLastMoveUs
proc { |move, user, target, ai, battle|
next true if target.effects[PBEffects::Disable] > 0 || !target.battler.lastRegularMoveUsed
next true if move.move.pbMoveFailedAromaVeil?(user.battler, target.battler, false)
will_fail = true
target.battler.eachMove do |m|
next if m.id != target.battler.lastRegularMoveUsed
next if m.pp == 0 && m.total_pp > 0
will_fail = false
break
end
next will_fail
next !target.check_for_move { |m| m.id == target.battler.lastRegularMoveUsed }
}
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("DisableTargetUsingSameMoveConsecutively",
@@ -563,13 +555,7 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("DisableTargetUsingDiffe
next true if target.effects[PBEffects::ShellTrap]
next true if move.move.pbMoveFailedAromaVeil?(user.battler, target.battler, false)
will_fail = true
target.battler.eachMove do |m|
next if m.id != target.battler.lastRegularMoveUsed
next if m.pp == 0 && m.total_pp > 0
will_fail = false
break
end
next will_fail
next !target.check_for_move { |m| m.id == target.battler.lastRegularMoveUsed }
}
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("DisableTargetUsingDifferentMove",
@@ -627,7 +613,7 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("DisableTargetStatusMoves
next Battle::AI::MOVE_USELESS_SCORE if !target.check_for_move { |m| m.statusMove? }
# Not worth using on a sleeping target that won't imminently wake up
if target.status == :SLEEP && target.statusCount > ((target.faster_than?(user)) ? 2 : 1)
if !target.check_for_move { |m| m.statusMove? && m.usableWhenAsleep? && (m.pp > 0 || m.total_pp == 0) }
if !target.check_for_move { |m| m.statusMove? && m.usableWhenAsleep? }
next Battle::AI::MOVE_USELESS_SCORE
end
end
@@ -652,8 +638,7 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("DisableTargetStatusMoves
"ProtectUserFromTargetingMovesSpikyShield", # Spiky Shield
"ProtectUserBanefulBunker" # Baneful Bunker
]
if target.check_for_move { |m| m.statusMove? && protection_moves.include?(m.function) &&
(m.pp > 0 || m.total_pp == 0) }
if target.check_for_move { |m| m.statusMove? && protection_moves.include?(m.function) }
score += 6
end
# Inherent preference
@@ -675,7 +660,7 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("DisableTargetHealingMov
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("DisableTargetHealingMoves",
proc { |score, move, user, target, ai, battle|
# Useless if the foe can't heal themselves with a move or some held items
if !target.check_for_move { |m| m.healingMove? && (m.pp > 0 || m.total_pp == 0) }
if !target.check_for_move { |m| m.healingMove? }
if !target.has_active_item?(:LEFTOVERS) &&
!(target.has_active_item?(:BLACKSLUDGE) && target.has_type?(:POISON))
next Battle::AI::MOVE_USELESS_SCORE
@@ -693,7 +678,7 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("DisableTargetHealingMove
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("DisableTargetSoundMoves",
proc { |score, move, user, target, ai, battle|
next score if target.effects[PBEffects::ThroatChop] > 1
next score if !target.check_for_move { |m| m.soundMove? && (m.pp > 0 || m.total_pp == 0) }
next score if !target.check_for_move { |m| m.soundMove? }
# Inherent preference
score += 8
next score
@@ -714,13 +699,9 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("DisableTargetMovesKnownB
shared_move = false
user_moves = user.battler.moves.map { |m| m.id }
ai.each_foe_battler(user.side) do |b, i|
b.battler.eachMove do |m|
next if !user_moves.include?(m.id)
next if m.pp == 0 && m.total_pp > 0
shared_move = true
break
end
break if shared_move
next if !b.check_for_move { |m| user_moves.include?(m.id) }
shared_move = true
break
end
next Battle::AI::MOVE_USELESS_SCORE if !shared_move
# Inherent preference