Generalised AI code for scoring stat changes

This commit is contained in:
Maruno17
2022-12-18 20:51:16 +00:00
parent 7ace4c5289
commit f33184d413
14 changed files with 382 additions and 729 deletions

View File

@@ -155,6 +155,8 @@ end
# Lower multiple of user's stats.
#===============================================================================
class Battle::Move::StatDownMove < Battle::Move
attr_reader :statDown
def pbEffectWhenDealingDamage(user, target)
return if @battle.pbAllFainted?(target.idxOwnSide)
showAnim = true

View File

@@ -907,11 +907,12 @@ class Battle::Move::RaiseTargetAtkSpAtk2 < Battle::Move
end
def pbEffectAgainstTarget(user, target)
showAnim = true
if target.pbCanRaiseStatStage?(:ATTACK, user, self)
target.pbRaiseStatStage(:ATTACK, 2, user)
showAnim = false if target.pbRaiseStatStage(:ATTACK, 2, user, showAnim)
end
if target.pbCanRaiseStatStage?(:SPECIAL_ATTACK, user, self)
target.pbRaiseStatStage(:SPECIAL_ATTACK, 2, user)
target.pbRaiseStatStage(:SPECIAL_ATTACK, 2, user, showAnim)
end
end
end

View File

@@ -65,7 +65,8 @@ class Battle::AI
scoreSum = 0
scoreCount = 0
battler.allOpposing.each do |b|
scoreSum += pbGetMoveScore(battler.moves[idxEncoredMove], [b])
set_up_move_check(battler.moves[idxEncoredMove])
scoreSum += pbGetMoveScore([b])
scoreCount += 1
end
if scoreCount > 0 && scoreSum / scoreCount <= 20

View File

@@ -11,6 +11,11 @@ class Battle::AI
#=============================================================================
# Get scores for the user's moves (done before any action is assessed).
# NOTE: For any move with a target type that can target a foe (or which
# includes a foe(s) if it has multiple targets), the score calculated
# for a target ally will be inverted. The MoveHandlers for those moves
# should therefore treat an ally as a foe when calculating a score
# against it.
#=============================================================================
def pbGetMoveScores
choices = []
@@ -37,7 +42,7 @@ class Battle::AI
when 0 # No targets, affects the user or a side or the whole field
# Includes: BothSides, FoeSide, None, User, UserSide
score = MOVE_BASE_SCORE
PBDebug.logonerr { score = pbGetMoveScore(move) }
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
@@ -46,16 +51,11 @@ class Battle::AI
# Lightning Rod. Skip any battlers that can't be targeted.
@battle.allBattlers.each do |b|
next if !@battle.pbMoveCanTarget?(@user.battler.index, b.index, target_data)
# TODO: This should consider targeting an ally if possible. Scores will
# need to distinguish between harmful and beneficial to target.
# def pbGetMoveScore uses "175 - score" if the target is an ally;
# is this okay?
# Noticeably affects a few moves like Heal Pulse, as well as moves
# that the target can be immune to by an ability (you may want to
# attack the ally anyway so it gains the effect of that ability).
# TODO: Should this sometimes consider targeting an ally? See def
# pbGetMoveScoreAgainstTarget for more information.
next if target_data.targets_foe && !@user.battler.opposes?(b)
score = MOVE_BASE_SCORE
PBDebug.logonerr { score = pbGetMoveScore(move, [b]) }
PBDebug.logonerr { score = pbGetMoveScore([b]) }
add_move_to_choices(choices, idxMove, score, b.index)
end
else # Multiple targets at once
@@ -66,7 +66,7 @@ class Battle::AI
targets.push(b)
end
score = MOVE_BASE_SCORE
PBDebug.logonerr { score = pbGetMoveScore(move, targets) }
PBDebug.logonerr { score = pbGetMoveScore(targets) }
add_move_to_choices(choices, idxMove, score)
end
end
@@ -93,8 +93,7 @@ class Battle::AI
end
def set_up_move_check_target(target)
# TODO: Set @target to nil if there isn't one?
@target = (target) ? @battlers[target.index] : nil # @user
@target = (target) ? @battlers[target.index] : nil
@target&.refresh_battler
end
@@ -104,11 +103,12 @@ class Battle::AI
# TODO: Add skill checks in here for particular calculations?
#=============================================================================
def pbPredictMoveFailure
# TODO: Something involving user.usingMultiTurnAttack? (perhaps earlier than
# this?).
# TODO: Something involving user.battler.usingMultiTurnAttack? (perhaps
# earlier than this?).
# User is asleep and will not wake up
return true if @trainer.medium_skill? && @user.battler.asleep? &&
@user.statusCount > 1 && !@move.move.usableWhenAsleep?
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
return true if @user.has_active_ability?(:TRUANT) && @user.effects[PBEffects::Truant]
# Primal weather
@@ -142,8 +142,8 @@ class Battle::AI
typeMod = @move.move.pbCalcTypeMod(calc_type, @user.battler, @target.battler)
return true if @move.move.pbDamagingMove? && Effectiveness.ineffective?(typeMod)
# Dark-type immunity to moves made faster by Prankster
return true if Settings::MECHANICS_GENERATION >= 7 && @user.has_active_ability?(:PRANKSTER) &&
@target.has_type?(:DARK) && @target.opposes?(@user)
return true if Settings::MECHANICS_GENERATION >= 7 && @move.statusMove? &&
@user.has_active_ability?(:PRANKSTER) && @target.has_type?(:DARK) && @target.opposes?(@user)
# Airborne-based immunity to Ground moves
return true if @move.damagingMove? && calc_type == :GROUND &&
@target.battler.airborne? && !@move.move.hitsFlyingTargets?
@@ -157,8 +157,9 @@ class Battle::AI
#=============================================================================
# Get a score for the given move being used against the given target.
# Assumes def set_up_move_check has previously been called.
#=============================================================================
def pbGetMoveScore(move, targets = nil)
def pbGetMoveScore(targets = nil)
# Get the base score for the move
score = MOVE_BASE_SCORE
# Scores for each target in turn
@@ -170,32 +171,17 @@ class Battle::AI
# Get a score for the move against each target in turn
targets.each do |target|
set_up_move_check_target(target)
# Predict whether the move will fail against the target
if @trainer.has_skill_flag?("PredictMoveFailure")
next if pbPredictMoveFailureAgainstTarget
end
t_score = pbGetMoveScoreAgainstTarget
next if t_score < 0
score += t_score
affected_targets += 1
# Score the move
t_score = MOVE_BASE_SCORE
if @trainer.has_skill_flag?("ScoreMoves")
# Modify the score according to the move's effect against the target
t_score = Battle::AI::Handlers.apply_move_effect_against_target_score(@move.function,
MOVE_BASE_SCORE, @move, @user, @target, self, @battle)
# Modify the score according to various other effects against the target
score = Battle::AI::Handlers.apply_general_move_against_target_score_modifiers(
score, @move, @user, @target, self, @battle)
end
score += (@target.opposes?(@user)) ? t_score : 175 - t_score
end
# Check if any targets were affected
if affected_targets == 0
if @trainer.has_skill_flag?("PredictMoveFailure")
return MOVE_FAIL_SCORE if !@move.move.worksWithNoTargets?
score = MOVE_USELESS_SCORE
else
score = MOVE_BASE_SCORE
end
if affected_targets == 0 && @trainer.has_skill_flag?("PredictMoveFailure")
return MOVE_FAIL_SCORE if !@move.move.worksWithNoTargets?
score = MOVE_USELESS_SCORE
else
affected_targets = 1 if affected_targets == 0 # To avoid dividing by 0
# TODO: Can this accounting for multiple targets be improved somehow?
score /= affected_targets # Average the score against multiple targets
# Bonus for affecting multiple targets
@@ -220,6 +206,46 @@ class Battle::AI
return score
end
#=============================================================================
# Returns the score of @move being used against @target. A return value of -1
# means the move will fail or do nothing against the target.
# 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 "175 - 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")
return -1 if pbPredictMoveFailureAgainstTarget
end
# Score the move
score = MOVE_BASE_SCORE
if @trainer.has_skill_flag?("ScoreMoves")
# Modify the score according to the move's effect against the target
score = Battle::AI::Handlers.apply_move_effect_against_target_score(@move.function,
MOVE_BASE_SCORE, @move, @user, @target, self, @battle)
# Modify the score according to various other effects against the target
score = Battle::AI::Handlers.apply_general_move_against_target_score_modifiers(
score, @move, @user, @target, self, @battle)
end
# Add the score against the target to the overall score
target_data = @move.pbTarget(@user.battler)
if target_data.targets_foe && !@target.opposes?(@user) && @target.index != @user.index
return -1 if score == MOVE_USELESS_SCORE
# TODO: Is this reversal of the score okay?
score = 175 - score
end
return score
end
#=============================================================================
# Make the final choice of which move to use depending on the calculated
# scores for each move. Moves with higher scores are more likely to be chosen.

View File

@@ -1,88 +1,100 @@
class Battle::AI
#=============================================================================
# Main method for calculating the score for moves that raise the user's stat(s).
# Main method for calculating the score for moves that raise a battler's
# stat(s).
# By default, assumes that a stat raise is a good thing. However, this score
# is inverted (by desire_mult) if the target opposes the user. If the move
# 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_user_stat_raise(score)
# Discard status move/don't prefer damaging move if user has Contrary
if !@battle.moldBreaker && @user.has_active_ability?(:CONTRARY)
return (@move.statusMove?) ? MOVE_USELESS_SCORE : score - 20
def get_score_for_target_stat_raise(score, target, stat_changes, whole_effect = true)
whole_effect = false if @move.damagingMove?
# Decide whether the target raising its stat(s) is a good thing
desire_mult = 1
if target.opposes?(@user) ||
(@move.pbTarget(@user.battler).targets_foe && target.index != @user.index)
desire_mult = -1
end
# Don't make score changes if user will faint from EOR damage
if @user.rough_end_of_round_damage > @user.hp
return (@move.statusMove?) ? MOVE_USELESS_SCORE : score
# 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 !@battle.moldBreaker && target.has_active_ability?(:CONTRARY) && desire_mult > 1
return (whole_effect) ? MOVE_USELESS_SCORE : score - 20
end
# Don't make score changes if foes have Unaware and user can't make use of
# Don't make score changes if target will faint from EOR damage
if target.rough_end_of_round_damage > target.hp
return (whole_effect) ? MOVE_USELESS_SCORE : score
end
# Don't make score changes if foes have Unaware and target can't make use of
# extra stat stages
if !@user.check_for_move { |m| m.function == "PowerHigherWithUserPositiveStatStages" }
if !target.check_for_move { |m| m.function == "PowerHigherWithUserPositiveStatStages" }
foe_is_aware = false
each_foe_battler(@user.side) do |b, i|
each_foe_battler(target.side) do |b, i|
foe_is_aware = true if !b.has_active_ability?(:UNAWARE)
end
if !foe_is_aware
return (@move.statusMove?) ? MOVE_USELESS_SCORE : score
return (whole_effect) ? MOVE_USELESS_SCORE : score
end
end
# Figure out which stat raises can happen
stat_changes = []
@move.move.statUp.each_with_index do |stat, idx|
real_stat_changes = []
stat_changes.each_with_index do |stat, idx|
next if idx.odd?
next if !stat_raise_worthwhile?(stat)
next if !stat_raise_worthwhile?(target, stat)
# Calculate amount that stat will be raised by
increment = @move.move.statUp[idx + 1]
increment = stat_changes[idx + 1]
if @move.function == "RaiseUserAtkSpAtk1Or2InSun"
increment = 1
increment = 2 if [:Sun, :HarshSun].include?(@user.battler.effectiveWeather)
increment = 2 if [:Sun, :HarshSun].include?(target.battler.effectiveWeather)
end
increment *= 2 if !@battle.moldBreaker && @user.has_active_ability?(:SIMPLE)
increment = [increment, 6 - @user.stages[stat]].min # The actual stages gained
increment *= 2 if !@battle.moldBreaker && target.has_active_ability?(:SIMPLE)
increment = [increment, 6 - target.stages[stat]].min # The actual stages gained
# Count this as a valid stat raise
stat_changes.push([stat, increment]) if increment > 0
real_stat_changes.push([stat, increment]) if increment > 0
end
# Discard move if it can't raise any stats
if stat_changes.length == 0
# TODO: Have a parameter that decides whether to reduce the score here
# (for moves where this is just part of the effect).
return (@move.statusMove?) ? MOVE_USELESS_SCORE : score
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_user_stat_raise_score_generic(score, stat_changes)
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
stat_changes.each do |change|
score = get_user_stat_raise_score_one(score, change[0], change[1])
real_stat_changes.each do |change|
score = get_target_stat_raise_score_one(score, target, change[0], change[1], desire_mult)
end
return score
end
#=============================================================================
# Returns whether the user raising the given stat will have any impact.
# Returns whether the target raising the given stat will have any impact.
# TODO: Make sure the move's actual damage category is taken into account,
# i.e. CategoryDependsOnHigherDamagePoisonTarget and
# CategoryDependsOnHigherDamageIgnoreTargetAbility.
#=============================================================================
def stat_raise_worthwhile?(stat)
return false if !@user.battler.pbCanRaiseStatStage?(stat, @user.battler, @move.move)
# Check if user won't benefit from the stat being raised
# TODO: Exception if user knows Baton Pass/Stored Power?
def stat_raise_worthwhile?(target, stat)
return false if !target.battler.pbCanRaiseStatStage?(stat, @user.battler, @move.move)
# Check if target won't benefit from the stat being raised
# TODO: Exception if target knows Baton Pass/Stored Power?
case stat
when :ATTACK
return false if !@user.check_for_move { |m| m.physicalMove?(m.type) &&
m.function != "UseUserDefenseInsteadOfUserAttack" &&
m.function != "UseTargetAttackInsteadOfUserAttack" }
return false if !target.check_for_move { |m| m.physicalMove?(m.type) &&
m.function != "UseUserDefenseInsteadOfUserAttack" &&
m.function != "UseTargetAttackInsteadOfUserAttack" }
when :DEFENSE
each_foe_battler(@user.side) do |b, i|
each_foe_battler(target.side) do |b, i|
return true if b.check_for_move { |m| m.physicalMove?(m.type) ||
m.function == "UseTargetDefenseInsteadOfTargetSpDef" }
end
return false
when :SPECIAL_ATTACK
return false if !@user.check_for_move { |m| m.specialMove?(m.type) }
return false if !target.check_for_move { |m| m.specialMove?(m.type) }
when :SPECIAL_DEFENSE
each_foe_battler(@user.side) do |b, i|
each_foe_battler(target.side) do |b, i|
return true if b.check_for_move { |m| m.specialMove?(m.type) &&
m.function != "UseTargetDefenseInsteadOfTargetSpDef" }
end
@@ -92,9 +104,9 @@ class Battle::AI
"PowerHigherWithUserFasterThanTarget",
"PowerHigherWithUserPositiveStatStages"
]
if !@user.check_for_move { |m| moves_that_prefer_high_speed.include?(m.function) }
each_foe_battler(@user.side) do |b, i|
return true if b.faster_than?(@user)
if !target.check_for_move { |m| moves_that_prefer_high_speed.include?(m.function) }
each_foe_battler(target.side) do |b, i|
return true if b.faster_than?(target)
end
return false
end
@@ -107,27 +119,35 @@ class Battle::AI
#=============================================================================
# Make score changes based on the general concept of raising stats at all.
#=============================================================================
def get_user_stat_raise_score_generic(score, stat_changes)
def get_target_stat_raise_score_generic(score, target, stat_changes, desire_mult = 1)
total_increment = stat_changes.sum { |change| change[1] }
# TODO: Just return if foe is predicted to use a phazing move (one that
# switches the user out).
# TODO: Don't prefer if foe is faster than user and is predicted to deal
# TODO: Just return if the target's foe is predicted to use a phazing move
# (one that switches the target out).
# TODO: Don't prefer if foe is faster than target and is predicted to deal
# lethal damage.
# TODO: Don't prefer if foe is slower than user but is predicted to be able
# to 2HKO user.
# TODO: Prefer if foe is semi-invulnerable and user is faster (can't hit
# TODO: Don't prefer if foe is slower than target but is predicted to be
# 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 * 4
score += total_increment * desire_mult * 4
end
# Prefer if user is at high HP, don't prefer if user is at low HP
if @user.hp >= @user.totalhp * 0.7
score += 4 * total_increment
if target.index != @user.index
if @user.hp >= @user.totalhp * 0.7
score += total_increment * desire_mult * 3
else
score += total_increment * desire_mult * ((100 * @user.hp / @user.totalhp) - 50) / 6 # +3 to -8 per stage
end
end
# Prefer if target is at high HP, don't prefer if target is at low HP
if target.hp >= target.totalhp * 0.7
score += total_increment * desire_mult * 4
else
score += total_increment * ((100 * @user.hp / @user.totalhp) - 50) / 4 # +5 to -12 per stage
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.
@@ -225,7 +245,7 @@ class Battle::AI
#=============================================================================
# Make score changes based on the raising of a specific stat.
#=============================================================================
def get_user_stat_raise_score_one(score, stat, increment)
def get_target_stat_raise_score_one(score, target, stat, increment, desire_mult = 1)
# Figure out how much the stat will actually change by
stage_mul = [2, 2, 2, 2, 2, 2, 2, 3, 4, 5, 6, 7, 8]
stage_div = [8, 7, 6, 5, 4, 3, 2, 2, 2, 2, 2, 2, 2]
@@ -233,97 +253,95 @@ class Battle::AI
stage_mul = [3, 3, 3, 3, 3, 3, 3, 4, 5, 6, 7, 8, 9]
stage_div = [9, 8, 7, 6, 5, 4, 3, 3, 3, 3, 3, 3, 3]
end
old_stage = @user.stages[stat]
old_stage = target.stages[stat]
new_stage = old_stage + increment
inc_mult = (stage_mul[new_stage].to_f * stage_div[old_stage]) / (stage_div[new_stage] * stage_mul[old_stage])
inc_mult -= 1
inc_mult *= desire_mult
# Stat-based score changes
case stat
when :ATTACK
# Modify score depending on current stat stage
# More strongly prefer if the user has no special moves
# More strongly prefer if the target has no special moves
if old_stage >= 2
score -= 20
score -= 20 * ((target.opposes?(@user)) ? 1 : desire_mult)
else
has_special_moves = @user.check_for_move { |m| m.specialMove?(m.type) }
has_special_moves = target.check_for_move { |m| m.specialMove?(m.type) }
inc = (has_special_moves) ? 10 : 20
score += inc * inc_mult
score += 8 * inc_mult if @user.hp == @user.totalhp
end
when :DEFENSE
# Modify score depending on current stat stage
if old_stage >= 2
score -= 20
score -= 20 * ((target.opposes?(@user)) ? 1 : desire_mult)
else
score += 10 * inc_mult
score += 8 * inc_mult if @user.hp == @user.totalhp
end
when :SPECIAL_ATTACK
# Modify score depending on current stat stage
# More strongly prefer if the user has no physical moves
# More strongly prefer if the target has no physical moves
if old_stage >= 2
score -= 20
score -= 20 * ((target.opposes?(@user)) ? 1 : desire_mult)
else
has_physical_moves = @user.check_for_move { |m| m.physicalMove?(m.type) &&
m.function != "UseUserDefenseInsteadOfUserAttack" &&
m.function != "UseTargetAttackInsteadOfUserAttack" }
has_physical_moves = target.check_for_move { |m| m.physicalMove?(m.type) &&
m.function != "UseUserDefenseInsteadOfUserAttack" &&
m.function != "UseTargetAttackInsteadOfUserAttack" }
inc = (has_physical_moves) ? 10 : 20
score += inc * inc_mult
score += 8 * inc_mult if @user.hp == @user.totalhp
end
when :SPECIAL_DEFENSE
# Modify score depending on current stat stage
if old_stage >= 2
score -= 20
score -= 20 * ((target.opposes?(@user)) ? 1 : desire_mult)
else
score += 10 * inc_mult
score += 8 * inc_mult if @user.hp == @user.totalhp
end
when :SPEED
# Prefer if user is slower than a foe
# TODO: Don't prefer if the user is too much slower than any foe that it
# Prefer if target is slower than a foe
# TODO: Don't prefer if the target is too much slower than any foe that it
# can't catch up.
each_foe_battler(@user.side) do |b, i|
next if @user.faster_than?(b)
each_foe_battler(target.side) do |b, i|
next if target.faster_than?(b)
score += 15 * inc_mult
break
end
# TODO: Prefer if the user is able to cause flinching (moves that flinch,
# or has King's Rock/Stench).
# Prefer if the user has Electro Ball or Power Trip/Stored Power
# 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",
"PowerHigherWithUserPositiveStatStages"
]
if @user.check_for_move { |m| moves_that_prefer_high_speed.include?(m.function) }
if target.check_for_move { |m| moves_that_prefer_high_speed.include?(m.function) }
score += 8 * inc_mult
end
# Don't prefer if any foe has Gyro Ball
each_foe_battler(@user.side) do |b, i|
each_foe_battler(target.side) do |b, i|
next if !b.check_for_move { |m| m.function == "PowerHigherWithTargetFasterThanUser" }
score -= 8 * inc_mult
end
# Don't prefer if user has Speed Boost (will be gaining Speed anyway)
score -= 20 if @user.has_active_ability?(:SPEEDBOOST)
# Don't prefer if target has Speed Boost (will be gaining Speed anyway)
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
score -= 20
score -= 20 * ((target.opposes?(@user)) ? 1 : desire_mult)
else
min_accuracy = 100
@user.battler.moves.each do |m|
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
min_accuracy = min_accuracy * stage_mul[old_stage] / stage_div[old_stage]
if min_accuracy < 90
score += 10 * inc_mult
score += 8 * inc_mult if @user.hp == @user.totalhp
end
end
@@ -331,29 +349,28 @@ class Battle::AI
# 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 user is rooted via Ingrain.
each_foe_battler(@user.side) do |b, i|
# TODO: Emerald AI also prefers if target is rooted via Ingrain.
each_foe_battler(target.side) do |b, i|
eor_damage = b.rough_end_of_round_damage
score += 60 * eor_damage / b.totalhp if eor_damage > 0
next if eor_damage <= 0
end
# Modify score depending on current stat stage
if old_stage >= 2
score -= 20
score -= 20 * ((target.opposes?(@user)) ? 1 : desire_mult)
else
score += 10 * (2 - old_stage) * inc_mult
score += 8 * inc_mult if @user.hp == @user.totalhp
score += 10 * inc_mult
end
end
# Prefer if user has Stored Power
if @user.check_for_move { |m| m.function == "PowerHigherWithUserPositiveStatStages" }
score += 5 * pos_change
# Prefer if target has Stored Power
if target.check_for_move { |m| m.function == "PowerHigherWithUserPositiveStatStages" }
score += 5 * increment * desire_mult
end
# Don't prefer if any foe has Punishment
each_foe_battler(@user.side) do |b, i|
each_foe_battler(target.side) do |b, i|
next if !b.check_for_move { |m| m.function == "PowerHigherWithTargetPositiveStatStages" }
score -= 5 * pos_change
score -= 5 * increment * desire_mult
end
return score
@@ -363,9 +380,6 @@ class Battle::AI
mini_score = 1.0
case stat
when :ATTACK
# TODO: Don't prefer if target has previously used a move that benefits
# from user's Attack being boosted.
# mini_score *= 0.3 if @target.check_for_move { |m| m.function == "UseTargetAttackInsteadOfUserAttack" } # Foul Play
# 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 &&
@@ -567,55 +581,67 @@ class Battle::AI
end
#=============================================================================
# Main method for calculating the score for moves that lower the target's
# Main method for calculating the score for moves that lower a battler's
# stat(s).
# By default, assumes that a stat drop is a good thing. However, this score
# is inverted (by desire_mult) if the target is the user or an ally. This
# inversion does not happen if the move could target a foe but is targeting an
# ally, but only because it is inverted in def pbGetMoveScoreAgainstTarget
# instead.
# TODO: Revisit this method as parts may need rewriting.
#=============================================================================
def get_score_for_target_stat_drop(score)
def get_score_for_target_stat_drop(score, target, stat_changes, whole_effect = true)
whole_effect = false if @move.damagingMove?
# Decide whether the target raising its stat(s) is a good thing
desire_mult = -1
if target.opposes?(@user) ||
(@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
if !@battle.moldBreaker && @target.has_active_ability?(:CONTRARY)
return (@move.statusMove?) ? MOVE_USELESS_SCORE : score - 20
# TODO: Maybe this should return get_score_for_target_stat_raise if Contrary
# applies and desire_mult < 1.
if !@battle.moldBreaker && target.has_active_ability?(:CONTRARY) && desire_mult > 1
return (whole_effect) ? MOVE_USELESS_SCORE : score - 20
end
# Don't make score changes if target will faint from EOR damage
if @target.rough_end_of_round_damage > @target.hp
return (@move.statusMove?) ? MOVE_USELESS_SCORE : score
if target.rough_end_of_round_damage > target.hp
return (whole_effect) ? MOVE_USELESS_SCORE : score
end
# Don't make score changes if allies have Unaware and can't make use of
# target's lowered stat stages
ally_is_aware = false
each_foe_battler(@target.side) do |b, i|
ally_is_aware = true if !b.has_active_ability?(:UNAWARE)
# Don't make score changes if foes have Unaware and target can't make use of
# its lowered stat stages
foe_is_aware = false
each_foe_battler(target.side) do |b, i|
foe_is_aware = true if !b.has_active_ability?(:UNAWARE)
end
if !ally_is_aware
return (@move.statusMove?) ? MOVE_USELESS_SCORE : score
if !foe_is_aware
return (whole_effect) ? MOVE_USELESS_SCORE : score
end
# Figure out which stat raises can happen
stat_changes = []
@move.move.statDown.each_with_index do |stat, idx|
real_stat_changes = []
stat_changes.each_with_index do |stat, idx|
next if idx.odd?
next if !stat_drop_worthwhile?(stat)
next if !stat_drop_worthwhile?(target, stat)
# Calculate amount that stat will be raised by
decrement = @move.move.statDown[idx + 1]
decrement = stat_changes[idx + 1]
decrement *= 2 if !@battle.moldBreaker && @user.has_active_ability?(:SIMPLE)
decrement = [decrement, 6 + @target.stages[stat]].min # The actual stages lost
decrement = [decrement, 6 + target.stages[stat]].min # The actual stages lost
# Count this as a valid stat drop
stat_changes.push([stat, decrement]) if decrement > 0
real_stat_changes.push([stat, decrement]) if decrement > 0
end
# Discard move if it can't lower any stats
if stat_changes.length == 0
# TODO: Have a parameter that decides whether to reduce the score here
# (for moves where this is just part of the effect).
return (@move.statusMove?) ? MOVE_USELESS_SCORE : score
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, stat_changes)
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
stat_changes.each do |change|
score = get_target_stat_drop_score_one(score, change[0], change[1])
real_stat_changes.each do |change|
score = get_target_stat_drop_score_one(score, target, change[0], change[1], desire_mult)
end
return score
@@ -628,24 +654,24 @@ class Battle::AI
# CategoryDependsOnHigherDamageIgnoreTargetAbility.
# TODO: Revisit this method as parts may need rewriting.
#=============================================================================
def stat_drop_worthwhile?(stat)
return false if !@target.battler.pbCanLowerStatStage?(stat, @user.battler, @move.move)
def stat_drop_worthwhile?(target, stat)
return false if !target.battler.pbCanLowerStatStage?(stat, @user.battler, @move.move)
# Check if target won't benefit from the stat being lowered
case stat
when :ATTACK
return false if !@target.check_for_move { |m| m.physicalMove?(m.type) &&
m.function != "UseUserDefenseInsteadOfUserAttack" &&
m.function != "UseTargetAttackInsteadOfUserAttack" }
return false if !target.check_for_move { |m| m.physicalMove?(m.type) &&
m.function != "UseUserDefenseInsteadOfUserAttack" &&
m.function != "UseTargetAttackInsteadOfUserAttack" }
when :DEFENSE
each_foe_battler(@target.side) do |b, i|
each_foe_battler(target.side) do |b, i|
return true if b.check_for_move { |m| m.physicalMove?(m.type) ||
m.function == "UseTargetDefenseInsteadOfTargetSpDef" }
end
return false
when :SPECIAL_ATTACK
return false if !@target.check_for_move { |m| m.specialMove?(m.type) }
return false if !target.check_for_move { |m| m.specialMove?(m.type) }
when :SPECIAL_DEFENSE
each_foe_battler(@target.side) do |b, i|
each_foe_battler(target.side) do |b, i|
return true if b.check_for_move { |m| m.specialMove?(m.type) &&
m.function != "UseTargetDefenseInsteadOfTargetSpDef" }
end
@@ -655,9 +681,9 @@ class Battle::AI
"PowerHigherWithUserFasterThanTarget",
"PowerHigherWithUserPositiveStatStages"
]
if !@target.check_for_move { |m| moves_that_prefer_high_speed.include?(m.function) }
each_foe_battler(@target.side) do |b, i|
return true if !b.faster_than?(@target)
if !target.check_for_move { |m| moves_that_prefer_high_speed.include?(m.function) }
each_foe_battler(target.side) do |b, i|
return true if !b.faster_than?(target)
end
return false
end
@@ -672,7 +698,7 @@ class Battle::AI
# TODO: Revisit this method as parts may need rewriting.
# TODO: All comments in this method may be inaccurate.
#=============================================================================
def get_target_stat_drop_score_generic(score, stat_changes)
def get_target_stat_drop_score_generic(score, target, stat_changes, desire_mult = 1)
total_decrement = stat_changes.sum { |change| change[1] }
# TODO: Just return if target is predicted to switch out (except via Baton Pass).
# TODO: Don't prefer if target is faster than user and is predicted to deal
@@ -683,20 +709,22 @@ class Battle::AI
# 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 * 4
score += total_decrement * desire_mult * 4
end
# Prefer if user is at high HP, don't prefer if user is at low HP
if @user.hp >= @user.totalhp * 0.7
score += 3 * total_decrement
else
score += total_decrement * ((100 * @user.hp / @user.totalhp) - 50) / 6 # +3 to -8 per stage
if target.index != @user.index
if @user.hp >= @user.totalhp * 0.7
score += total_decrement * desire_mult * 3
else
score += total_decrement * desire_mult * ((100 * @user.hp / @user.totalhp) - 50) / 6 # +3 to -8 per stage
end
end
# Prefer if target is at high HP, don't prefer if target is at low HP
if @target.hp >= @target.totalhp * 0.7
score += 3 * total_decrement
if target.hp >= target.totalhp * 0.7
score += total_decrement * desire_mult * 3
else
score += total_decrement * ((100 * @target.hp / @target.totalhp) - 50) / 6 # +3 to -8 per stage
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.
@@ -708,7 +736,7 @@ class Battle::AI
# Make score changes based on the lowering of a specific stat.
# TODO: Revisit this method as parts may need rewriting.
#=============================================================================
def get_target_stat_drop_score_one(score, stat, decrement)
def get_target_stat_drop_score_one(score, target, stat, decrement, desire_mult = 1)
# Figure out how much the stat will actually change by
stage_mul = [2, 2, 2, 2, 2, 2, 2, 3, 4, 5, 6, 7, 8]
stage_div = [8, 7, 6, 5, 4, 3, 2, 2, 2, 2, 2, 2, 2]
@@ -716,102 +744,99 @@ class Battle::AI
stage_mul = [3, 3, 3, 3, 3, 3, 3, 4, 5, 6, 7, 8, 9]
stage_div = [9, 8, 7, 6, 5, 4, 3, 3, 3, 3, 3, 3, 3]
end
old_stage = @target.stages[stat]
old_stage = target.stages[stat]
new_stage = old_stage - decrement
dec_mult = (stage_mul[old_stage].to_f * stage_div[new_stage]) / (stage_div[old_stage] * stage_mul[new_stage])
dec_mult -= 1
dec_mult *= desire_mult
# Stat-based score changes
case stat
when :ATTACK
# Modify score depending on current stat stage
# More strongly prefer if the target has no special moves
if old_stage <= -2
score -= 20
score -= 20 * ((target.opposes?(@user)) ? 1 : desire_mult)
else
has_special_moves = @target.check_for_move { |m| m.specialMove?(m.type) }
has_special_moves = target.check_for_move { |m| m.specialMove?(m.type) }
dec = (has_special_moves) ? 5 : 10
score += dec * (2 + old_stage) * dec_mult
score += 4 * dec_mult if @user.hp == @user.totalhp
score += dec * dec_mult
end
when :DEFENSE
# Modify score depending on current stat stage
if old_stage <= -2
score -= 20
score -= 20 * ((target.opposes?(@user)) ? 1 : desire_mult)
else
score += 5 * (2 + old_stage) * dec_mult
score += 4 * dec_mult if @user.hp == @user.totalhp
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
if old_stage <= -2
score -= 20
score -= 20 * ((target.opposes?(@user)) ? 1 : desire_mult)
else
has_physical_moves = @target.check_for_move { |m| m.physicalMove?(m.type) &&
m.function != "UseUserDefenseInsteadOfUserAttack" &&
m.function != "UseTargetAttackInsteadOfUserAttack" }
has_physical_moves = target.check_for_move { |m| m.physicalMove?(m.type) &&
m.function != "UseUserDefenseInsteadOfUserAttack" &&
m.function != "UseTargetAttackInsteadOfUserAttack" }
dec = (has_physical_moves) ? 5 : 10
score += dec * (2 + old_stage) * dec_mult
score += 4 * dec_mult if @user.hp == @user.totalhp
score += dec * dec_mult
end
when :SPECIAL_DEFENSE
# Modify score depending on current stat stage
if old_stage <= -2
score -= 20
score -= 20 * ((target.opposes?(@user)) ? 1 : desire_mult)
else
score += 5 * (2 + old_stage) * dec_mult
score += 4 * dec_mult if @user.hp == @user.totalhp
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
# can't be brought slow enough.
each_foe_battler(@target.side) do |b, i|
next if b.faster_than?(@target)
each_foe_battler(target.side) do |b, i|
next if b.faster_than?(target)
score += 15 * dec_mult
break
end
# Prefer if any ally has Electro Ball
each_foe_battler(@target.side) do |b, i|
each_foe_battler(target.side) do |b, i|
next if !b.check_for_move { |m| m.function == "PowerHigherWithUserFasterThanTarget" }
score += 8 * dec_mult
end
# Don't prefer if target has Speed Boost (will be gaining Speed anyway)
score -= 20 if @target.has_active_ability?(:SPEEDBOOST)
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
score -= 20
score -= 20 * ((target.opposes?(@user)) ? 1 : desire_mult)
else
score += 5 * (2 + old_stage) * dec_mult
score += 4 * dec_mult if @user.hp == @user.totalhp
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
score -= 20
score -= 20 * ((target.opposes?(@user)) ? 1 : desire_mult)
else
score += 5 * (2 + old_stage) * dec_mult
score += 4 * dec_mult if @user.hp == @user.totalhp
score += 5 * dec_mult
end
end
# Prefer if target has Stored Power
if @target.check_for_move { |m| m.function == "PowerHigherWithUserPositiveStatStages" }
score += 5 * decrement
if target.check_for_move { |m| m.function == "PowerHigherWithUserPositiveStatStages" }
score += 5 * decrement * desire_mult
end
# Don't prefer if any ally has Punishment
each_foe_battler(@target.side) do |b, i|
# Don't prefer if any foe has Punishment
each_foe_battler(target.side) do |b, i|
next if !b.check_for_move { |m| m.function == "PowerHigherWithTargetPositiveStatStages" }
score -= 5 * decrement
score -= 5 * decrement * desire_mult
end
return score

View File

@@ -1,305 +0,0 @@
class Battle::AI
#=============================================================================
# Calculate how much damage a move is likely to do to a given target (as a
# percentage of the target's current HP)
# TODO: How much is this going to be used? Should the predicted percentage of
# damage be used as the initial score for damaging moves?
#=============================================================================
def pbGetDamagingMoveBaseScore
return 100
=begin
# Don't prefer moves that are ineffective because of abilities or effects
return 0 if @target.immune_to_move?
user_battler = @user.battler
target_battler = @target.battler
# Calculate how much damage the move will do (roughly)
calc_damage = @move.rough_damage
# TODO: Maybe move this check elsewhere? Note that Reborn's base score does
# not include this halving, but the predicted damage does.
# Two-turn attacks waste 2 turns to deal one lot of damage
calc_damage /= 2 if @move.move.chargingTurnMove?
# TODO: Maybe move this check elsewhere?
# Increased critical hit rate
if @trainer.medium_skill?
crit_stage = @move.rough_critical_hit_stage
if crit_stage >= 0
crit_fraction = (crit_stage > 50) ? 1 : Battle::Move::CRITICAL_HIT_RATIOS[crit_stage]
crit_mult = (Settings::NEW_CRITICAL_HIT_RATE_MECHANICS) ? 0.5 : 1
calc_damage *= (1 + crit_mult / crit_fraction)
end
end
# Convert damage to percentage of target's remaining HP
damage_percentage = calc_damage * 100.0 / target_battler.hp
# Don't prefer weak attacks
# damage_percentage /= 2 if damage_percentage < 20
# Prefer damaging attack if level difference is significantly high
# damage_percentage *= 1.2 if user_battler.level - 10 > target_battler.level
# Adjust score
damage_percentage = 110 if damage_percentage > 110 # Treat all lethal moves the same
damage_percentage += 40 if damage_percentage > 100 # Prefer moves likely to be lethal
return damage_percentage.to_i
=end
end
#=============================================================================
# TODO: Remove this method. If we're keeping any score changes inherent to a
# move's effect, they will go in MoveEffectScore handlers instead.
#=============================================================================
def pbGetStatusMoveBaseScore
return 100
=begin
# TODO: Call @target.immune_to_move? here too, not just for damaging moves
# (only if this status move will be affected).
# TODO: Make sure all status moves are accounted for.
# TODO: Duplicates in Reborn's AI:
# "SleepTarget" Grass Whistle (15), Hypnosis (15), Sing (15),
# Lovely Kiss (20), Sleep Powder (20), Spore (60)
# "PoisonTarget" - Poison Powder (15), Poison Gas (20)
# "ParalyzeTarget" - Stun Spore (25), Glare (30)
# "ConfuseTarget" - Teeter Dance (5), Supersonic (10),
# Sweet Kiss (20), Confuse Ray (25)
# "RaiseUserAttack1" - Howl (10), Sharpen (10), Medicate (15)
# "RaiseUserSpeed2" - Agility (15), Rock Polish (25)
# "LowerTargetAttack1" - Growl (10), Baby-Doll Eyes (15)
# "LowerTargetAccuracy1" - Sand Attack (5), Flash (10), Kinesis (10), Smokescreen (10)
# "LowerTargetAttack2" - Charm (10), Feather Dance (15)
# "LowerTargetSpeed2" - String Shot (10), Cotton Spore (15), Scary Face (15)
# "LowerTargetSpDef2" - Metal Sound (10), Fake Tears (15)
case @move.move.function
when "ConfuseTarget",
"LowerTargetAccuracy1",
"LowerTargetEvasion1RemoveSideEffects",
"UserTargetSwapAtkSpAtkStages",
"UserTargetSwapDefSpDefStages",
"UserSwapBaseAtkDef",
"UserTargetAverageBaseAtkSpAtk",
"UserTargetAverageBaseDefSpDef",
"SetUserTypesToUserMoveType",
"SetTargetTypesToWater",
"SetUserTypesToTargetTypes",
"SetTargetAbilityToUserAbility",
"UserTargetSwapAbilities",
"PowerUpAllyMove",
"StartWeakenElectricMoves",
"StartWeakenFireMoves",
"EnsureNextMoveAlwaysHits",
"StartNegateTargetEvasionStatStageAndGhostImmunity",
"StartNegateTargetEvasionStatStageAndDarkImmunity",
"ProtectUserSideFromPriorityMoves",
"ProtectUserSideFromMultiTargetDamagingMoves",
"BounceBackProblemCausingStatusMoves",
"StealAndUseBeneficialStatusMove",
"DisableTargetMovesKnownByUser",
"DisableTargetHealingMoves",
"SetAttackerMovePPTo0IfUserFaints",
"UserEnduresFaintingThisTurn",
"RestoreUserConsumedItem",
"StartNegateHeldItems",
"StartDamageTargetEachTurnIfTargetAsleep",
"HealUserDependingOnUserStockpile",
"StartGravity",
"StartUserAirborne",
"UserSwapsPositionsWithAlly",
"StartSwapAllBattlersBaseDefensiveStats",
"RaiseTargetSpDef1",
"RaiseGroundedGrassBattlersAtkSpAtk1",
"RaiseGrassBattlersDef1",
"AddGrassTypeToTarget",
"TrapAllBattlersInBattleForOneTurn",
"EnsureNextCriticalHit",
"UserTargetSwapBaseSpeed",
"RedirectAllMovesToTarget",
"TargetUsesItsLastUsedMoveAgain"
return 55
when "RaiseUserAttack1",
"RaiseUserDefense1",
"RaiseUserDefense1CurlUpUser",
"RaiseUserCriticalHitRate2",
"RaiseUserAtkSpAtk1",
"RaiseUserAtkSpAtk1Or2InSun",
"RaiseUserAtkAcc1",
"RaiseTargetRandomStat2",
"LowerTargetAttack1",
"LowerTargetDefense1",
"LowerTargetAccuracy1",
"LowerTargetAttack2",
"LowerTargetSpeed2",
"LowerTargetSpDef2",
"ResetAllBattlersStatStages",
"UserCopyTargetStatStages",
"SetUserTypesBasedOnEnvironment",
"DisableTargetUsingSameMoveConsecutively",
"StartTargetCannotUseItem",
"LowerTargetAttack1BypassSubstitute",
"LowerTargetAtkSpAtk1",
"LowerTargetSpAtk1",
"TargetNextFireMoveDamagesTarget"
return 60
when "SleepTarget",
"SleepTargetIfUserDarkrai",
"SleepTargetChangeUserMeloettaForm",
"PoisonTarget",
"CureUserBurnPoisonParalysis",
"RaiseUserAttack1",
"RaiseUserSpDef1PowerUpElectricMove",
"RaiseUserEvasion1",
"RaiseUserSpeed2",
"LowerTargetAttack1",
"LowerTargetAtkDef1",
"LowerTargetAttack2",
"LowerTargetDefense2",
"LowerTargetSpeed2",
"LowerTargetSpAtk2IfCanAttract",
"LowerTargetSpDef2",
"ReplaceMoveThisBattleWithTargetLastMoveUsed",
"ReplaceMoveWithTargetLastMoveUsed",
"SetUserAbilityToTargetAbility",
"UseMoveTargetIsAboutToUse",
"UseRandomMoveFromUserParty",
"StartHealUserEachTurnTrapUserInBattle",
"HealTargetHalfOfTotalHP",
"UserFaintsHealAndCureReplacement",
"UserFaintsHealAndCureReplacementRestorePP",
"StartSunWeather",
"StartRainWeather",
"StartSandstormWeather",
"StartHailWeather",
"RaisePlusMinusUserAndAlliesDefSpDef1",
"LowerTargetSpAtk2",
"LowerPoisonedTargetAtkSpAtkSpd1",
"AddGhostTypeToTarget",
"LowerTargetAtkSpAtk1SwitchOutUser",
"RaisePlusMinusUserAndAlliesAtkSpAtk1",
"HealTargetDependingOnGrassyTerrain"
return 65
when "SleepTarget",
"SleepTargetChangeUserMeloettaForm",
"SleepTargetNextTurn",
"PoisonTarget",
"ConfuseTarget",
"RaiseTargetSpAtk1ConfuseTarget",
"RaiseTargetAttack2ConfuseTarget",
"UserTargetSwapStatStages",
"StartUserSideImmunityToStatStageLowering",
"SetUserTypesToResistLastAttack",
"SetTargetAbilityToSimple",
"SetTargetAbilityToInsomnia",
"NegateTargetAbility",
"TransformUserIntoTarget",
"UseLastMoveUsedByTarget",
"UseLastMoveUsed",
"UseRandomMove",
"HealUserFullyAndFallAsleep",
"StartHealUserEachTurn",
"StartPerishCountsForAllBattlers",
"SwitchOutTargetStatusMove",
"TrapTargetInBattle",
"TargetMovesBecomeElectric",
"NormalMovesBecomeElectric",
"PoisonTargetLowerTargetSpeed1"
return 70
when "BadPoisonTarget",
"ParalyzeTarget",
"BurnTarget",
"ConfuseTarget",
"AttractTarget",
"GiveUserStatusToTarget",
"RaiseUserDefSpDef1",
"RaiseUserDefense2",
"RaiseUserSpeed2",
"RaiseUserSpeed2LowerUserWeight",
"RaiseUserSpDef2",
"RaiseUserEvasion2MinimizeUser",
"RaiseUserDefense3",
"MaxUserAttackLoseHalfOfTotalHP",
"UserTargetAverageHP",
"ProtectUser",
"DisableTargetLastMoveUsed",
"DisableTargetStatusMoves",
"HealUserHalfOfTotalHP",
"HealUserHalfOfTotalHPLoseFlyingTypeThisTurn",
"HealUserPositionNextTurn",
"HealUserDependingOnWeather",
"StartLeechSeedTarget",
"AttackerFaintsIfUserFaints",
"UserTargetSwapItems",
"UserMakeSubstitute",
"UserAddStockpileRaiseDefSpDef1",
"RedirectAllMovesToUser",
"InvertTargetStatStages",
"HealUserByTargetAttackLowerTargetAttack1",
"HealUserDependingOnSandstorm"
return 75
when "ParalyzeTarget",
"ParalyzeTargetIfNotTypeImmune",
"RaiseUserAtkDef1",
"RaiseUserAtkDefAcc1",
"RaiseUserSpAtkSpDef1",
"UseMoveDependingOnEnvironment",
"UseRandomUserMoveIfAsleep",
"DisableTargetUsingDifferentMove",
"SwitchOutUserPassOnEffects",
"AddSpikesToFoeSide",
"AddToxicSpikesToFoeSide",
"AddStealthRocksToFoeSide",
"CurseTargetOrLowerUserSpd1RaiseUserAtkDef1",
"StartSlowerBattlersActFirst",
"ProtectUserFromTargetingMovesSpikyShield",
"StartElectricTerrain",
"StartGrassyTerrain",
"StartMistyTerrain",
"StartPsychicTerrain",
"CureTargetStatusHealUserHalfOfTotalHP"
return 80
when "CureUserPartyStatus",
"RaiseUserAttack2",
"RaiseUserSpAtk2",
"RaiseUserSpAtk3",
"StartUserSideDoubleSpeed",
"StartWeakenPhysicalDamageAgainstUserSide",
"StartWeakenSpecialDamageAgainstUserSide",
"ProtectUserSideFromDamagingMovesIfUserFirstTurn",
"ProtectUserFromDamagingMovesKingsShield",
"ProtectUserBanefulBunker"
return 85
when "RaiseUserAtkSpd1",
"RaiseUserSpAtkSpDefSpd1",
"LowerUserDefSpDef1RaiseUserAtkSpAtkSpd2",
"RaiseUserAtk1Spd2",
"TwoTurnAttackRaiseUserSpAtkSpDefSpd2"
return 90
when "SleepTarget",
"SleepTargetChangeUserMeloettaForm",
"AddStickyWebToFoeSide",
"StartWeakenDamageAgainstUserSideIfHail"
return 100
end
# "DoesNothingUnusableInGravity",
# "StartUserSideImmunityToInflictedStatus",
# "LowerTargetEvasion1",
# "LowerTargetEvasion2",
# "StartPreventCriticalHitsAgainstUserSide",
# "UserFaintsLowerTargetAtkSpAtk2",
# "FleeFromBattle",
# "SwitchOutUserStatusMove"
# "TargetTakesUserItem",
# "LowerPPOfTargetLastMoveBy4",
# "StartTargetAirborneAndAlwaysHitByMoves",
# "TargetActsNext",
# "TargetActsLast",
# "ProtectUserSideFromStatusMoves"
return 100
=end
end
end

View File

@@ -10,7 +10,7 @@ Battle::AI::Handlers::MoveFailureCheck.add("RaiseUserAttack1",
)
Battle::AI::Handlers::MoveEffectScore.add("RaiseUserAttack1",
proc { |score, move, user, ai, battle|
next ai.get_score_for_user_stat_raise(score)
next ai.get_score_for_target_stat_raise(score, user, move.move.statUp)
}
)
@@ -28,7 +28,7 @@ Battle::AI::Handlers::MoveEffectScore.copy("RaiseUserAttack1",
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("RaiseUserAttack2IfTargetFaints",
proc { |score, move, user, target, ai, battle|
if move.rough_damage >= target.hp * 0.9
next ai.get_score_for_user_stat_raise(score)
next ai.get_score_for_target_stat_raise(score, user, move.move.statUp)
end
}
)
@@ -58,7 +58,7 @@ Battle::AI::Handlers::MoveFailureCheck.add("MaxUserAttackLoseHalfOfTotalHP",
)
Battle::AI::Handlers::MoveEffectScore.add("MaxUserAttackLoseHalfOfTotalHP",
proc { |score, move, user, ai, battle|
score = ai.get_score_for_user_stat_raise(score)
score = ai.get_score_for_target_stat_raise(score, user, move.move.statUp)
# Don't prefer the lower the user's HP is
score -= 80 * (1 - (user.hp.to_f / user.totalhp)) # 0 to -40
next score
@@ -81,7 +81,7 @@ Battle::AI::Handlers::MoveFailureCheck.copy("RaiseUserDefense1",
"RaiseUserDefense1CurlUpUser")
Battle::AI::Handlers::MoveEffectScore.add("RaiseUserDefense1CurlUpUser",
proc { |score, move, user, ai, battle|
score = ai.get_score_for_user_stat_raise(score)
score = ai.get_score_for_target_stat_raise(score, user, move.move.statUp)
if !user.effects[PBEffects::DefenseCurl] &&
user.check_for_move { |m| m.function == "MultiTurnAttackPowersUpEachTurn" }
score += 10
@@ -146,7 +146,7 @@ Battle::AI::Handlers::MoveFailureCheck.copy("RaiseUserSpDef1",
"RaiseUserSpDef1PowerUpElectricMove")
Battle::AI::Handlers::MoveEffectScore.add("RaiseUserSpDef1PowerUpElectricMove",
proc { |score, move, user, ai, battle|
score = ai.get_score_for_user_stat_raise(score)
score = ai.get_score_for_target_stat_raise(score, user, move.move.statUp)
if user.check_for_move { |m| m.damagingMove? && m.type == :ELECTRIC }
score += 10
end
@@ -193,7 +193,7 @@ Battle::AI::Handlers::MoveFailureCheck.copy("RaiseUserSpeed2",
"RaiseUserSpeed2LowerUserWeight")
Battle::AI::Handlers::MoveEffectScore.add("RaiseUserSpeed2LowerUserWeight",
proc { |score, move, user, ai, battle|
score = ai.get_score_for_user_stat_raise(score)
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
@@ -274,7 +274,7 @@ Battle::AI::Handlers::MoveFailureCheck.copy("RaiseUserEvasion2",
"RaiseUserEvasion2MinimizeUser")
Battle::AI::Handlers::MoveEffectScore.add("RaiseUserEvasion2MinimizeUser",
proc { |score, move, user, ai, battle|
score = ai.get_score_for_user_stat_raise(score)
score = ai.get_score_for_target_stat_raise(score, user, move.move.statUp)
if ai.trainer.medium_skill? && !user.effects[PBEffects::Minimize]
ai.each_foe_battler(user.side) do |b, i|
# Moves that do double damage and (in Gen 6+) have perfect accuracy
@@ -368,7 +368,7 @@ Battle::AI::Handlers::MoveEffectScore.copy("RaiseUserAtkSpAtk1",
"RaiseUserAtkSpAtk1Or2InSun")
#===============================================================================
# TODO: Review score modifiers.
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("LowerUserDefSpDef1RaiseUserAtkSpAtkSpd2",
proc { |move, user, ai, battle|
@@ -388,21 +388,9 @@ Battle::AI::Handlers::MoveFailureCheck.add("LowerUserDefSpDef1RaiseUserAtkSpAtkS
)
Battle::AI::Handlers::MoveEffectScore.add("LowerUserDefSpDef1RaiseUserAtkSpAtkSpd2",
proc { |score, move, user, ai, battle|
score -= user.stages[:ATTACK] * 20
score -= user.stages[:SPEED] * 20
score -= user.stages[:SPECIAL_ATTACK] * 20
score += user.stages[:DEFENSE] * 10
score += user.stages[:SPECIAL_DEFENSE] * 10
if ai.trainer.medium_skill?
hasDamagingAttack = false
user.battler.eachMove do |m|
next if !m.damagingMove?
hasDamagingAttack = true
break
end
score += 20 if hasDamagingAttack
end
next score
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)
}
)
@@ -463,7 +451,7 @@ Battle::AI::Handlers::MoveEffectScore.copy("RaiseUserAtkSpAtk1",
"RaiseUserMainStats1")
#===============================================================================
# TODO: Review score modifiers.
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("RaiseUserMainStats1LoseThirdOfTotalHP",
proc { |move, user, ai, battle|
@@ -479,12 +467,12 @@ Battle::AI::Handlers::MoveFailureCheck.add("RaiseUserMainStats1LoseThirdOfTotalH
)
Battle::AI::Handlers::MoveEffectScore.add("RaiseUserMainStats1LoseThirdOfTotalHP",
proc { |score, move, user, ai, battle|
next 0 if !battle.moldBreaker && user.has_active_ability?(:CONTRARY)
score += 30 if ai.trainer.high_skill? && user.hp >= user.totalhp * 0.75
GameData::Stat.each_main_battle { |s| score += 10 if user.stages[s.id] <= 0 }
if ai.trainer.medium_skill?
hasDamagingAttack = user.battler.moves.any? { |m| next m&.damagingMove? }
score += 20 if hasDamagingAttack
# Score for stat increase
score = ai.get_score_for_target_stat_raise(score, user, move.move.statUp)
next score if score == Battle::AI::MOVE_USELESS_SCORE
# Score for losing HP
if user.hp <= user.totalhp * 0.75
score -= 30 * (user.totalhp - user.hp) / user.totalhp
end
next score
}
@@ -507,15 +495,11 @@ Battle::AI::Handlers::MoveFailureCheck.add("RaiseUserMainStats1TrapUserInBattle"
)
Battle::AI::Handlers::MoveEffectScore.add("RaiseUserMainStats1TrapUserInBattle",
proc { |score, move, user, ai, battle|
next 0 if !battle.moldBreaker && user.has_active_ability?(:CONTRARY)
if ai.trainer.high_skill?
score -= 50 if user.hp <= user.totalhp / 2
score += 30 if user.battler.trappedInBattle?
end
GameData::Stat.each_main_battle { |s| score += 10 if user.stages[s.id] <= 0 }
if ai.trainer.medium_skill?
hasDamagingAttack = user.battler.moves.any? { |m| next m&.damagingMove? }
score += 20 if hasDamagingAttack
# Score for stat increase
score = ai.get_score_for_target_stat_raise(score, user, move.move.statUp)
# Score for trapping user in battle
if ai.trainer.medium_skill? && !user.battler.trappedInBattle?
score -= 10 if user.hp <= user.totalhp / 2
end
next score
}
@@ -531,113 +515,85 @@ Battle::AI::Handlers::MoveEffectScore.add("StartRaiseUserAtk1WhenDamaged",
)
#===============================================================================
# TODO: Review score modifiers.
#
#===============================================================================
Battle::AI::Handlers::MoveEffectScore.add("LowerUserAttack1",
proc { |score, move, user, ai, battle|
next score + user.stages[:ATTACK] * 10
next ai.get_score_for_target_stat_drop(score, user, move.move.statDown)
}
)
#===============================================================================
# TODO: Review score modifiers.
#
#===============================================================================
Battle::AI::Handlers::MoveEffectScore.copy("LowerUserAttack1",
"LowerUserAttack2")
#===============================================================================
# TODO: Review score modifiers.
#
#===============================================================================
Battle::AI::Handlers::MoveEffectScore.add("LowerUserDefense1",
proc { |score, move, user, ai, battle|
next score + user.stages[:DEFENSE] * 10
}
)
Battle::AI::Handlers::MoveEffectScore.copy("LowerUserAttack1",
"LowerUserDefense1")
#===============================================================================
# TODO: Review score modifiers.
#
#===============================================================================
Battle::AI::Handlers::MoveEffectScore.copy("LowerUserDefense1",
"LowerUserDefense2")
#===============================================================================
# TODO: Review score modifiers.
#
#===============================================================================
Battle::AI::Handlers::MoveEffectScore.add("LowerUserSpAtk1",
proc { |score, move, user, ai, battle|
next score + user.stages[:SPECIAL_ATTACK] * 10
}
)
Battle::AI::Handlers::MoveEffectScore.copy("LowerUserAttack1",
"LowerUserSpAtk1")
#===============================================================================
# TODO: Review score modifiers.
#
#===============================================================================
Battle::AI::Handlers::MoveEffectScore.copy("LowerUserSpAtk1",
"LowerUserSpAtk2")
#===============================================================================
# TODO: Review score modifiers.
#
#===============================================================================
Battle::AI::Handlers::MoveEffectScore.add("LowerUserSpDef1",
proc { |score, move, user, ai, battle|
next score + user.stages[:SPECIAL_DEFENSE] * 10
}
)
Battle::AI::Handlers::MoveEffectScore.copy("LowerUserDefense1",
"LowerUserSpDef1")
#===============================================================================
# TODO: Review score modifiers.
#
#===============================================================================
Battle::AI::Handlers::MoveEffectScore.copy("LowerUserSpDef1",
"LowerUserSpDef2")
#===============================================================================
# TODO: Review score modifiers.
#
#===============================================================================
Battle::AI::Handlers::MoveEffectScore.add("LowerUserSpeed1",
proc { |score, move, user, ai, battle|
next score + user.stages[:SPECIAL_DEFENSE] * 10
}
)
Battle::AI::Handlers::MoveEffectScore.copy("LowerUserAttack1",
"LowerUserSpeed1")
#===============================================================================
# TODO: Review score modifiers.
#
#===============================================================================
Battle::AI::Handlers::MoveEffectScore.copy("LowerUserSpeed1",
"LowerUserSpeed2")
#===============================================================================
# TODO: Review score modifiers.
#
#===============================================================================
Battle::AI::Handlers::MoveEffectScore.add("LowerUserAtkDef1",
proc { |score, move, user, ai, battle|
avg = user.stages[:ATTACK] * 10
avg += user.stages[:DEFENSE] * 10
next score + avg / 2
}
)
Battle::AI::Handlers::MoveEffectScore.copy("LowerUserAttack1",
"LowerUserAtkDef1")
#===============================================================================
# TODO: Review score modifiers.
#
#===============================================================================
Battle::AI::Handlers::MoveEffectScore.add("LowerUserDefSpDef1",
proc { |score, move, user, ai, battle|
avg = user.stages[:DEFENSE] * 10
avg += user.stages[:SPECIAL_DEFENSE] * 10
next score + avg / 2
}
)
Battle::AI::Handlers::MoveEffectScore.copy("LowerUserAttack1",
"LowerUserDefSpDef1")
#===============================================================================
# TODO: Review score modifiers.
#
#===============================================================================
Battle::AI::Handlers::MoveEffectScore.add("LowerUserDefSpDefSpd1",
proc { |score, move, user, ai, battle|
avg = user.stages[:DEFENSE] * 10
avg += user.stages[:SPEED] * 10
avg += user.stages[:SPECIAL_DEFENSE] * 10
next score + (avg / 3).floor
}
)
Battle::AI::Handlers::MoveEffectScore.copy("LowerUserAttack1",
"LowerUserDefSpDefSpd1")
#===============================================================================
# TODO: Review score modifiers.
@@ -648,6 +604,11 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("RaiseTargetAttack1",
!target.battler.pbCanRaiseStatStage?(:ATTACK, user.battler, move.move)
}
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("RaiseTargetAttack1",
proc { |score, move, user, target, ai, battle|
next ai.get_score_for_target_stat_raise(score, target, [:ATTACK, 1])
}
)
#===============================================================================
# TODO: Review score modifiers.
@@ -660,8 +621,12 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("RaiseTargetAttack2Confu
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("RaiseTargetAttack2ConfuseTarget",
proc { |score, move, user, target, ai, battle|
next Battle::AI::MOVE_USELESS_SCORE if !target.battler.pbCanConfuse?(user.battler, false)
next score + 30 if target.stages[:ATTACK] < 0
next Battle::AI::MOVE_USELESS_SCORE if !target.battler.pbCanConfuse?(user.battler, false, move.move)
# Score for stat raise
stat_score = ai.get_score_for_target_stat_raise(score, target, [:ATTACK, 2], false)
# Score for confusing the target
next Battle::AI::Handlers.apply_move_effect_against_target_score(
"ConfuseTarget", score, move, user, target, ai, battle)
}
)
@@ -676,13 +641,17 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("RaiseTargetSpAtk1Confus
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("RaiseTargetSpAtk1ConfuseTarget",
proc { |score, move, user, target, ai, battle|
next Battle::AI::MOVE_USELESS_SCORE if !target.battler.pbCanConfuse?(user.battler, false)
next score + 30 if target.stages[:SPECIAL_ATTACK] < 0
next Battle::AI::MOVE_USELESS_SCORE if !target.battler.pbCanConfuse?(user.battler, false, move.move)
# Score for stat raise
stat_score = ai.get_score_for_target_stat_raise(score, target, [:SPECIAL_ATTACK, 1], false)
# Score for confusing the target
next Battle::AI::Handlers.apply_move_effect_against_target_score(
"ConfuseTarget", score, move, user, target, ai, battle)
}
)
#===============================================================================
# TODO: Review score modifiers.
#
#===============================================================================
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("RaiseTargetSpDef1",
proc { |move, user, target, ai, battle|
@@ -691,7 +660,7 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("RaiseTargetSpDef1",
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("RaiseTargetSpDef1",
proc { |score, move, user, target, ai, battle|
next score - target.stages[:SPECIAL_DEFENSE] * 10
next ai.get_score_for_target_stat_raise(score, target, [:SPECIAL_DEFENSE, 1])
}
)
@@ -720,7 +689,7 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("RaiseTargetRandomStat2",
)
#===============================================================================
# TODO: Review score modifiers.
#
#===============================================================================
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("RaiseTargetAtkSpAtk2",
proc { |move, user, target, ai, battle|
@@ -731,10 +700,7 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("RaiseTargetAtkSpAtk2",
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("RaiseTargetAtkSpAtk2",
proc { |score, move, user, target, ai, battle|
next Battle::AI::MOVE_USELESS_SCORE if target.opposes?(user)
next Battle::AI::MOVE_USELESS_SCORE if !battle.moldBreaker && target.has_active_ability?(:CONTRARY)
score -= target.stages[:ATTACK] * 10
score -= target.stages[:SPECIAL_ATTACK] * 10
next score
next ai.get_score_for_target_stat_raise(score, target, [:ATTACK, 2, :SPECIAL_ATTACK, 2])
}
)
@@ -749,7 +715,7 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("LowerTargetAttack1",
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("LowerTargetAttack1",
proc { |score, move, user, target, ai, battle|
next ai.get_score_for_target_stat_drop(score)
next ai.get_score_for_target_stat_drop(score, target, move.move.statDown)
}
)
@@ -911,7 +877,7 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("LowerTargetSpeed1MakeTa
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("LowerTargetSpeed1MakeTargetWeakerToFire",
proc { |score, move, user, target, ai, battle|
# Score for stat drop
score = ai.get_score_for_target_stat_drop(score)
score = ai.get_score_for_target_stat_drop(score, target, move.move.statDown)
# Score for adding weakness to Fire
if !target.effects[PBEffects::TarShot]
score += 20 if user.battler.moves.any? { |m| m.damagingMove? && m.pbCalcType(user.battler) == :FIRE }
@@ -997,7 +963,7 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("LowerTargetEvasion1Remo
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("LowerTargetEvasion1RemoveSideEffects",
proc { |score, move, user, target, ai, battle|
# Score for stat drop
score = ai.get_score_for_target_stat_drop(score)
score = ai.get_score_for_target_stat_drop(score, target, move.move.statDown)
# Score for removing side effects/terrain
score += 30 if target.pbOwnSide.effects[PBEffects::AuroraVeil] > 0 ||
target.pbOwnSide.effects[PBEffects::Reflect] > 0 ||
@@ -1089,13 +1055,7 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("RaiseAlliesAtkDef1",
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("RaiseAlliesAtkDef1",
proc { |score, move, user, target, ai, battle|
user.battler.allAllies.each do |b|
if !battle.moldBreaker && b.hasActiveAbility?(:CONTRARY)
score -= 40
else
score += 10
score -= b.stages[:ATTACK] * 10
score -= b.stages[:SPECIAL_ATTACK] * 10
end
score = ai.get_score_for_target_stat_raise(score, b, [:ATTACK, 1, :DEFENSE, 1])
end
next score
}
@@ -1125,13 +1085,10 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("RaisePlusMinusUserAndAl
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("RaisePlusMinusUserAndAlliesAtkSpAtk1",
proc { |score, move, user, target, ai, battle|
# score = ai.get_score_for_target_stat_raise(score, user, [:ATTACK, 1, :SPECIAL_ATTACK, 1], false)
user.battler.allAllies.each do |b|
next if b.statStageAtMax?(:ATTACK) && b.statStageAtMax?(:SPECIAL_ATTACK)
score -= b.stages[:ATTACK] * 10
score -= b.stages[:SPECIAL_ATTACK] * 10
score = ai.get_score_for_target_stat_raise(score, b, [:ATTACK, 1, :SPECIAL_ATTACK, 1], false)
end
score -= user.stages[:ATTACK] * 10
score -= user.stages[:SPECIAL_ATTACK] * 10
next score
}
)
@@ -1250,9 +1207,11 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("UserTargetSwapAtkSpAtkSt
target_attack = target.stages[:ATTACK]
target_spatk = target.stages[:SPECIAL_ATTACK]
next Battle::AI::MOVE_USELESS_SCORE if user_attack >= target_attack && user_spatk >= target_spatk
next score - 20 if user_attack + user_spatk <= target_attack + target_spatk
score += (target_attack - user_attack) * 10
score += (target_spatk - user_spatk) * 10
next score - 20 if user_attack + user_spatk >= target_attack + target_spatk
# TODO: Check whether the user has physical/special moves that will be
# stronger after the swap, and vice versa for the target?
score += (target_attack - user_attack) * 5
score += (target_spatk - user_spatk) * 5
next score
}
)
@@ -1270,9 +1229,11 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("UserTargetSwapDefSpDefSt
target_def = target.stages[:DEFENSE]
target_spdef = target.stages[:SPECIAL_DEFENSE]
next Battle::AI::MOVE_USELESS_SCORE if user_def >= target_def && user_spdef >= target_spdef
next score - 20 if user_def + user_spdef <= target_def + target_spdef
score += (target_def - user_def) * 10
score += (target_spdef - user_spdef) * 10
next score - 20 if user_def + user_spdef >= target_def + target_spdef
# TODO: Check whether the target has physical/special moves that will be
# more resisted after the swap, and vice versa for the user?
score += (target_def - user_def) * 5
score += (target_spdef - user_spdef) * 5
next score
}
)
@@ -1493,15 +1454,13 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("UserTargetAverageBaseDef
)
#===============================================================================
# TODO: Review score modifiers.
#
#===============================================================================
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("UserTargetAverageHP",
proc { |score, move, user, target, ai, battle|
if user.hp >= (user.hp + target.hp) / 2
score -= 25
else
score += 25
end
next Battle::AI::MOVE_USELESS_SCORE if user.hp >= (user.hp + target.hp) / 2
mult = (user.hp + target.hp) / (2.0 * user.hp)
score += 10 * mult if mult >= 1.2
next score
}
)

View File

@@ -591,11 +591,6 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("FlinchTarget",
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("FlinchTargetFailsIfUserNotAsleep",
proc { |move, user, ai, battle|
next true if !user.battler.asleep?
}
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.copy("FlinchTarget",
"FlinchTargetFailsIfUserNotAsleep")

View File

@@ -321,7 +321,7 @@ Battle::AI::Handlers::MoveFailureCheck.copy("RaiseUserAtkDef1",
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TwoTurnAttackRaiseUserSpAtkSpDefSpd2",
proc { |score, move, user, target, ai, battle|
# Score for raising user's stats
score = ai.get_score_for_user_stat_raise(score)
score = ai.get_score_for_target_stat_raise(score, user, move.move.statUp)
# Score for being a two turn attack
score = Battle::AI::Handlers.apply_move_effect_against_target_score("TwoTurnAttack",
score, move, user, target, ai, battle)

View File

@@ -440,11 +440,10 @@ Battle::AI::Handlers::MoveFailureCheck.add("UseRandomMoveFromUserParty",
)
#===============================================================================
# TODO: Review score modifiers.
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("UseRandomUserMoveIfAsleep",
proc { |move, user, ai, battle|
next true if !user.battler.asleep?
will_fail = true
user.battler.eachMoveWithIndex do |m, i|
next if move.move.moveBlacklist.include?(m.function)

View File

@@ -322,7 +322,7 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("TargetUsesItsLastUsedMo
proc { |move, user, target, ai, battle|
next true if !target.battler.lastRegularMoveUsed ||
!target.battler.pbHasMove?(target.battler.lastRegularMoveUsed)
next true if target.usingMultiTurnAttack?
next true if target.battler.usingMultiTurnAttack?
next true if move.move.moveBlacklist.include?(GameData::Move.get(target.battler.lastRegularMoveUsed).function_code)
idxMove = -1
target.battler.eachMoveWithIndex do |m, i|
@@ -522,11 +522,6 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("AllBattlersLoseHalfHPUs
next true if target.hp <= 1
}
)
Battle::AI::Handlers::MoveEffectScore.add("AllBattlersLoseHalfHPUserSkipsNextTurn",
proc { |score, move, user, ai, battle|
next score + 10 # Shadow moves are more preferable
}
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("AllBattlersLoseHalfHPUserSkipsNextTurn",
proc { |score, move, user, target, ai, battle|
next score + 20 if target.hp >= target.totalhp / 2
@@ -538,9 +533,7 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("AllBattlersLoseHalfHPUse
#===============================================================================
Battle::AI::Handlers::MoveEffectScore.add("UserLosesHalfHP",
proc { |score, move, user, ai, battle|
score += 20 # Shadow moves are more preferable
score -= 40
next score
next score - 40
}
)
@@ -551,7 +544,6 @@ Battle::AI::Handlers::MoveFailureCheck.copy("StartSunWeather",
"StartShadowSkyWeather")
Battle::AI::Handlers::MoveEffectScore.add("StartShadowSkyWeather",
proc { |score, move, user, ai, battle|
score += 20 # Shadow moves are more preferable
next Battle::AI::MOVE_USELESS_SCORE if battle.pbCheckGlobalAbility(:AIRLOCK) ||
battle.pbCheckGlobalAbility(:CLOUDNINE)
score += 10 if battle.field.weather != :None # Prefer replacing another weather
@@ -577,7 +569,6 @@ Battle::AI::Handlers::MoveFailureCheck.add("RemoveAllScreens",
)
Battle::AI::Handlers::MoveEffectScore.add("RemoveAllScreens",
proc { |score, move, user, ai, battle|
score += 20 # Shadow moves are more preferable
if user.pbOpposingSide.effects[PBEffects::AuroraVeil] > 0 ||
user.pbOpposingSide.effects[PBEffects::Reflect] > 0 ||
user.pbOpposingSide.effects[PBEffects::LightScreen] > 0 ||

View File

@@ -46,8 +46,8 @@ Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:add_predicted_damage,
proc { |score, move, user, target, ai, battle|
if move.damagingMove?
dmg = move.rough_damage
score += [15.0 * dmg / target.hp, 20].min
score += 15 if dmg > target.hp * 1.1 # Predicted to KO the target
score += [20.0 * dmg / target.hp, 25].min
score += 10 if dmg > target.hp * 1.1 # Predicted to KO the target
next score.to_i
end
}
@@ -93,6 +93,12 @@ 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.

View File

@@ -267,57 +267,6 @@ class Battle::AI::AIBattler
return ret
end
def immune_to_move?
user = @ai.user
user_battler = user.battler
move = @ai.move
# TODO: Add consideration of user's Mold Breaker.
move_type = move.rough_type
typeMod = effectiveness_of_type_against_battler(move_type, user)
# Type effectiveness
return true if move.damagingMove? && Effectiveness.ineffective?(typeMod)
# Immunity due to ability/item/other effects
if @ai.trainer.medium_skill?
case move_type
when :GROUND
# TODO: Split target.airborne? into separate parts to allow different
# skill levels to apply to each part.
return true if @battler.airborne? && !move.move.hitsFlyingTargets?
when :FIRE
return true if has_active_ability?(:FLASHFIRE)
when :WATER
return true if has_active_ability?([:DRYSKIN, :STORMDRAIN, :WATERABSORB])
when :GRASS
return true if has_active_ability?(:SAPSIPPER)
when :ELECTRIC
return true if has_active_ability?([:LIGHTNINGROD, :MOTORDRIVE, :VOLTABSORB])
end
return true if move.damagingMove? && Effectiveness.not_very_effective?(typeMod) &&
has_active_ability?(:WONDERGUARD)
return true if move.damagingMove? && user.index != @index && !opposes?(user) &&
has_active_ability?(:TELEPATHY)
return true if move.statusMove? && move.move.canMagicCoat? &&
!@ai.battle.moldBreaker && has_active_ability?(:MAGICBOUNCE) &&
opposes?(user)
return true if move.move.soundMove? && !@ai.battle.moldBreaker && has_active_ability?(:SOUNDPROOF)
return true if move.move.bombMove? && has_active_ability?(:BULLETPROOF)
if move.move.powderMove?
return true if has_type?(:GRASS)
return true if !@ai.battle.moldBreaker && has_active_ability?(:OVERCOAT)
return true if has_active_ability?(:SAFETYGOGGLES)
end
return true if move.move.statusMove? && @battler.effects[PBEffects::Substitute] > 0 &&
!move.move.ignoresSubstitute?(user) && user.index != @index
return true if move.move.statusMove? && Settings::MECHANICS_GENERATION >= 7 &&
user.has_active_ability?(:PRANKSTER) && has_type?(:DARK) &&
opposes?(user)
return true if move.move.priority > 0 && @ai.battle.field.terrain == :Psychic &&
@battler.affectedByTerrain? && opposes?(user)
# TODO: Dazzling/Queenly Majesty go here.
end
return false
end
#=============================================================================
def can_switch_lax?

View File

@@ -40,6 +40,10 @@ class Battle::AI::AIMove
#=============================================================================
def pbTarget(_user)
return @move.pbTarget(_user)
end
# Returns whether this move targets multiple battlers.
def targets_multiple_battlers?
user_battler = @ai_battler.battler