More work on the AI, refactored stat stage multipliers

This commit is contained in:
Maruno17
2023-04-09 22:26:48 +01:00
parent 5d9cc71a99
commit a22c5ea89c
21 changed files with 618 additions and 651 deletions

View File

@@ -45,6 +45,13 @@ class Battle::Battler
attr_accessor :canRestoreIceFace # Whether Hail started in the round
attr_accessor :damageState
# These arrays should all have the same number of values in them
STAT_STAGE_MULTIPLIERS = [2, 2, 2, 2, 2, 2, 2, 3, 4, 5, 6, 7, 8]
STAT_STAGE_DIVISORS = [8, 7, 6, 5, 4, 3, 2, 2, 2, 2, 2, 2, 2]
ACC_EVA_STAGE_MULTIPLIERS = [3, 3, 3, 3, 3, 3, 3, 4, 5, 6, 7, 8, 9]
ACC_EVA_STAGE_DIVISORS = [9, 8, 7, 6, 5, 4, 3, 3, 3, 3, 3, 3, 3]
STAT_STAGE_MAXIMUM = 6 # Is also the minimum (-6)
#=============================================================================
# Complex accessors
#=============================================================================
@@ -240,10 +247,8 @@ class Battle::Battler
#=============================================================================
def pbSpeed
return 1 if fainted?
stageMul = [2, 2, 2, 2, 2, 2, 2, 3, 4, 5, 6, 7, 8]
stageDiv = [8, 7, 6, 5, 4, 3, 2, 2, 2, 2, 2, 2, 2]
stage = @stages[:SPEED] + 6
speed = @speed * stageMul[stage] / stageDiv[stage]
stage = @stages[:SPEED] + STAT_STAGE_MAXIMUM
speed = @speed * STAT_STAGE_MULTIPLIERS[stage] / STAT_STAGE_DIVISORS[stage]
speedMult = 1.0
# Ability effects that alter calculated Speed
if abilityActive?

View File

@@ -3,7 +3,7 @@ class Battle::Battler
# Increase stat stages
#=============================================================================
def statStageAtMax?(stat)
return @stages[stat] >= 6
return @stages[stat] >= STAT_STAGE_MAXIMUM
end
def pbCanRaiseStatStage?(stat, user = nil, move = nil, showFailMsg = false, ignoreContrary = false)
@@ -33,7 +33,7 @@ class Battle::Battler
increment *= 2 if hasActiveAbility?(:SIMPLE)
end
# Change the stat stage
increment = [increment, 6 - @stages[stat]].min
increment = [increment, STAT_STAGE_MAXIMUM - @stages[stat]].min
if increment > 0
stat_name = GameData::Stat.get(stat).name
new = @stages[stat] + increment
@@ -117,7 +117,7 @@ class Battle::Battler
# Decrease stat stages
#=============================================================================
def statStageAtMin?(stat)
return @stages[stat] <= -6
return @stages[stat] <= -STAT_STAGE_MAXIMUM
end
def pbCanLowerStatStage?(stat, user = nil, move = nil, showFailMsg = false,
@@ -183,7 +183,7 @@ class Battle::Battler
increment *= 2 if hasActiveAbility?(:SIMPLE)
end
# Change the stat stage
increment = [increment, 6 + @stages[stat]].min
increment = [increment, STAT_STAGE_MAXIMUM + @stages[stat]].min
if increment > 0
stat_name = GameData::Stat.get(stat).name
new = @stages[stat] - increment

View File

@@ -101,10 +101,11 @@ class Battle::Move
# Check if move can't miss
return true if modifiers[:base_accuracy] == 0
# Calculation
accStage = [[modifiers[:accuracy_stage], -6].max, 6].min + 6
evaStage = [[modifiers[:evasion_stage], -6].max, 6].min + 6
stageMul = [3, 3, 3, 3, 3, 3, 3, 4, 5, 6, 7, 8, 9]
stageDiv = [9, 8, 7, 6, 5, 4, 3, 3, 3, 3, 3, 3, 3]
max_stage = Battle::Battler::STAT_STAGE_MAXIMUM
accStage = [[modifiers[:accuracy_stage], -max_stage].max, max_stage].min + max_stage
evaStage = [[modifiers[:evasion_stage], -max_stage].max, max_stage].min + max_stage
stageMul = Battle::Battler::ACC_EVA_STAGE_MULTIPLIERS
stageDiv = Battle::Battler::ACC_EVA_STAGE_DIVISORS
accuracy = 100.0 * stageMul[accStage] / stageDiv[accStage]
evasion = 100.0 * stageMul[evaStage] / stageDiv[evaStage]
accuracy = (accuracy * modifiers[:accuracy_multiplier]).round
@@ -226,13 +227,13 @@ class Battle::Move
def pbModifyDamage(damageMult, user, target); return damageMult; end
def pbGetAttackStats(user, target)
return user.spatk, user.stages[:SPECIAL_ATTACK] + 6 if specialMove?
return user.attack, user.stages[:ATTACK] + 6
return user.spatk, user.stages[:SPECIAL_ATTACK] + Battle::Battler::STAT_STAGE_MAXIMUM if specialMove?
return user.attack, user.stages[:ATTACK] + Battle::Battler::STAT_STAGE_MAXIMUM
end
def pbGetDefenseStats(user, target)
return target.spdef, target.stages[:SPECIAL_DEFENSE] + 6 if specialMove?
return target.defense, target.stages[:DEFENSE] + 6
return target.spdef, target.stages[:SPECIAL_DEFENSE] + Battle::Battler::STAT_STAGE_MAXIMUM if specialMove?
return target.defense, target.stages[:DEFENSE] + Battle::Battler::STAT_STAGE_MAXIMUM
end
def pbCalcDamage(user, target, numTargets = 1)
@@ -241,8 +242,9 @@ class Battle::Move
target.damageState.calcDamage = 1
return
end
stageMul = [2, 2, 2, 2, 2, 2, 2, 3, 4, 5, 6, 7, 8]
stageDiv = [8, 7, 6, 5, 4, 3, 2, 2, 2, 2, 2, 2, 2]
max_stage = Battle::Battler::STAT_STAGE_MAXIMUM
stageMul = Battle::Battler::STAT_STAGE_MULTIPLIERS
stageDiv = Battle::Battler::STAT_STAGE_DIVISORS
# Get the move's type
type = @calcType # nil is treated as physical
# Calculate whether this hit deals critical damage
@@ -252,13 +254,13 @@ class Battle::Move
# Calculate user's attack stat
atk, atkStage = pbGetAttackStats(user, target)
if !target.hasActiveAbility?(:UNAWARE) || @battle.moldBreaker
atkStage = 6 if target.damageState.critical && atkStage < 6
atkStage = max_stage if target.damageState.critical && atkStage < max_stage
atk = (atk.to_f * stageMul[atkStage] / stageDiv[atkStage]).floor
end
# Calculate target's defense stat
defense, defStage = pbGetDefenseStats(user, target)
if !user.hasActiveAbility?(:UNAWARE)
defStage = 6 if target.damageState.critical && defStage > 6
defStage = max_stage if target.damageState.critical && defStage > max_stage
defense = (defense.to_f * stageMul[defStage] / stageDiv[defStage]).floor
end
# Calculate all multiplier effects

View File

@@ -94,14 +94,14 @@ class Battle::Move::MaxUserAttackLoseHalfOfTotalHP < Battle::Move
hpLoss = [user.totalhp / 2, 1].max
user.pbReduceHP(hpLoss, false, false)
if user.hasActiveAbility?(:CONTRARY)
user.stages[@statUp[0]] = -6
user.stages[@statUp[0]] = -Battle::Battler::STAT_STAGE_MAXIMUM
user.statsLoweredThisRound = true
user.statsDropped = true
@battle.pbCommonAnimation("StatDown", user)
@battle.pbDisplay(_INTL("{1} cut its own HP and minimized its {2}!",
user.pbThis, GameData::Stat.get(@statUp[0]).name))
else
user.stages[@statUp[0]] = 6
user.stages[@statUp[0]] = Battle::Battler::STAT_STAGE_MAXIMUM
user.statsRaisedThisRound = true
@battle.pbCommonAnimation("StatUp", user)
@battle.pbDisplay(_INTL("{1} cut its own HP and maximized its {2}!",

View File

@@ -1121,17 +1121,18 @@ class Battle::Move::CategoryDependsOnHigherDamagePoisonTarget < Battle::Move::Po
def pbOnStartUse(user, targets)
target = targets[0]
stageMul = [2, 2, 2, 2, 2, 2, 2, 3, 4, 5, 6, 7, 8]
stageDiv = [8, 7, 6, 5, 4, 3, 2, 2, 2, 2, 2, 2, 2]
max_stage = Battle::Battler::STAT_STAGE_MAXIMUM
stageMul = Battle::Battler::STAT_STAGE_MULTIPLIERS
stageDiv = Battle::Battler::STAT_STAGE_DIVISORS
# Calculate user's effective attacking values
attack_stage = user.stages[:ATTACK] + 6
attack_stage = user.stages[:ATTACK] + max_stage
real_attack = (user.attack.to_f * stageMul[attack_stage] / stageDiv[attack_stage]).floor
special_attack_stage = user.stages[:SPECIAL_ATTACK] + 6
special_attack_stage = user.stages[:SPECIAL_ATTACK] + max_stage
real_special_attack = (user.spatk.to_f * stageMul[special_attack_stage] / stageDiv[special_attack_stage]).floor
# Calculate target's effective defending values
defense_stage = target.stages[:DEFENSE] + 6
defense_stage = target.stages[:DEFENSE] + max_stage
real_defense = (target.defense.to_f * stageMul[defense_stage] / stageDiv[defense_stage]).floor
special_defense_stage = target.stages[:SPECIAL_DEFENSE] + 6
special_defense_stage = target.stages[:SPECIAL_DEFENSE] + max_stage
real_special_defense = (target.spdef.to_f * stageMul[special_defense_stage] / stageDiv[special_defense_stage]).floor
# Perform simple damage calculation
physical_damage = real_attack.to_f / real_defense
@@ -1166,13 +1167,14 @@ class Battle::Move::CategoryDependsOnHigherDamageIgnoreTargetAbility < Battle::M
def pbOnStartUse(user, targets)
# Calculate user's effective attacking value
stageMul = [2, 2, 2, 2, 2, 2, 2, 3, 4, 5, 6, 7, 8]
stageDiv = [8, 7, 6, 5, 4, 3, 2, 2, 2, 2, 2, 2, 2]
max_stage = Battle::Battler::STAT_STAGE_MAXIMUM
stageMul = Battle::Battler::STAT_STAGE_MULTIPLIERS
stageDiv = Battle::Battler::STAT_STAGE_DIVISORS
atk = user.attack
atkStage = user.stages[:ATTACK] + 6
atkStage = user.stages[:ATTACK] + max_stage
realAtk = (atk.to_f * stageMul[atkStage] / stageDiv[atkStage]).floor
spAtk = user.spatk
spAtkStage = user.stages[:SPECIAL_ATTACK] + 6
spAtkStage = user.stages[:SPECIAL_ATTACK] + max_stage
realSpAtk = (spAtk.to_f * stageMul[spAtkStage] / stageDiv[spAtkStage]).floor
# Determine move's category
@calcCategory = (realAtk > realSpAtk) ? 0 : 1
@@ -1187,7 +1189,7 @@ end
#===============================================================================
class Battle::Move::UseUserDefenseInsteadOfUserAttack < Battle::Move
def pbGetAttackStats(user, target)
return user.defense, user.stages[:DEFENSE] + 6
return user.defense, user.stages[:DEFENSE] + Battle::Battler::STAT_STAGE_MAXIMUM
end
end
@@ -1197,8 +1199,8 @@ end
#===============================================================================
class Battle::Move::UseTargetAttackInsteadOfUserAttack < Battle::Move
def pbGetAttackStats(user, target)
return target.spatk, target.stages[:SPECIAL_ATTACK] + 6 if specialMove?
return target.attack, target.stages[:ATTACK] + 6
return target.spatk, target.stages[:SPECIAL_ATTACK] + Battle::Battler::STAT_STAGE_MAXIMUM if specialMove?
return target.attack, target.stages[:ATTACK] + Battle::Battler::STAT_STAGE_MAXIMUM
end
end
@@ -1208,7 +1210,7 @@ end
#===============================================================================
class Battle::Move::UseTargetDefenseInsteadOfTargetSpDef < Battle::Move
def pbGetDefenseStats(user, target)
return target.defense, target.stages[:DEFENSE] + 6
return target.defense, target.stages[:DEFENSE] + Battle::Battler::STAT_STAGE_MAXIMUM
end
end
@@ -1264,7 +1266,7 @@ class Battle::Move::IgnoreTargetDefSpDefEvaStatStages < Battle::Move
def pbGetDefenseStats(user, target)
ret1, _ret2 = super
return ret1, 6 # Def/SpDef stat stage
return ret1, Battle::Battler::STAT_STAGE_MAXIMUM # Def/SpDef stat stage
end
end

View File

@@ -139,10 +139,11 @@ class Battle::Move::HealUserByTargetAttackLowerTargetAttack1 < Battle::Move
def pbEffectAgainstTarget(user, target)
# Calculate target's effective attack value
stageMul = [2, 2, 2, 2, 2, 2, 2, 3, 4, 5, 6, 7, 8]
stageDiv = [8, 7, 6, 5, 4, 3, 2, 2, 2, 2, 2, 2, 2]
max_stage = Battle::Battler::STAT_STAGE_MAXIMUM
stageMul = Battle::Battler::STAT_STAGE_MULTIPLIERS
stageDiv = Battle::Battler::STAT_STAGE_DIVISORS
atk = target.attack
atkStage = target.stages[@statDown[0]] + 6
atkStage = target.stages[@statDown[0]] + max_stage
healAmt = (atk.to_f * stageMul[atkStage] / stageDiv[atkStage]).floor
# Reduce target's Attack stat
if target.pbCanLowerStatStage?(@statDown[0], user, self)

View File

@@ -231,7 +231,6 @@ class Battle::AI
if targets
# Reset the base score for the move (each target will add its own score)
score = 0
# TODO: Distinguish between affected foes and affected allies?
affected_targets = 0
# Get a score for the move against each target in turn
orig_move = @move.move # In case move is Mirror Move and changes depending on the target

View File

@@ -62,7 +62,7 @@ class Battle::AI
# Calculate amount that stat will be raised by
increment = stat_changes[idx + 1]
increment *= 2 if !fixed_change && !@battle.moldBreaker && target.has_active_ability?(:SIMPLE)
increment = [increment, 6 - target.stages[stat]].min # The actual stages gained
increment = [increment, Battle::Battler::STAT_STAGE_MAXIMUM - target.stages[stat]].min # The actual stages gained
# Count this as a valid stat raise
real_stat_changes.push([stat, increment]) if increment > 0
end
@@ -98,8 +98,10 @@ class Battle::AI
if !fixed_change
return false if !target.battler.pbCanRaiseStatStage?(stat, @user.battler, @move.move)
end
# TODO: Not worth it if target is predicted to switch out (except via Baton Pass).
# Check if target won't benefit from the stat being raised
# TODO: Exception if target knows Baton Pass/Stored Power?
return true if target.has_move_with_function?("SwitchOutUserPassOnEffects",
"PowerHigherWithUserPositiveStatStages")
case stat
when :ATTACK
return false if !target.check_for_move { |m| m.physicalMove?(m.type) &&
@@ -146,24 +148,18 @@ class Battle::AI
#=============================================================================
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 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 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 * desire_mult * 5
end
# Prefer if user is at high HP, don't prefer if user is at low HP
if target.index != @user.index
score += total_increment * desire_mult * ((100 * @user.hp / @user.totalhp) - 50) / 8 # +6 to -6 per stage
if @trainer.has_skill_flag?("HPAware")
# Prefer if user is at high HP, don't prefer if user is at low HP
if target.index != @user.index
score += total_increment * desire_mult * ((100 * @user.hp / @user.totalhp) - 50) / 8 # +6 to -6 per stage
end
# Prefer if target is at high HP, don't prefer if target is at low HP
score += total_increment * desire_mult * ((100 * target.hp / target.totalhp) - 50) / 8 # +6 to -6 per stage
end
# Prefer if target is at high HP, don't prefer if target is at low HP
score += total_increment * desire_mult * ((100 * target.hp / target.totalhp) - 50) / 8 # +6 to -6 per stage
# TODO: Look at abilities that trigger upon stat raise. There are none.
return score
end
@@ -173,15 +169,16 @@ class Battle::AI
#=============================================================================
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]
max_stage = Battle::Battler::STAT_STAGE_MAXIMUM
stage_mul = Battle::Battler::STAT_STAGE_MULTIPLIERS
stage_div = Battle::Battler::STAT_STAGE_DIVISORS
if [:ACCURACY, :EVASION].include?(stat)
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]
stage_mul = Battle::Battler::ACC_EVA_STAGE_MULTIPLIERS
stage_div = Battle::Battler::ACC_EVA_STAGE_DIVISORS
end
old_stage = target.stages[stat]
new_stage = old_stage + increment
inc_mult = (stage_mul[new_stage + 6].to_f * stage_div[old_stage + 6]) / (stage_div[new_stage + 6] * stage_mul[old_stage + 6])
inc_mult = (stage_mul[new_stage + max_stage].to_f * stage_div[old_stage + max_stage]) / (stage_div[new_stage + max_stage] * stage_mul[old_stage + max_stage])
inc_mult -= 1
inc_mult *= desire_mult
# Stat-based score changes
@@ -304,7 +301,6 @@ class Battle::AI
# 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, target, stat_changes, whole_effect = true,
fixed_change = false, ignore_contrary = false)
@@ -358,8 +354,8 @@ class Battle::AI
end
# Calculate amount that stat will be lowered by
decrement = stat_changes[idx + 1]
decrement *= 2 if !fixed_change && !@battle.moldBreaker && @user.has_active_ability?(:SIMPLE)
decrement = [decrement, 6 + target.stages[stat]].min # The actual stages lost
decrement *= 2 if !fixed_change && !@battle.moldBreaker && target.has_active_ability?(:SIMPLE)
decrement = [decrement, Battle::Battler::STAT_STAGE_MAXIMUM + target.stages[stat]].min # The actual stages lost
# Count this as a valid stat drop
real_stat_changes.push([stat, decrement]) if decrement > 0
end
@@ -390,12 +386,12 @@ class Battle::AI
# TODO: Make sure the move's actual damage category is taken into account,
# i.e. CategoryDependsOnHigherDamagePoisonTarget and
# CategoryDependsOnHigherDamageIgnoreTargetAbility.
# TODO: Revisit this method as parts may need rewriting.
#=============================================================================
def stat_drop_worthwhile?(target, stat, fixed_change = false)
if !fixed_change
return false if !target.battler.pbCanLowerStatStage?(stat, @user.battler, @move.move)
end
# TODO: Not worth it if target is predicted to switch out (except via Baton Pass).
# Check if target won't benefit from the stat being lowered
case stat
when :ATTACK
@@ -437,46 +433,40 @@ class Battle::AI
#=============================================================================
# Make score changes based on the general concept of lowering stats at all.
# 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, 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
# lethal damage.
# 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 * 5
end
# Prefer if user is at high HP, don't prefer if user is at low HP
if target.index != @user.index
score += total_decrement * desire_mult * ((100 * @user.hp / @user.totalhp) - 50) / 8 # +6 to -6 per stage
if @trainer.has_skill_flag?("HPAware")
# Prefer if user is at high HP, don't prefer if user is at low HP
if target.index != @user.index
score += total_decrement * desire_mult * ((100 * @user.hp / @user.totalhp) - 50) / 8 # +6 to -6 per stage
end
# Prefer if target is at high HP, don't prefer if target is at low HP
score += total_decrement * desire_mult * ((100 * target.hp / target.totalhp) - 50) / 8 # +6 to -6 per stage
end
# Prefer if target is at high HP, don't prefer if target is at low HP
score += total_decrement * desire_mult * ((100 * target.hp / target.totalhp) - 50) / 8 # +6 to -6 per stage
# TODO: Look at abilities that trigger upon stat lowering.
return score
end
#=============================================================================
# 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, 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]
max_stage = Battle::Battler::STAT_STAGE_MAXIMUM
stage_mul = Battle::Battler::STAT_STAGE_MULTIPLIERS
stage_div = Battle::Battler::STAT_STAGE_DIVISORS
if [:ACCURACY, :EVASION].include?(stat)
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]
stage_mul = Battle::Battler::ACC_EVA_STAGE_MULTIPLIERS
stage_div = Battle::Battler::ACC_EVA_STAGE_DIVISORS
end
old_stage = target.stages[stat]
new_stage = old_stage - decrement
dec_mult = (stage_mul[old_stage + 6].to_f * stage_div[new_stage + 6]) / (stage_div[old_stage + 6] * stage_mul[new_stage + 6])
dec_mult = (stage_mul[old_stage + max_stage].to_f * stage_div[new_stage + max_stage]) / (stage_div[old_stage + max_stage] * stage_mul[new_stage + max_stage])
dec_mult -= 1
dec_mult *= desire_mult
# Stat-based score changes

View File

@@ -1,27 +1,3 @@
# TODO: Check all lingering effects to see if the AI needs to adjust itself
# because of them.
#===============================================================================
#
#===============================================================================
# TODO:
# => Don't prefer damaging move if it won't KO, user has Stance Change and
# is in shield form, and user is slower than the target
# => Check memory for past damage dealt by a target's non-high priority move,
# and prefer move if user is slower than the target and another hit from
# the same amount will KO the user
# => Check memory for past damage dealt by a target's priority move, and don't
# prefer the move if user is slower than the target and can't move faster
# than it because of priority
# => Check memory for whether target has previously used Quick Guard, and
# don't prefer move if so
#===============================================================================
#===============================================================================
#===============================================================================
#===============================================================================
#===============================================================================
#===============================================================================
# Don't prefer hitting a wild shiny Pokémon.
#===============================================================================
@@ -37,233 +13,78 @@ Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:shiny_target,
)
#===============================================================================
# Adjust score based on how much damage it can deal.
# Prefer the move even more if it's predicted to do enough damage to KO the
# target.
# TODO: Review score modifier.
# => If target has previously used a move that will hurt the user by 30% of
# its current HP or more, moreso don't prefer a status move.
# => Include EOR damage in this?
# => Prefer move if it will KO the target (moreso if user is slower than target)
# Prefer Shadow moves (for flavour).
#===============================================================================
Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:predicted_damage,
Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:shadow_moves,
proc { |score, move, user, target, ai, battle|
if move.damagingMove?
dmg = move.rough_damage
if move.rough_type == :SHADOW
old_score = score
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
score += 10
PBDebug.log_score_change(score - old_score, "predicted to KO the target")
end
score += 10
PBDebug.log_score_change(score - old_score, "prefer using a Shadow move")
end
next score
}
)
#===============================================================================
# Account for accuracy of move.
# TODO: Review score modifier.
# If user is frozen, prefer a move that can thaw the user.
#===============================================================================
Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:move_accuracy,
proc { |score, move, user, target, ai, battle|
acc = move.rough_accuracy.to_i
if acc < 90
Battle::AI::Handlers::GeneralMoveScore.add(:thawing_move_when_frozen,
proc { |score, move, user, ai, battle|
if ai.trainer.medium_skill? && user.status == :FROZEN
old_score = score
score -= (0.2 * (100 - acc)).to_i # -2 (89%) to -19 (1%)
PBDebug.log_score_change(score - old_score, "accuracy (predicted #{acc}%)")
if move.move.thawsUser?
score += 20
PBDebug.log_score_change(score - old_score, "move will thaw the user")
elsif user.check_for_move { |m| m.thawsUser? }
score -= 20 # Don't prefer this move if user knows another move that thaws
PBDebug.log_score_change(score - old_score, "user knows another move will thaw it")
end
end
next score
}
)
#===============================================================================
# Don't prefer attacking the target if they'd be semi-invulnerable.
# TODO: Review score modifier.
# Prefer using a priority move if the user is slower than the target and...
# - the user is at low HP, or
# - the target is predicted to be knocked out by the move.
# TODO: Less prefer a priority move if any foe knows Quick Guard?
#===============================================================================
Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:target_semi_invulnerable,
proc { |score, move, user, target, ai, battle|
# TODO: Also consider the move's priority compared to that of the move the
# target is using.
if move.rough_accuracy > 0 && user.faster_than?(target) &&
(target.battler.semiInvulnerable? || target.effects[PBEffects::SkyDrop] >= 0)
miss = true
miss = false if user.has_active_ability?(:NOGUARD) || target.has_active_ability?(:NOGUARD)
if ai.trainer.high_skill? && miss
# Knows what can get past semi-invulnerability
if target.effects[PBEffects::SkyDrop] >= 0 ||
target.battler.inTwoTurnAttack?("TwoTurnAttackInvulnerableInSky",
"TwoTurnAttackInvulnerableInSkyParalyzeTarget",
"TwoTurnAttackInvulnerableInSkyTargetCannotAct")
miss = false if move.move.hitsFlyingTargets?
elsif target.battler.inTwoTurnAttack?("TwoTurnAttackInvulnerableUnderground")
miss = false if move.move.hitsDiggingTargets?
elsif target.battler.inTwoTurnAttack?("TwoTurnAttackInvulnerableUnderwater")
miss = false if move.move.hitsDivingTargets?
end
end
if miss
Battle::AI::Handlers::GeneralMoveScore.add(:priority_move_against_faster_target,
proc { |score, move, user, ai, battle|
if ai.trainer.high_skill? && target.faster_than?(user) && move.rough_priority(user) > 0
# User is at risk of being knocked out
if ai.trainer.has_skill_flag?("HPAware") && user.hp < user.totalhp / 3
old_score = score
score = Battle::AI::MOVE_USELESS_SCORE
PBDebug.log_score_change(score - old_score, "target is semi-invulnerable")
score += 8
PBDebug.log_score_change(score - old_score, "user at low HP and move has priority over faster target")
end
end
next score
}
)
#===============================================================================
#
#===============================================================================
# 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.
#===============================================================================
Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:thawing_move_against_frozen_target,
proc { |score, move, user, target, ai, battle|
if ai.trainer.medium_skill? && target.status == :FROZEN
if move.rough_type == :FIRE || (Settings::MECHANICS_GENERATION >= 6 && move.move.thawsUser?)
# Target is predicted to be knocked out by the move
if move.damaging_move? && move.rough_damage >= target.hp
old_score = score
score -= 20
PBDebug.log_score_change(score - old_score, "thaws the target")
score += 8
PBDebug.log_score_change(score - old_score, "target at low HP and move has priority over faster target")
end
end
next score
}
)
#===============================================================================
#
#===============================================================================
# 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
# lowered offences (Atk/Def or SpAtk/SpDef, whichever is relevant).
#===============================================================================
# Prefer flinching external effects (note that move effects which cause
# flinching are dealt with in the function code part of score calculation).
# TODO: Review score modifier.
#===============================================================================
Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:external_flinching_effects,
proc { |score, move, user, target, ai, battle|
if ai.trainer.medium_skill? && move.damagingMove? && !move.move.flinchingMove?
if (battle.moldBreaker || !target.has_active_ability?([:INNERFOCUS, :SHIELDDUST])) &&
target.effects[PBEffects::Substitute] == 0
if user.has_active_item?([:KINGSROCK, :RAZORFANG]) ||
user.has_active_ability?(:STENCH)
old_score = score
score += 8
PBDebug.log_score_change(score - old_score, "flinching")
end
end
end
next score
}
)
#===============================================================================
#
#===============================================================================
# TODO: Don't prefer contact move if making contact with the target could
# trigger an effect that's bad for the user (Static, etc.).
# => Also check if target has previously used Spiky Shield/King's Shield/
# Baneful Bunker, and don't prefer move if so
#===============================================================================
#
#===============================================================================
# TODO: Prefer a contact move if making contact with the target could trigger
# an effect that's good for the user (Poison Touch/Pickpocket).
#===============================================================================
#
#===============================================================================
# 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)
# have priority moves of their own? Limit this to prefer priority damaging
# moves?
#===============================================================================
# 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.
#===============================================================================
Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(: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 -= 20
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
# Destiny Bond.
# TODO: Review score modifier.
# => Also don't prefer damaging moves if user is slower than the target, move
# is likely to be lethal, and target has previously used Destiny Bond
#===============================================================================
Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:knocking_out_a_destiny_bonder,
proc { |score, move, user, target, ai, battle|
if ai.trainer.medium_skill? && move.damagingMove? && target.effects[PBEffects::DestinyBond]
dmg = move.rough_damage
if dmg > target.hp * 1.05 # Predicted to KO the target
old_score = score
score -= 20
score -= 10 if battle.pbAbleNonActiveCount(user.idxOwnSide) == 0
PBDebug.log_score_change(score - old_score, "don't want to KO the Destiny Bonding target")
end
end
next score
}
)
#===============================================================================
#
#===============================================================================
# TODO: Don't prefer Fire-type moves if target has previously used Powder and is
# faster than the user.
#===============================================================================
#
#===============================================================================
# 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.).
#===============================================================================
# Don't prefer a move that can be Magic Coated if the target (or any foe if the
# move doesn't have a target) knows Magic Coat/has Magic Bounce.
#===============================================================================
Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:target_can_Magic_Coat_or_Bounce_move,
proc { |score, move, user, target, ai, battle|
# TODO: Modify the semiInvulnerable? check to only apply if the target will
# still be invulnerable when the user acts, i.e. compare speeds?
if move.statusMove? && move.move.canMagicCoat? &&
target.opposes?(user) && !target.battler.semiInvulnerable?
if move.statusMove? && move.move.canMagicCoat? && target.opposes?(user) &&
(target.faster_than?(user) || !target.battler.semiInvulnerable?)
old_score = score
if !battle.moldBreaker && target.has_active_ability?(:MAGICBOUNCE)
score = Battle::AI::MOVE_USELESS_SCORE
PBDebug.log_score_change(score - old_score, "useless because target will Magic Bounce it")
elsif target.has_move_with_function?("BounceBackProblemCausingStatusMoves")
elsif target.has_move_with_function?("BounceBackProblemCausingStatusMoves") &&
target.can_attack? && !target.battler.semiInvulnerable?
score -= 7
PBDebug.log_score_change(score - old_score, "target knows Magic Coat and could bounce it")
end
@@ -277,15 +98,13 @@ Battle::AI::Handlers::GeneralMoveScore.add(:any_foe_can_Magic_Coat_or_Bounce_mov
if move.statusMove? && move.move.canMagicCoat? && move.pbTarget(user.battler).num_targets == 0
old_score = score
ai.each_foe_battler(user.side) do |b, i|
# TODO: Modify the semiInvulnerable? check to only apply if the target
# will still be invulnerable when the user acts, i.e. compare
# speeds?
next if b.battler.semiInvulnerable?
next if user.faster_than?(b) && b.battler.semiInvulnerable?
if b.has_active_ability?(:MAGICBOUNCE) && !battle.moldBreaker
score = Battle::AI::MOVE_USELESS_SCORE
PBDebug.log_score_change(score - old_score, "useless because a foe will Magic Bounce it")
break
elsif b.has_move_with_function?("BounceBackProblemCausingStatusMoves")
elsif b.has_move_with_function?("BounceBackProblemCausingStatusMoves") &&
b.can_attack? && !b.battler.semiInvulnerable?
score -= 7
PBDebug.log_score_change(score - old_score, "a foe knows Magic Coat and could bounce it")
break
@@ -316,52 +135,6 @@ Battle::AI::Handlers::GeneralMoveScore.add(:any_battler_can_Snatch_move,
}
)
#===============================================================================
#===============================================================================
#===============================================================================
#===============================================================================
#===============================================================================
#===============================================================================
#
#===============================================================================
# TODO: Prefer Shadow moves (for flavour).
#===============================================================================
# If user is frozen, prefer a move that can thaw the user.
#===============================================================================
Battle::AI::Handlers::GeneralMoveScore.add(:thawing_move_when_frozen,
proc { |score, move, user, ai, battle|
if ai.trainer.medium_skill? && user.status == :FROZEN
old_score = score
if move.move.thawsUser?
score += 20
PBDebug.log_score_change(score - old_score, "move will thaw the user")
elsif user.check_for_move { |m| m.thawsUser? }
score -= 20 # Don't prefer this move if user knows another move that thaws
PBDebug.log_score_change(score - old_score, "user knows another move will thaw it")
end
end
next score
}
)
#===============================================================================
# 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 -= 10 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.
@@ -371,9 +144,9 @@ Battle::AI::Handlers::GeneralMoveScore.add(:good_move_for_choice_item,
if ai.trainer.medium_skill?
if user.has_active_item?([:CHOICEBAND, :CHOICESPECS, :CHOICESCARF]) ||
user.has_active_ability?(:GORILLATACTICS)
old_score = score
# Really don't prefer status moves (except Trick)
if move.statusMove? && move.function != "UserTargetSwapItems"
old_score = score
score -= 25
PBDebug.log_score_change(score - old_score, "don't want to be Choiced into a status move")
next score
@@ -400,7 +173,6 @@ Battle::AI::Handlers::GeneralMoveScore.add(:good_move_for_choice_item,
# Prefer damaging moves if the foe is down to their last Pokémon (opportunistic).
# Prefer damaging moves if the AI is down to its last Pokémon but the foe has
# more (desperate).
# TODO: Review score modifier.
#===============================================================================
Battle::AI::Handlers::GeneralMoveScore.add(:damaging_move_and_either_side_no_reserves,
proc { |score, move, user, ai, battle|
@@ -426,11 +198,225 @@ Battle::AI::Handlers::GeneralMoveScore.add(:damaging_move_and_either_side_no_res
#===============================================================================
#
#===============================================================================
# TODO: Don't prefer a move that is stopped by Wide Guard if any foe has
# previously used Wide Guard.
# TODO: Don't prefer Fire-type moves if target has previously used Powder and is
# faster than the user.
#===============================================================================
#
#===============================================================================
# TODO: Don't prefer sound move if user hasn't been Throat Chopped but a foe has
# previously used Throat Chop.
# TODO: Don't prefer Normal-type moves if target has previously used Ion Deluge
# and is immune to Electric moves.
#===============================================================================
#
#===============================================================================
# TODO: Don't prefer a move that is stopped by Wide Guard if any foe has
# previously used Wide Guard.
#===============================================================================
# Don't prefer attacking the target if they'd be semi-invulnerable.
#===============================================================================
Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:target_semi_invulnerable,
proc { |score, move, user, target, ai, battle|
if ai.trainer.medium_skill? && move.rough_accuracy > 0 &&
(target.battler.semiInvulnerable? || target.effects[PBEffects::SkyDrop] >= 0)
next score if user.has_active_ability?(:NOGUARD) || target.has_active_ability?(:NOGUARD)
priority = move.rough_priority
if priority > 0 || (priority == 0 && user.faster_than?(target)) # User goes first
miss = true
if ai.trainer.high_skill?
# Knows what can get past semi-invulnerability
if target.effects[PBEffects::SkyDrop] >= 0 ||
target.battler.inTwoTurnAttack?("TwoTurnAttackInvulnerableInSky",
"TwoTurnAttackInvulnerableInSkyParalyzeTarget",
"TwoTurnAttackInvulnerableInSkyTargetCannotAct")
miss = false if move.move.hitsFlyingTargets?
elsif target.battler.inTwoTurnAttack?("TwoTurnAttackInvulnerableUnderground")
miss = false if move.move.hitsDiggingTargets?
elsif target.battler.inTwoTurnAttack?("TwoTurnAttackInvulnerableUnderwater")
miss = false if move.move.hitsDivingTargets?
end
end
if miss
old_score = score
score = Battle::AI::MOVE_USELESS_SCORE
PBDebug.log_score_change(score - old_score, "target is semi-invulnerable")
end
end
end
next score
}
)
#===============================================================================
# Account for accuracy of move.
#===============================================================================
Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:predicted_accuracy,
proc { |score, move, user, target, ai, battle|
acc = move.rough_accuracy.to_i
if acc < 90
old_score = score
score -= (0.25 * (100 - acc)).to_i # -2 (89%) to -24 (1%)
PBDebug.log_score_change(score - old_score, "accuracy (predicted #{acc}%)")
end
next score
}
)
#===============================================================================
# Adjust score based on how much damage it can deal.
# Prefer the move even more if it's predicted to do enough damage to KO the
# target.
#===============================================================================
Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:predicted_damage,
proc { |score, move, user, target, ai, battle|
if move.damagingMove?
dmg = move.rough_damage
old_score = score
if target.effects[PBEffects::Substitute] > 0
target_hp = target.effects[PBEffects::Substitute]
score += ([15.0 * dmg / target.effects[PBEffects::Substitute], 20].min).to_i
PBDebug.log_score_change(score - old_score, "damaging move (predicted damage #{dmg} = #{100 * dmg / target.hp}% of target's Substitute)")
else
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 ai.trainer.has_skill_flag?("HPAware") && dmg > target.hp * 1.1 # Predicted to KO the target
old_score = score
score += 10
PBDebug.log_score_change(score - old_score, "predicted to KO the target")
end
end
end
next score
}
)
#===============================================================================
# Prefer flinching external effects (note that move effects which cause
# flinching are dealt with in the function code part of score calculation).
#===============================================================================
Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:external_flinching_effects,
proc { |score, move, user, target, ai, battle|
if ai.trainer.medium_skill? && move.damagingMove? && !move.move.flinchingMove? &&
user.faster_than?(target) && target.effects[PBEffects::Substitute] == 0
if user.has_active_item?([:KINGSROCK, :RAZORFANG]) ||
user.has_active_ability?(:STENCH)
if battle.moldBreaker || !target.has_active_ability?([:INNERFOCUS, :SHIELDDUST])
old_score = score
score += 8
PBDebug.log_score_change(score - old_score, "added chance to cause flinching")
end
end
end
next score
}
)
#===============================================================================
# If target is frozen, don't prefer moves that could thaw them.
#===============================================================================
Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:thawing_move_against_frozen_target,
proc { |score, move, user, target, ai, battle|
if ai.trainer.medium_skill? && target.status == :FROZEN
if move.rough_type == :FIRE || (Settings::MECHANICS_GENERATION >= 6 && move.move.thawsUser?)
old_score = score
score -= 20
PBDebug.log_score_change(score - old_score, "thaws the target")
end
end
next score
}
)
#===============================================================================
#
#===============================================================================
# TODO: Prefer a contact move if making contact with the target could trigger
# an effect that's good for the user (Poison Touch/Pickpocket).
#===============================================================================
#
#===============================================================================
# TODO: Don't prefer contact move if making contact with the target could
# trigger an effect that's bad for the user (Static, etc.).
# => Also check if target has previously used Spiky Shield/King's Shield/
# Baneful Bunker, and don't prefer move if so
#===============================================================================
# Don't prefer damaging moves that will knock out the target if they are using
# Destiny Bond or Grudge.
#===============================================================================
Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:knocking_out_a_destiny_bonder_or_grudger,
proc { |score, move, user, target, ai, battle|
if (ai.trainer.has_skill_flag?("HPAware") || ai.trainer.high_skill?) && move.damagingMove? &&
(target.effects[PBEffects::DestinyBond] || target.effects[PBEffects::Grudge])
priority = move.rough_priority
if priority > 0 || (priority == 0 && user.faster_than?(target)) # User goes first
if move.rough_damage > target.hp * 1.1 # Predicted to KO the target
old_score = score
if target.effects[PBEffects::DestinyBond]
score -= 20
score -= 10 if battle.pbAbleNonActiveCount(user.idxOwnSide) == 0
PBDebug.log_score_change(score - old_score, "don't want to KO the Destiny Bonding target")
elsif target.effects[PBEffects::Grudge]
score -= 15
score -= 7 if battle.pbAbleNonActiveCount(user.idxOwnSide) == 0
PBDebug.log_score_change(score - old_score, "don't want to KO the Grudge-using target")
end
end
end
end
next score
}
)
#===============================================================================
#
#===============================================================================
# TODO: Don't prefer damaging moves if the target is using Rage and they benefit
# from the raised Attack.
#===============================================================================
# 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.
#===============================================================================
Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(: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)
# Worth damaging the target if it can be knocked out before Bide ends
if ai.trainer.has_skill_flag?("HPAware")
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)
next score if dmg * hits_possible + eor_dmg > target.hp * 1.1
end
old_score = score
score -= 20
PBDebug.log_score_change(score - old_score, "don't want to damage the Biding target")
end
end
next score
}
)
#===============================================================================
# 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 -= 10 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
}
)

View File

@@ -17,6 +17,8 @@
# ConsiderSwitching (can choose to switch out Pokémon)
# ReserveLastPokemon (don't switch it in if possible)
# UsePokemonInOrder (uses earliest-listed Pokémon possible)
#
# TODO: Add more skill flags.
#===============================================================================
class Battle::AI::AITrainer
attr_reader :side, :trainer_index
@@ -53,7 +55,6 @@ class Battle::AI::AITrainer
if @trainer
@trainer.flags.each { |flag| @skill_flags.push(flag) }
end
# TODO: Add skill flags depending on @skill.
if @skill > 0
@skill_flags.push("PredictMoveFailure")
@skill_flags.push("ScoreMoves")
@@ -74,7 +75,6 @@ class Battle::AI::AITrainer
# NOTE: Any skill flag which is shorthand for multiple other skill flags
# should be "unpacked" here.
# TODO: Have a bunch of "AntiX" flags that negate the corresponding "X" flags.
# TODO: Have flag "DontReserveLastPokemon" which negates "ReserveLastPokemon".
end
def has_skill_flag?(flag)

View File

@@ -15,34 +15,34 @@ class Battle::AI::AIBattler
def refresh_battler
old_party_index = @party_index
@battler = @ai.battle.battlers[@index]
@party_index = @battler.pokemonIndex
@party_index = battler.pokemonIndex
if @party_index != old_party_index
# TODO: Start of battle or Pokémon switched/shifted; recalculate roles,
# etc.
end
end
def pokemon; return @battler.pokemon; end
def level; return @battler.level; end
def hp; return @battler.hp; end
def totalhp; return @battler.totalhp; end
def fainted?; return @battler.fainted?; end
def status; return @battler.status; end
def statusCount; return @battler.statusCount; end
def gender; return @battler.gender; end
def turnCount; return @battler.turnCount; end
def effects; return @battler.effects; end
def stages; return @battler.stages; end
def statStageAtMax?(stat); return @battler.statStageAtMax?(stat); end
def statStageAtMin?(stat); return @battler.statStageAtMin?(stat); end
def moves; return @battler.moves; end
def pokemon; return battler.pokemon; end
def level; return battler.level; end
def hp; return battler.hp; end
def totalhp; return battler.totalhp; end
def fainted?; return battler.fainted?; end
def status; return battler.status; end
def statusCount; return battler.statusCount; end
def gender; return battler.gender; end
def turnCount; return battler.turnCount; end
def effects; return battler.effects; end
def stages; return battler.stages; end
def statStageAtMax?(stat); return battler.statStageAtMax?(stat); end
def statStageAtMin?(stat); return battler.statStageAtMin?(stat); end
def moves; return battler.moves; end
def wild?
return @ai.battle.wildBattle? && opposes?
end
def name
return sprintf("%s (%d)", @battler.name, @index)
return sprintf("%s (%d)", battler.name, @index)
end
def opposes?(other = nil)
@@ -50,10 +50,10 @@ class Battle::AI::AIBattler
return other.side != @side
end
def idxOwnSide; return @battler.idxOwnSide; end
def pbOwnSide; return @battler.pbOwnSide; end
def idxOpposingSide; return @battler.idxOpposingSide; end
def pbOpposingSide; return @battler.pbOpposingSide; end
def idxOwnSide; return battler.idxOwnSide; end
def pbOwnSide; return battler.pbOwnSide; end
def idxOpposingSide; return battler.idxOpposingSide; end
def pbOpposingSide; return battler.pbOpposingSide; end
#=============================================================================
@@ -63,44 +63,44 @@ class Battle::AI::AIBattler
# Future Sight/Doom Desire
# TODO
# Wish
if @ai.battle.positions[@index].effects[PBEffects::Wish] == 1 && @battler.canHeal?
if @ai.battle.positions[@index].effects[PBEffects::Wish] == 1 && battler.canHeal?
ret -= @ai.battle.positions[@index].effects[PBEffects::WishAmount]
end
# Sea of Fire
if @ai.battle.sides[@side].effects[PBEffects::SeaOfFire] > 1 &&
@battler.takesIndirectDamage? && !has_type?(:FIRE)
battler.takesIndirectDamage? && !has_type?(:FIRE)
ret += self.totalhp / 8
end
# Grassy Terrain (healing)
if @ai.battle.field.terrain == :Grassy && @battler.affectedByTerrain? && @battler.canHeal?
if @ai.battle.field.terrain == :Grassy && battler.affectedByTerrain? && battler.canHeal?
ret -= [battler.totalhp / 16, 1].max
end
# Leftovers/Black Sludge
if has_active_item?(:BLACKSLUDGE)
if has_type?(:POISON)
ret -= [battler.totalhp / 16, 1].max if @battler.canHeal?
ret -= [battler.totalhp / 16, 1].max if battler.canHeal?
else
ret += [battler.totalhp / 8, 1].max if @battler.takesIndirectDamage?
ret += [battler.totalhp / 8, 1].max if battler.takesIndirectDamage?
end
elsif has_active_item?(:LEFTOVERS)
ret -= [battler.totalhp / 16, 1].max if @battler.canHeal?
ret -= [battler.totalhp / 16, 1].max if battler.canHeal?
end
# Aqua Ring
if self.effects[PBEffects::AquaRing] && @battler.canHeal?
if self.effects[PBEffects::AquaRing] && battler.canHeal?
amt = battler.totalhp / 16
amt = (amt * 1.3).floor if has_active_item?(:BIGROOT)
ret -= [amt, 1].max
end
# Ingrain
if self.effects[PBEffects::Ingrain] && @battler.canHeal?
if self.effects[PBEffects::Ingrain] && battler.canHeal?
amt = battler.totalhp / 16
amt = (amt * 1.3).floor if has_active_item?(:BIGROOT)
ret -= [amt, 1].max
end
# Leech Seed
if self.effects[PBEffects::LeechSeed] >= 0
if @battler.takesIndirectDamage?
ret += [battler.totalhp / 8, 1].max if @battler.takesIndirectDamage?
if battler.takesIndirectDamage?
ret += [battler.totalhp / 8, 1].max if battler.takesIndirectDamage?
end
else
@ai.each_battler do |b, i|
@@ -111,31 +111,33 @@ class Battle::AI::AIBattler
end
end
# Hyper Mode (Shadow Pokémon)
# TODO
if battler.inHyperMode?
ret += [battler.totalhp / 24, 1].max
end
# Poison/burn/Nightmare
if self.status == :POISON
if has_active_ability?(:POISONHEAL)
ret -= [battler.totalhp / 8, 1].max if @battler.canHeal?
elsif @battler.takesIndirectDamage?
ret -= [battler.totalhp / 8, 1].max if battler.canHeal?
elsif battler.takesIndirectDamage?
mult = 2
mult = [self.effects[PBEffects::Toxic] + 1, 16].min if self.statusCount > 0 # Toxic
ret += [mult * battler.totalhp / 16, 1].max
end
elsif self.status == :BURN
if @battler.takesIndirectDamage?
if battler.takesIndirectDamage?
amt = (Settings::MECHANICS_GENERATION >= 7) ? self.totalhp / 16 : self.totalhp / 8
amt = (amt / 2.0).round if has_active_ability?(:HEATPROOF)
ret += [amt, 1].max
end
elsif @battler.asleep? && self.statusCount > 1 && self.effects[PBEffects::Nightmare]
ret += [battler.totalhp / 4, 1].max if @battler.takesIndirectDamage?
elsif battler.asleep? && self.statusCount > 1 && self.effects[PBEffects::Nightmare]
ret += [battler.totalhp / 4, 1].max if battler.takesIndirectDamage?
end
# Curse
if self.effects[PBEffects::Curse]
ret += [battler.totalhp / 4, 1].max if @battler.takesIndirectDamage?
ret += [battler.totalhp / 4, 1].max if battler.takesIndirectDamage?
end
# Trapping damage
if self.effects[PBEffects::Trapping] > 1 && @battler.takesIndirectDamage?
if self.effects[PBEffects::Trapping] > 1 && battler.takesIndirectDamage?
amt = (Settings::MECHANICS_GENERATION >= 6) ? self.totalhp / 8 : self.totalhp / 16
if @ai.battlers[self.effects[PBEffects::TrappingUser]].has_active_item?(:BINDINGBAND)
amt = (Settings::MECHANICS_GENERATION >= 6) ? self.totalhp / 6 : self.totalhp / 8
@@ -143,16 +145,16 @@ class Battle::AI::AIBattler
ret += [amt, 1].max
end
# Perish Song
# TODO
return 999_999 if self.effects[PBEffects::PerishSong] == 1
# Bad Dreams
if @battler.asleep? && self.statusCount > 1 && @battler.takesIndirectDamage?
if battler.asleep? && self.statusCount > 1 && battler.takesIndirectDamage?
@ai.each_battler do |b, i|
next if i == @index || !b.battler.near?(@battler) || !b.has_active_ability?(:BADDREAMS)
next if i == @index || !b.battler.near?(battler) || !b.has_active_ability?(:BADDREAMS)
ret += [battler.totalhp / 8, 1].max
end
end
# Sticky Barb
if has_active_item?(:STICKYBARB) && @battler.takesIndirectDamage?
if has_active_item?(:STICKYBARB) && battler.takesIndirectDamage?
ret += [battler.totalhp / 8, 1].max
end
return ret
@@ -163,22 +165,26 @@ class Battle::AI::AIBattler
def base_stat(stat)
ret = 0
case stat
when :ATTACK then ret = @battler.attack
when :DEFENSE then ret = @battler.defense
when :SPECIAL_ATTACK then ret = @battler.spatk
when :SPECIAL_DEFENSE then ret = @battler.spdef
when :SPEED then ret = @battler.speed
when :ATTACK then ret = battler.attack
when :DEFENSE then ret = battler.defense
when :SPECIAL_ATTACK then ret = battler.spatk
when :SPECIAL_DEFENSE then ret = battler.spdef
when :SPEED then ret = battler.speed
end
return ret
end
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]
stageDiv = [8, 7, 6, 5, 4, 3, 2, 2, 2, 2, 2, 2, 2]
stage = @battler.stages[stat] + 6
return battler.pbSpeed if stat == :SPEED && @ai.trainer.high_skill?
stage_mul = Battle::Battler::STAT_STAGE_MULTIPLIERS
stage_div = Battle::Battler::STAT_STAGE_DIVISORS
if [:ACCURACY, :EVASION].include?(stat)
stage_mul = Battle::Battler::ACC_EVA_STAGE_MULTIPLIERS
stage_div = Battle::Battler::ACC_EVA_STAGE_DIVISORS
end
stage = battler.stages[stat] + Battle::Battler::STAT_STAGE_MAXIMUM
value = base_stat(stat)
return (value.to_f * stageMul[stage] / stageDiv[stage]).floor
return (value.to_f * stage_mul[stage] / stage_div[stage]).floor
end
def faster_than?(other)
@@ -190,8 +196,8 @@ class Battle::AI::AIBattler
#=============================================================================
def types; return @battler.types; end
def pbTypes(withExtraType = false); return @battler.pbTypes(withExtraType); end
def types; return battler.types; end
def pbTypes(withExtraType = false); return battler.pbTypes(withExtraType); end
def has_type?(type)
return false if !type
@@ -201,19 +207,20 @@ class Battle::AI::AIBattler
# TODO: Also make a def effectiveness_of_move_against_battler which calls
# pbCalcTypeModSingle instead of effectiveness_of_type_against_single_battler_type.
# Why?
def effectiveness_of_type_against_battler(type, user = nil)
ret = Effectiveness::NORMAL_EFFECTIVE_MULTIPLIER
return ret if !type
return ret if type == :GROUND && has_type?(:FLYING) && has_active_item?(:IRONBALL)
# Get effectivenesses
if type == :SHADOW
if @battler.shadowPokemon?
if battler.shadowPokemon?
ret = Effectiveness::NOT_VERY_EFFECTIVE_MULTIPLIER
else
ret = Effectiveness::SUPER_EFFECTIVE_MULTIPLIER
end
else
@battler.pbTypes(true).each do |defend_type|
battler.pbTypes(true).each do |defend_type|
ret *= effectiveness_of_type_against_single_battler_type(type, defend_type, user)
end
ret *= 2 if self.effects[PBEffects::TarShot] && type == :FIRE
@@ -223,39 +230,39 @@ class Battle::AI::AIBattler
#=============================================================================
def ability_id; return @battler.ability_id; end
def ability; return @battler.ability; end
def ability_id; return battler.ability_id; end
def ability; return battler.ability; end
def ability_active?
return @battler.abilityActive?
return battler.abilityActive?
end
def has_active_ability?(ability, ignore_fainted = false)
return @battler.hasActiveAbility?(ability, ignore_fainted)
return battler.hasActiveAbility?(ability, ignore_fainted)
end
def has_mold_breaker?
return @ai.move.function == "IgnoreTargetAbility" || @battler.hasMoldBreaker?
return @ai.move.function == "IgnoreTargetAbility" || battler.hasMoldBreaker?
end
#=============================================================================
def item_id; return @battler.item_id; end
def item; return @battler.item; end
def item_id; return battler.item_id; end
def item; return battler.item; end
def item_active?
return @battler.itemActive?
return battler.itemActive?
end
def has_active_item?(item)
return @battler.hasActiveItem?(item)
return battler.hasActiveItem?(item)
end
#=============================================================================
def check_for_move
ret = false
@battler.eachMove do |move|
battler.eachMove do |move|
next if move.pp == 0 && move.total_pp > 0
next unless yield move
ret = true
@@ -266,7 +273,7 @@ class Battle::AI::AIBattler
def has_damaging_move_of_type?(*types)
check_for_move do |m|
return true if m.damagingMove? && types.include?(m.pbCalcType(@battler))
return true if m.damagingMove? && types.include?(m.pbCalcType(battler))
end
return false
end
@@ -303,24 +310,24 @@ class Battle::AI::AIBattler
def can_become_trapped?
return false if fainted?
# Ability/item effects that allow switching no matter what
if ability_active? && Battle::AbilityEffects.triggerCertainSwitching(ability, @battler, @ai.battle)
if ability_active? && Battle::AbilityEffects.triggerCertainSwitching(ability, battler, @ai.battle)
return false
end
if item_active? && Battle::ItemEffects.triggerCertainSwitching(item, @battler, @ai.battle)
if item_active? && Battle::ItemEffects.triggerCertainSwitching(item, battler, @ai.battle)
return false
end
# Other certain switching effects
return false if Settings::MORE_TYPE_EFFECTS && has_type?(:GHOST)
# Other certain trapping effects
return false if @battler.trappedInBattle?
return false if battler.trappedInBattle?
# Trapping abilities/items
ai.each_foe_battler(side) do |b, i|
if b.ability_active? &&
Battle::AbilityEffects.triggerTrappingByTarget(b.ability, @battler, b.battler, @ai.battle)
Battle::AbilityEffects.triggerTrappingByTarget(b.ability, battler, b.battler, @ai.battle)
return false
end
if b.item_active? &&
Battle::ItemEffects.triggerTrappingByTarget(b.item, @battler, b.battler, @ai.battle)
Battle::ItemEffects.triggerTrappingByTarget(b.item, battler, b.battler, @ai.battle)
return false
end
end
@@ -432,9 +439,11 @@ class Battle::AI::AIBattler
return 0 if has_active_ability?(:KLUTZ)
# TODO: Unnerve, other item-negating effects.
ret = BASE_ITEM_RATINGS[item] || 0
# TODO: Add more context-sensitive modifications to the ratings from above.
# Should they be moved into a handler?
case item
when :ADAMANTORB
ret = 0 if !@battler.isSpecies?(:DIALGA) || !has_damaging_move_of_type?(:DRAGON, :STEEL)
ret = 0 if !battler.isSpecies?(:DIALGA) || !has_damaging_move_of_type?(:DRAGON, :STEEL)
when :BLACKBELT, :BLACKGLASSES, :CHARCOAL, :DRAGONFANG, :HARDSTONE, :MAGNET,
:METALCOAT, :MIRACLESEED, :MYSTICWATER, :NEVERMELTICE, :POISONBARB,
:SHARPBEAK, :SILKSCARF, :SILVERPOWDER, :SOFTSAND, :SPELLTAG,
@@ -493,17 +502,17 @@ class Battle::AI::AIBattler
when :CHOICESPECS, :WISEGLASSES
ret = 0 if !check_for_move { |m| m.specialMove?(m.type) }
when :DEEPSEATOOTH
ret = 0 if !@battler.isSpecies?(:CLAMPERL) || !check_for_move { |m| m.specialMove?(m.type) }
ret = 0 if !battler.isSpecies?(:CLAMPERL) || !check_for_move { |m| m.specialMove?(m.type) }
when :GRISEOUSORB
ret = 0 if !@battler.isSpecies?(:GIRATINA) || !has_damaging_move_of_type?(:DRAGON, :GHOST)
ret = 0 if !battler.isSpecies?(:GIRATINA) || !has_damaging_move_of_type?(:DRAGON, :GHOST)
when :IRONBALL
ret = 0 if has_move_with_function?("ThrowUserItemAtTarget")
when :LIGHTBALL
ret = 0 if !@battler.isSpecies?(:PIKACHU) || !check_for_move { |m| m.damagingMove? }
ret = 0 if !battler.isSpecies?(:PIKACHU) || !check_for_move { |m| m.damagingMove? }
when :LUSTROUSORB
ret = 0 if !@battler.isSpecies?(:PALKIA) || !has_damaging_move_of_type?(:DRAGON, :WATER)
ret = 0 if !battler.isSpecies?(:PALKIA) || !has_damaging_move_of_type?(:DRAGON, :WATER)
when :SOULDEW
if !@battler.isSpecies?(:LATIAS) && !@battler.isSpecies?(:LATIOS)
if !battler.isSpecies?(:LATIAS) && !battler.isSpecies?(:LATIOS)
ret = 0
elsif Settings::SOUL_DEW_POWERS_UP_TYPES
ret = 0 if !has_damaging_move_of_type?(:PSYCHIC, :DRAGON)
@@ -511,7 +520,7 @@ class Battle::AI::AIBattler
ret -= 2 if !check_for_move { |m| m.specialMove?(m.type) } # Also boosts SpDef
end
when :THICKCLUB
ret = 0 if (!@battler.isSpecies?(:CUBONE) && !@battler.isSpecies?(:MAROWAK)) ||
ret = 0 if (!battler.isSpecies?(:CUBONE) && !battler.isSpecies?(:MAROWAK)) ||
!check_for_move { |m| m.physicalMove?(m.type) }
end
# Prefer if this battler knows Fling and it will do a lot of damage/have an
@@ -568,18 +577,18 @@ class Battle::AI::AIBattler
ret += (cured_status && status == cured_status) ? 6 : -6
when :PERSIMBERRY
# Confusion cure
ret += (effects[PBEffects::Confusion] > 1) ? 6 : -6
ret += (self.effects[PBEffects::Confusion] > 1) ? 6 : -6
when :LUMBERRY
# Any status/confusion cure
ret += (status != :NONE || effects[PBEffects::Confusion] > 1) ? 6 : -6
ret += (status != :NONE || self.effects[PBEffects::Confusion] > 1) ? 6 : -6
when :MENTALHERB
# Cure mental effects
if effects[PBEffects::Attract] >= 0 ||
effects[PBEffects::Taunt] > 1 ||
effects[PBEffects::Encore] > 1 ||
effects[PBEffects::Torment] ||
effects[PBEffects::Disable] > 1 ||
effects[PBEffects::HealBlock] > 1
if self.effects[PBEffects::Attract] >= 0 ||
self.effects[PBEffects::Taunt] > 1 ||
self.effects[PBEffects::Encore] > 1 ||
self.effects[PBEffects::Torment] ||
self.effects[PBEffects::Disable] > 1 ||
self.effects[PBEffects::HealBlock] > 1
ret += 6
else
ret -= 6
@@ -604,13 +613,13 @@ class Battle::AI::AIBattler
ret = ret * 3 / 2 if GameData::Item.get(item).is_berry? && has_active_ability?(:RIPEN)
when :WHITEHERB
# Resets lowered stats
ret += (@battler.hasLoweredStatStages?) ? 8 : -8
ret += (battler.hasLoweredStatStages?) ? 8 : -8
when :MICLEBERRY
# Raises accuracy of next move
ret += (@ai.stat_raise_worthwhile?(self, :ACCURACY, true)) ? 6 : -6
when :LANSATBERRY
# Focus energy
ret += (effects[PBEffects::FocusEnergy] < 2) ? 6 : -6
ret += (self.effects[PBEffects::FocusEnergy] < 2) ? 6 : -6
when :LEPPABERRY
# Restore PP
ret += 6
@@ -903,54 +912,53 @@ class Battle::AI::AIBattler
# 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
# calculations. Should they all go in this method, or should there be
# more handlers for each ability?
ret = BASE_ABILITY_RATINGS[ability] || 0
# TODO: Add more context-sensitive modifications to the ratings from above.
# Should they be moved into a handler?
case ability
when :BLAZE
return 0 if !has_damaging_move_of_type?(:FIRE)
ret = 0 if !has_damaging_move_of_type?(:FIRE)
when :CUTECHARM, :RIVALRY
return 0 if gender == 2
ret = 0 if gender == 2
when :FRIENDGUARD, :HEALER, :SYMBOISIS, :TELEPATHY
has_ally = false
each_ally(@side) { |b, i| has_ally = true }
return 0 if !has_ally
ret = 0 if !has_ally
when :GALEWINGS
return 0 if !check_for_move { |m| m.type == :FLYING }
ret = 0 if !check_for_move { |m| m.type == :FLYING }
when :HUGEPOWER, :PUREPOWER
return 0 if !ai.stat_raise_worthwhile?(self, :ATTACK, true)
ret = 0 if !ai.stat_raise_worthwhile?(self, :ATTACK, true)
when :IRONFIST
return 0 if !check_for_move { |m| m.punchingMove? }
ret = 0 if !check_for_move { |m| m.punchingMove? }
when :LIQUIDVOICE
return 0 if !check_for_move { |m| m.soundMove? }
ret = 0 if !check_for_move { |m| m.soundMove? }
when :MEGALAUNCHER
return 0 if !check_for_move { |m| m.pulseMove? }
ret = 0 if !check_for_move { |m| m.pulseMove? }
when :OVERGROW
return 0 if !has_damaging_move_of_type?(:GRASS)
ret = 0 if !has_damaging_move_of_type?(:GRASS)
when :PRANKSTER
return 0 if !check_for_move { |m| m.statusMove? }
ret = 0 if !check_for_move { |m| m.statusMove? }
when :PUNKROCK
return 1 if !check_for_move { |m| m.damagingMove? && m.soundMove? }
ret = 1 if !check_for_move { |m| m.damagingMove? && m.soundMove? }
when :RECKLESS
return 0 if !check_for_move { |m| m.recoilMove? }
ret = 0 if !check_for_move { |m| m.recoilMove? }
when :ROCKHEAD
return 0 if !check_for_move { |m| m.recoilMove? && !m.is_a?(Battle::Move::CrashDamageIfFailsUnusableInGravity) }
ret = 0 if !check_for_move { |m| m.recoilMove? && !m.is_a?(Battle::Move::CrashDamageIfFailsUnusableInGravity) }
when :RUNAWAY
return 0 if wild?
ret = 0 if wild?
when :SANDFORCE
return 2 if !has_damaging_move_of_type?(:GROUND, :ROCK, :STEEL)
ret = 2 if !has_damaging_move_of_type?(:GROUND, :ROCK, :STEEL)
when :SKILLLINK
return 0 if !check_for_move { |m| m.is_a?(Battle::Move::HitTwoToFiveTimes) }
ret = 0 if !check_for_move { |m| m.is_a?(Battle::Move::HitTwoToFiveTimes) }
when :STEELWORKER
return 0 if !has_damaging_move_of_type?(:GRASS)
ret = 0 if !has_damaging_move_of_type?(:GRASS)
when :SWARM
return 0 if !has_damaging_move_of_type?(:BUG)
ret = 0 if !has_damaging_move_of_type?(:BUG)
when :TORRENT
return 0 if !has_damaging_move_of_type?(:WATER)
ret = 0 if !has_damaging_move_of_type?(:WATER)
when :TRIAGE
return 0 if !check_for_move { |m| m.healingMove? }
ret = 0 if !check_for_move { |m| m.healingMove? }
end
ret = BASE_ABILITY_RATINGS[ability] || 0
return ret
end
@@ -966,22 +974,22 @@ class Battle::AI::AIBattler
ret = Effectiveness::NORMAL_EFFECTIVE_MULTIPLIER
end
# Foresight
if (user&.has_active_ability?(:SCRAPPY) || @battler.effects[PBEffects::Foresight]) &&
if (user&.has_active_ability?(:SCRAPPY) || self.effects[PBEffects::Foresight]) &&
defend_type == :GHOST
ret = Effectiveness::NORMAL_EFFECTIVE_MULTIPLIER
end
# Miracle Eye
if @battler.effects[PBEffects::MiracleEye] && defend_type == :DARK
if self.effects[PBEffects::MiracleEye] && defend_type == :DARK
ret = Effectiveness::NORMAL_EFFECTIVE_MULTIPLIER
end
elsif Effectiveness.super_effective_type?(type, defend_type)
# Delta Stream's weather
if @battler.effectiveWeather == :StrongWinds && defend_type == :FLYING
if battler.effectiveWeather == :StrongWinds && defend_type == :FLYING
ret = Effectiveness::NORMAL_EFFECTIVE_MULTIPLIER
end
end
# Grounded Flying-type Pokémon become susceptible to Ground moves
if !@battler.airborne? && defend_type == :FLYING && type == :GROUND
if !battler.airborne? && defend_type == :FLYING && type == :GROUND
ret = Effectiveness::NORMAL_EFFECTIVE_MULTIPLIER
end
return ret

View File

@@ -105,20 +105,21 @@ class Battle::AI::AIMove
def rough_damage
base_dmg = base_power
return base_dmg if @move.is_a?(Battle::Move::FixedDamageMove)
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]
max_stage = Battle::Battler::STAT_STAGE_MAXIMUM
stage_mul = Battle::Battler::STAT_STAGE_MULTIPLIERS
stage_div = Battle::Battler::STAT_STAGE_DIVISORS
# Get the user and target of this move
user = @ai.user
user_battler = user.battler
target = @ai.target
target_battler = target.battler
# Get the move's type
calc_type = rough_type
# Decide whether the move will definitely be a critical hit
is_critical = rough_critical_hit_stage >= Battle::Move::CRITICAL_HIT_RATIOS.length
# Decide whether the move has 50% chance of higher of being a critical hit
# TODO: Make this a gradient/probability rather than all-or-nothing?
crit_stage = rough_critical_hit_stage
is_critical = crit_stage >= Battle::Move::CRITICAL_HIT_RATIOS.length ||
Battle::Move::CRITICAL_HIT_RATIOS[crit_stage] <= 2
##### Calculate user's attack stat #####
if ["CategoryDependsOnHigherDamagePoisonTarget",
"CategoryDependsOnHigherDamageIgnoreTargetAbility"].include?(function)
@@ -126,17 +127,15 @@ class Battle::AI::AIMove
end
atk, atk_stage = @move.pbGetAttackStats(user.battler, target.battler)
if !target.has_active_ability?(:UNAWARE) || @ai.battle.moldBreaker
atk_stage = 6 if is_critical && atk_stage < 6
atk_stage = max_stage if is_critical && atk_stage < max_stage
atk = (atk.to_f * stage_mul[atk_stage] / stage_div[atk_stage]).floor
end
##### Calculate target's defense stat #####
defense, def_stage = @move.pbGetDefenseStats(user.battler, target.battler)
if !user.has_active_ability?(:UNAWARE) || @ai.battle.moldBreaker
def_stage = 6 if is_critical && def_stage > 6
def_stage = max_stage if is_critical && def_stage > max_stage
defense = (defense.to_f * stage_mul[def_stage] / stage_div[def_stage]).floor
end
##### Calculate all multiplier effects #####
multipliers = {
:power_multiplier => 1.0,
@@ -154,7 +153,6 @@ class Battle::AI::AIMove
multipliers[:power_multiplier] *= 4 / 3.0
end
end
# Ability effects that alter damage
if user.ability_active?
# NOTE: These abilities aren't suitable for checking at the start of the
@@ -166,7 +164,6 @@ class Battle::AI::AIMove
)
end
end
if !@ai.battle.moldBreaker
user_battler.allAllies.each do |b|
next if !b.abilityActive?
@@ -198,7 +195,6 @@ class Battle::AI::AIMove
)
end
end
# Item effects that alter damage
# NOTE: Type-boosting gems aren't suitable for checking at the start of the
# round.
@@ -218,23 +214,18 @@ class Battle::AI::AIMove
target.item, user_battler, target_battler, @move, multipliers, base_dmg, calc_type
)
end
# Parental Bond
if user.has_active_ability?(:PARENTALBOND)
multipliers[:power_multiplier] *= (Settings::MECHANICS_GENERATION >= 7) ? 1.25 : 1.5
end
# Me First
# TODO
# Helping Hand - n/a
# Charge
if @ai.trainer.medium_skill? &&
user.effects[PBEffects::Charge] > 0 && calc_type == :ELECTRIC
multipliers[:power_multiplier] *= 2
end
# Mud Sport and Water Sport
if @ai.trainer.medium_skill?
if calc_type == :ELECTRIC
@@ -253,7 +244,6 @@ class Battle::AI::AIMove
end
end
end
# Terrain moves
if @ai.trainer.medium_skill?
terrain_multiplier = (Settings::MECHANICS_GENERATION >= 8) ? 1.3 : 1.5
@@ -268,7 +258,6 @@ class Battle::AI::AIMove
multipliers[:power_multiplier] /= 2 if calc_type == :DRAGON && target_battler.affectedByTerrain?
end
end
# Badge multipliers
if @ai.trainer.high_skill? && @ai.battle.internalBattle && target_battler.pbOwnedByPlayer?
# Don't need to check the Atk/Sp Atk-boosting badges because the AI
@@ -279,12 +268,10 @@ class Battle::AI::AIMove
multipliers[:defense_multiplier] *= 1.1
end
end
# Multi-targeting attacks
if @ai.trainer.high_skill? && targets_multiple_battlers?
multipliers[:final_damage_multiplier] *= 0.75
end
# Weather
if @ai.trainer.medium_skill?
case user_battler.effectiveWeather
@@ -309,7 +296,6 @@ class Battle::AI::AIMove
end
end
end
# Critical hits
if is_critical
if Settings::NEW_CRITICAL_HIT_RATE_MECHANICS
@@ -318,9 +304,7 @@ class Battle::AI::AIMove
multipliers[:final_damage_multiplier] *= 2
end
end
# Random variance - n/a
# STAB
if calc_type && user.has_type?(calc_type)
if user.has_active_ability?(:ADAPTABILITY)
@@ -329,17 +313,14 @@ class Battle::AI::AIMove
multipliers[:final_damage_multiplier] *= 1.5
end
end
# Type effectiveness
typemod = target.effectiveness_of_type_against_battler(calc_type, user)
multipliers[:final_damage_multiplier] *= typemod
# Burn
if @ai.trainer.high_skill? && user.status == :BURN && physicalMove?(calc_type) &&
@move.damageReducedByBurn? && !user.has_active_ability?(:GUTS)
multipliers[:final_damage_multiplier] /= 2
end
# Aurora Veil, Reflect, Light Screen
if @ai.trainer.medium_skill? && !@move.ignoresReflect? && !is_critical &&
!user.has_active_ability?(:INFILTRATOR)
@@ -363,18 +344,14 @@ class Battle::AI::AIMove
end
end
end
# Minimize
if @ai.trainer.medium_skill? && target.effects[PBEffects::Minimize] && @move.tramplesMinimize?
multipliers[:final_damage_multiplier] *= 2
end
# Move-specific base damage modifiers
# TODO
# Move-specific final damage modifiers
# TODO
##### Main damage calculation #####
base_dmg = [(base_dmg * multipliers[:power_multiplier]).round, 1].max
atk = [(atk * multipliers[:attack_multiplier]).round, 1].max
@@ -428,10 +405,11 @@ class Battle::AI::AIMove
return 0 if modifiers[:base_accuracy] < 0
return 100 if modifiers[:base_accuracy] == 0
# Calculation
accStage = [[modifiers[:accuracy_stage], -6].max, 6].min + 6
evaStage = [[modifiers[:evasion_stage], -6].max, 6].min + 6
stageMul = [3, 3, 3, 3, 3, 3, 3, 4, 5, 6, 7, 8, 9]
stageDiv = [9, 8, 7, 6, 5, 4, 3, 3, 3, 3, 3, 3, 3]
max_stage = Battle::Battler::STAT_STAGE_MAXIMUM
accStage = [[modifiers[:accuracy_stage], -max_stage].max, max_stage].min + max_stage
evaStage = [[modifiers[:evasion_stage], -max_stage].max, max_stage].min + max_stage
stageMul = Battle::Battler::ACC_EVA_STAGE_MULTIPLIERS
stageDiv = Battle::Battler::ACC_EVA_STAGE_DIVISORS
accuracy = 100.0 * stageMul[accStage] / stageDiv[accStage]
evasion = 100.0 * stageMul[evaStage] / stageDiv[evaStage]
accuracy = (accuracy * modifiers[:accuracy_multiplier]).round

View File

@@ -562,8 +562,9 @@ Battle::AI::Handlers::MoveEffectScore.add("UserMakeSubstitute",
ai.each_foe_battler(user.side) do |b, i|
score += 5 if !b.check_for_move { |m| m.ignoresSubstitute?(b.battler) }
end
# TODO: Predict incoming damage, and prefer if it's greater than
# user.totalhp / 4?
# Prefer if the user lost more than a Substitute's worth of HP from the last
# attack against it
score += 7 if user.battler.lastHPLost >= user.totalhp / 4
next score
}
)

View File

@@ -518,8 +518,6 @@ Battle::AI::Handlers::MoveEffectScore.add("StartRaiseUserAtk1WhenDamaged",
if ai.trainer.has_skill_flag?("HPAware")
next score if user.hp <= user.totalhp / 3
end
# TODO: Check whether any foe has damaging moves that will trigger the stat
# raise?
# Prefer if user benefits from a raised Attack stat
score += 10 if ai.stat_raise_worthwhile?(user, :ATTACK)
score += 7 if user.has_move_with_function?("PowerHigherWithUserPositiveStatStages")

View File

@@ -1,6 +1,5 @@
#===============================================================================
# TODO: Should there be all the "next score" for status moves? Remember that
# other function codes can call this code as part of their scoring.
#
#===============================================================================
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("SleepTarget",
proc { |move, user, target, ai, battle|
@@ -9,15 +8,16 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("SleepTarget",
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("SleepTarget",
proc { |score, move, user, target, ai, battle|
next score if target.effects[PBEffects::Yawn] > 0 # Target is going to fall asleep anyway
useless_score = (move.statusMove?) ? Battle::AI::MOVE_USELESS_SCORE : score
next useless_score if target.effects[PBEffects::Yawn] > 0 # Target is going to fall asleep anyway
# No score modifier if the sleep will be removed immediately
next score if target.has_active_item?([:CHESTOBERRY, :LUMBERRY])
next score if target.faster_than?(user) &&
target.has_active_ability?(:HYDRATION) &&
[:Rain, :HeavyRain].include?(target.battler.effectiveWeather)
next useless_score if target.has_active_item?([:CHESTOBERRY, :LUMBERRY])
next useless_score if target.faster_than?(user) &&
target.has_active_ability?(:HYDRATION) &&
[:Rain, :HeavyRain].include?(target.battler.effectiveWeather)
if target.battler.pbCanSleep?(user.battler, false, move.move)
add_effect = move.get_score_change_for_additional_effect(user, target)
next score if add_effect == -999 # Additional effect will be negated
next useless_score if add_effect == -999 # Additional effect will be negated
score += add_effect
# Inherent preference
score += 15
@@ -88,8 +88,7 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.copy("SleepTarget",
"SleepTargetNextTurn")
#===============================================================================
# TODO: Should there be all the "next score" for status moves? Remember that
# other function codes can call this code as part of their scoring.
#
#===============================================================================
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("PoisonTarget",
proc { |move, user, target, ai, battle|
@@ -98,16 +97,16 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("PoisonTarget",
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("PoisonTarget",
proc { |score, move, user, target, ai, battle|
next score if target.effects[PBEffects::Yawn] > 0 # Target is going to fall asleep
next Battle::AI::MOVE_USELESS_SCORE if move.statusMove? && target.has_active_ability?(:POISONHEAL)
useless_score = (move.statusMove?) ? Battle::AI::MOVE_USELESS_SCORE : score
next useless_score if target.has_active_ability?(:POISONHEAL)
# No score modifier if the poisoning will be removed immediately
next score if target.has_active_item?([:PECHABERRY, :LUMBERRY])
next score if target.faster_than?(user) &&
target.has_active_ability?(:HYDRATION) &&
[:Rain, :HeavyRain].include?(target.battler.effectiveWeather)
next useless_score if target.has_active_item?([:PECHABERRY, :LUMBERRY])
next useless_score if target.faster_than?(user) &&
target.has_active_ability?(:HYDRATION) &&
[:Rain, :HeavyRain].include?(target.battler.effectiveWeather)
if target.battler.pbCanPoison?(user.battler, false, move.move)
add_effect = move.get_score_change_for_additional_effect(user, target)
next score if add_effect == -999 # Additional effect will be negated
next useless_score if add_effect == -999 # Additional effect will be negated
score += add_effect
# Inherent preference
score += 15
@@ -159,10 +158,10 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("PoisonTargetLowerTarget
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("PoisonTargetLowerTargetSpeed1",
proc { |score, move, user, target, ai, battle|
score = Battle::AI::Handlers.apply_move_effect_against_target_score("PoisonTarget",
score, move, user, target, ai, battle)
score = Battle::AI::Handlers.apply_move_effect_against_target_score("LowerTargetSpeed1",
score, move, user, target, ai, battle)
poison_score = Battle::AI::Handlers.apply_move_effect_against_target_score("PoisonTarget",
0, move, user, target, ai, battle)
score += poison_score if poison_score != Battle::AI::MOVE_USELESS_SCORE
score = ai.get_score_for_target_stat_drop(score, target, move.move.statDown, false)
next score
}
)
@@ -176,8 +175,7 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.copy("PoisonTarget",
"BadPoisonTarget")
#===============================================================================
# TODO: Should there be all the "next score" for status moves? Remember that
# other function codes can call this code as part of their scoring.
#
#===============================================================================
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("ParalyzeTarget",
proc { |move, user, target, ai, battle|
@@ -186,15 +184,15 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("ParalyzeTarget",
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("ParalyzeTarget",
proc { |score, move, user, target, ai, battle|
next score if target.effects[PBEffects::Yawn] > 0 # Target is going to fall asleep
useless_score = (move.statusMove?) ? Battle::AI::MOVE_USELESS_SCORE : score
# No score modifier if the paralysis will be removed immediately
next score if target.has_active_item?([:CHERIBERRY, :LUMBERRY])
next score if target.faster_than?(user) &&
target.has_active_ability?(:HYDRATION) &&
[:Rain, :HeavyRain].include?(target.battler.effectiveWeather)
next useless_score if target.has_active_item?([:CHERIBERRY, :LUMBERRY])
next useless_score if target.faster_than?(user) &&
target.has_active_ability?(:HYDRATION) &&
[:Rain, :HeavyRain].include?(target.battler.effectiveWeather)
if target.battler.pbCanParalyze?(user.battler, false, move.move)
add_effect = move.get_score_change_for_additional_effect(user, target)
next score if add_effect == -999 # Additional effect will be negated
next useless_score if add_effect == -999 # Additional effect will be negated
score += add_effect
# Inherent preference (because of the chance of full paralysis)
score += 10
@@ -262,17 +260,22 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.copy("ParalyzeTarget",
#===============================================================================
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("ParalyzeFlinchTarget",
proc { |score, move, user, target, ai, battle|
score = Battle::AI::Handlers.apply_move_effect_against_target_score("ParalyzeTarget",
score, move, user, target, ai, battle)
score = Battle::AI::Handlers.apply_move_effect_against_target_score("FlinchTarget",
score, move, user, target, ai, battle)
paralyze_score = Battle::AI::Handlers.apply_move_effect_against_target_score("ParalyzeTarget",
0, move, user, target, ai, battle)
flinch_score = Battle::AI::Handlers.apply_move_effect_against_target_score("FlinchTarget",
0, move, user, target, ai, battle)
if paralyze_score == Battle::AI::MOVE_USELESS_SCORE &&
flinch_score == Battle::AI::MOVE_USELESS_SCORE
next Battle::AI::MOVE_USELESS_SCORE
end
score += paralyze_score if paralyze_score != Battle::AI::MOVE_USELESS_SCORE
score += flinch_score if flinch_score != Battle::AI::MOVE_USELESS_SCORE
next score
}
)
#===============================================================================
# TODO: Should there be all the "next score" for status moves? Remember that
# other function codes can call this code as part of their scoring.
#
#===============================================================================
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("BurnTarget",
proc { |move, user, target, ai, battle|
@@ -281,15 +284,15 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("BurnTarget",
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("BurnTarget",
proc { |score, move, user, target, ai, battle|
next score if target.effects[PBEffects::Yawn] > 0 # Target is going to fall asleep
useless_score = (move.statusMove?) ? Battle::AI::MOVE_USELESS_SCORE : score
# No score modifier if the burn will be removed immediately
next score if target.has_active_item?([:RAWSTBERRY, :LUMBERRY])
next score if target.faster_than?(user) &&
target.has_active_ability?(:HYDRATION) &&
[:Rain, :HeavyRain].include?(target.battler.effectiveWeather)
next useless_score if target.has_active_item?([:RAWSTBERRY, :LUMBERRY])
next useless_score if target.faster_than?(user) &&
target.has_active_ability?(:HYDRATION) &&
[:Rain, :HeavyRain].include?(target.battler.effectiveWeather)
if target.battler.pbCanBurn?(user.battler, false, move.move)
add_effect = move.get_score_change_for_additional_effect(user, target)
next score if add_effect == -999 # Additional effect will be negated
next useless_score if add_effect == -999 # Additional effect will be negated
score += add_effect
# Inherent preference
score += 15
@@ -339,17 +342,22 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("BurnTarget",
#===============================================================================
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("BurnFlinchTarget",
proc { |score, move, user, target, ai, battle|
score = Battle::AI::Handlers.apply_move_effect_against_target_score("BurnTarget",
score, move, user, target, ai, battle)
score = Battle::AI::Handlers.apply_move_effect_against_target_score("FlinchTarget",
score, move, user, target, ai, battle)
burn_score = Battle::AI::Handlers.apply_move_effect_against_target_score("BurnTarget",
0, move, user, target, ai, battle)
flinch_score = Battle::AI::Handlers.apply_move_effect_against_target_score("FlinchTarget",
0, move, user, target, ai, battle)
if burn_score == Battle::AI::MOVE_USELESS_SCORE &&
flinch_score == Battle::AI::MOVE_USELESS_SCORE
next Battle::AI::MOVE_USELESS_SCORE
end
score += burn_score if burn_score != Battle::AI::MOVE_USELESS_SCORE
score += flinch_score if flinch_score != Battle::AI::MOVE_USELESS_SCORE
next score
}
)
#===============================================================================
# TODO: Should there be all the "next score" for status moves? Remember that
# other function codes can call this code as part of their scoring.
#
#===============================================================================
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("FreezeTarget",
proc { |move, user, target, ai, battle|
@@ -358,15 +366,15 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("FreezeTarget",
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("FreezeTarget",
proc { |score, move, user, target, ai, battle|
next score if target.effects[PBEffects::Yawn] > 0 # Target is going to fall asleep
useless_score = (move.statusMove?) ? Battle::AI::MOVE_USELESS_SCORE : score
# No score modifier if the freeze will be removed immediately
next score if target.has_active_item?([:ASPEARBERRY, :LUMBERRY])
next score if target.faster_than?(user) &&
target.has_active_ability?(:HYDRATION) &&
[:Rain, :HeavyRain].include?(target.battler.effectiveWeather)
next useless_score if target.has_active_item?([:ASPEARBERRY, :LUMBERRY])
next useless_score if target.faster_than?(user) &&
target.has_active_ability?(:HYDRATION) &&
[:Rain, :HeavyRain].include?(target.battler.effectiveWeather)
if target.battler.pbCanFreeze?(user.battler, false, move.move)
add_effect = move.get_score_change_for_additional_effect(user, target)
next score if add_effect == -999 # Additional effect will be negated
next useless_score if add_effect == -999 # Additional effect will be negated
score += add_effect
# Inherent preference
score += 15
@@ -413,10 +421,16 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.copy("FreezeTarget",
#===============================================================================
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("FreezeFlinchTarget",
proc { |score, move, user, target, ai, battle|
score = Battle::AI::Handlers.apply_move_effect_against_target_score("FreezeTarget",
score, move, user, target, ai, battle)
score = Battle::AI::Handlers.apply_move_effect_against_target_score("FlinchTarget",
score, move, user, target, ai, battle)
freeze_score = Battle::AI::Handlers.apply_move_effect_against_target_score("FreezeTarget",
0, move, user, target, ai, battle)
flinch_score = Battle::AI::Handlers.apply_move_effect_against_target_score("FlinchTarget",
0, move, user, target, ai, battle)
if freeze_score == Battle::AI::MOVE_USELESS_SCORE &&
flinch_score == Battle::AI::MOVE_USELESS_SCORE
next Battle::AI::MOVE_USELESS_SCORE
end
score += freeze_score if freeze_score != Battle::AI::MOVE_USELESS_SCORE
score += flinch_score if flinch_score != Battle::AI::MOVE_USELESS_SCORE
next score
}
)
@@ -426,19 +440,17 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("FreezeFlinchTarget",
#===============================================================================
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("ParalyzeBurnOrFreezeTarget",
proc { |score, move, user, target, ai, battle|
next score if target.effects[PBEffects::Yawn] > 0 # Target is going to fall asleep
# No score modifier if the status problem will be removed immediately
next score if target.has_active_item?(:LUMBERRY)
next score if target.faster_than?(user) &&
target.has_active_ability?(:HYDRATION) &&
[:Rain, :HeavyRain].include?(target.battler.effectiveWeather)
# Scores for the possible effects
score += (Battle::AI::Handlers.apply_move_effect_against_target_score("ParalyzeTarget",
Battle::AI::MOVE_BASE_SCORE, move, user, target, ai, battle) - Battle::AI::MOVE_BASE_SCORE) / 3
score += (Battle::AI::Handlers.apply_move_effect_against_target_score("BurnTarget",
Battle::AI::MOVE_BASE_SCORE, move, user, target, ai, battle) - Battle::AI::MOVE_BASE_SCORE) / 3
score += (Battle::AI::Handlers.apply_move_effect_against_target_score("FreezeTarget",
Battle::AI::MOVE_BASE_SCORE, move, user, target, ai, battle) - Battle::AI::MOVE_BASE_SCORE) / 3
["ParalyzeTarget", "BurnTarget", "FreezeTarget"].each do |function_code|
effect_score = Battle::AI::Handlers.apply_move_effect_against_target_score(function_code,
0, move, user, target, ai, battle)
score += effect_score / 3 if effect_score != Battle::AI::MOVE_USELESS_SCORE
end
next score
}
)
@@ -461,22 +473,17 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("GiveUserStatusToTarget",
# Curing the user's status problem
score += 15 if !user.wants_status_problem?(user.status)
# Giving the target a status problem
case user.status
when :SLEEP
next Battle::AI::Handlers.apply_move_effect_against_target_score("SleepTarget",
score, move, user, target, ai, battle)
when :PARALYSIS
next Battle::AI::Handlers.apply_move_effect_against_target_score("ParalyzeTarget",
score, move, user, target, ai, battle)
when :POISON
next Battle::AI::Handlers.apply_move_effect_against_target_score("PoisonTarget",
score, move, user, target, ai, battle)
when :BURN
next Battle::AI::Handlers.apply_move_effect_against_target_score("BurnTarget",
score, move, user, target, ai, battle)
when :FROZEN
next Battle::AI::Handlers.apply_move_effect_against_target_score("FreezeTarget",
function_code = {
:SLEEP => "SleepTarget",
:PARALYSIS => "ParalyzeTarget",
:POISON => "PoisonTarget",
:BURN => "BurnTarget",
:FROZEN => "FreezeTarget"
}[user.status]
if function_code
new_score = Battle::AI::Handlers.apply_move_effect_against_target_score(function_code,
score, move, user, target, ai, battle)
next new_score if new_score != Battle::AI::MOVE_USELESS_SCORE
end
next score
}
@@ -633,7 +640,6 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("ConfuseTarget",
score += 20 * target.hp / target.totalhp
end
# Prefer if the target is paralysed or infatuated, to compound the turn skipping
# TODO: Also prefer if the target is trapped in battle or can't switch out?
score += 8 if target.status == :PARALYSIS || target.effects[PBEffects::Attract] >= 0
# Don't prefer if target benefits from being confused
score -= 15 if target.has_active_ability?(:TANGLEDFEET)
@@ -665,7 +671,6 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("AttractTarget",
# Inherent preference
score += 15
# Prefer if the target is paralysed or confused, to compound the turn skipping
# TODO: Also prefer if the target is trapped in battle or can't switch out?
score += 8 if target.status == :PARALYSIS || target.effects[PBEffects::Confusion] > 1
# Don't prefer if the target can infatuate the user because of this move
score -= 15 if target.has_active_item?(:DESTINYKNOT) &&

View File

@@ -464,9 +464,6 @@ Battle::AI::Handlers::MoveEffectScore.add("EnsureNextCriticalHit",
end
# Prefer if user knows a damaging move which won't definitely critical hit
if user.check_for_move { |m| m.damagingMove? && m.function != "AlwaysCriticalHit"}
# 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.
score += 15
end
next score
@@ -521,9 +518,6 @@ Battle::AI::Handlers::MoveEffectScore.add("StartPreventCriticalHitsAgainstUserSi
crit_stage = 99 if b.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.
score += 8 * crit_stage if crit_stage > 0
score += 10 if b.effects[PBEffects::LaserFocus] > 0
end
@@ -655,10 +649,7 @@ Battle::AI::Handlers::MoveEffectScore.add("StartWeakenPhysicalDamageAgainstUserS
# Doesn't stack with Aurora Veil
next Battle::AI::MOVE_USELESS_SCORE if user.pbOwnSide.effects[PBEffects::AuroraVeil] > 0
# Don't prefer the lower the user's HP is
# TODO: Should this HP check exist? The effect can still be set up for
# allies. Maybe just don't prefer if there are no replacement mons
# left.
if ai.trainer.has_skill_flag?("HPAware")
if ai.trainer.has_skill_flag?("HPAware") && battle.pbAbleNonActiveCount(user.idxOwnSide) == 0
if user.hp <= user.totalhp / 2
score -= (20 * (0.75 - (user.hp.to_f / user.totalhp))).to_i # -5 to -15
end
@@ -686,10 +677,7 @@ Battle::AI::Handlers::MoveEffectScore.add("StartWeakenSpecialDamageAgainstUserSi
# Doesn't stack with Aurora Veil
next Battle::AI::MOVE_USELESS_SCORE if user.pbOwnSide.effects[PBEffects::AuroraVeil] > 0
# Don't prefer the lower the user's HP is
# TODO: Should this HP check exist? The effect can still be set up for
# allies. Maybe just don't prefer if there are no replacement mons
# left.
if ai.trainer.has_skill_flag?("HPAware")
if ai.trainer.has_skill_flag?("HPAware") && battle.pbAbleNonActiveCount(user.idxOwnSide) == 0
if user.hp <= user.totalhp / 2
score -= (20 * (0.75 - (user.hp.to_f / user.totalhp))).to_i # -5 to -15
end
@@ -720,10 +708,7 @@ Battle::AI::Handlers::MoveEffectScore.add("StartWeakenDamageAgainstUserSideIfHai
next Battle::AI::MOVE_USELESS_SCORE if user.pbOwnSide.effects[PBEffects::Reflect] > 0 &&
user.pbOwnSide.effects[PBEffects::LightScreen] > 0
# Don't prefer the lower the user's HP is
# TODO: Should this HP check exist? The effect can still be set up for
# allies. Maybe just don't prefer if there are no replacement mons
# left.
if ai.trainer.has_skill_flag?("HPAware")
if ai.trainer.has_skill_flag?("HPAware") && battle.pbAbleNonActiveCount(user.idxOwnSide) == 0
if user.hp <= user.totalhp / 2
score -= (20 * (0.75 - (user.hp.to_f / user.totalhp))).to_i # -5 to -15
end
@@ -767,8 +752,7 @@ Battle::AI::Handlers::MoveEffectScore.add("ProtectUser",
ai.each_foe_battler(user.side) do |b, i|
next if !b.can_attack?
next if !b.check_for_move { |m| m.canProtectAgainst? }
# TODO: Include b's Unseen Fist somehow? We don't know which move b will
# be using, so we don't know if Unseen Fist will apply.
next if b.has_active_ability?(:UNSEENFIST) && b.check_for_move { |m| m.contactMove? }
useless = false
# General preference
score += 7
@@ -812,8 +796,7 @@ Battle::AI::Handlers::MoveEffectScore.add("ProtectUserBanefulBunker",
ai.each_foe_battler(user.side) do |b, i|
next if !b.can_attack?
next if !b.check_for_move { |m| m.canProtectAgainst? }
# TODO: Include b's Unseen Fist somehow? We don't know which move b will
# be using, so we don't know if Unseen Fist will apply.
next if b.has_active_ability?(:UNSEENFIST) && b.check_for_move { |m| m.contactMove? }
useless = false
# General preference
score += 7
@@ -821,7 +804,9 @@ Battle::AI::Handlers::MoveEffectScore.add("ProtectUserBanefulBunker",
if b.check_for_move { |m| m.contactMove? }
poison_score = Battle::AI::Handlers.apply_move_effect_against_target_score("PoisonTarget",
0, move, user, b, ai, battle)
score += poison_score / 2 # Halved because we don't know what move b will use
if poison_score != Battle::AI::MOVE_USELESS_SCORE
score += poison_score / 2 # Halved because we don't know what move b will use
end
end
# Prefer if the foe is in the middle of using a two turn attack
score += 15 if b.effects[PBEffects::TwoTurnAttack] &&
@@ -852,7 +837,7 @@ Battle::AI::Handlers::MoveEffectScore.add("ProtectUserBanefulBunker",
)
#===============================================================================
# TODO: Special scoring for Aegislash.
#
#===============================================================================
Battle::AI::Handlers::MoveEffectScore.add("ProtectUserFromDamagingMovesKingsShield",
proc { |score, move, user, ai, battle|
@@ -863,8 +848,7 @@ Battle::AI::Handlers::MoveEffectScore.add("ProtectUserFromDamagingMovesKingsShie
ai.each_foe_battler(user.side) do |b, i|
next if !b.can_attack?
next if !b.check_for_move { |m| m.damagingMove? && m.canProtectAgainst? }
# TODO: Include b's Unseen Fist somehow? We don't know which move b will
# be using, so we don't know if Unseen Fist will apply.
next if b.has_active_ability?(:UNSEENFIST) && b.check_for_move { |m| m.contactMove? }
useless = false
# General preference
score += 7
@@ -898,6 +882,9 @@ Battle::AI::Handlers::MoveEffectScore.add("ProtectUserFromDamagingMovesKingsShie
# Don't prefer if the user used a protection move last turn, making this one
# less likely to work
score -= (user.effects[PBEffects::ProtectRate] - 1) * ((Settings::MECHANICS_GENERATION >= 6) ? 15 : 10)
# Aegislash
score += 10 if user.battler.isSpecies?(:AEGISLASH) && user.form == 1 &&
user.ability == :STANCECHANGE
next score
}
)
@@ -914,8 +901,7 @@ Battle::AI::Handlers::MoveEffectScore.add("ProtectUserFromDamagingMovesObstruct"
ai.each_foe_battler(user.side) do |b, i|
next if !b.can_attack?
next if !b.check_for_move { |m| m.damagingMove? && m.canProtectAgainst? }
# TODO: Include b's Unseen Fist somehow? We don't know which move b will
# be using, so we don't know if Unseen Fist will apply.
next if b.has_active_ability?(:UNSEENFIST) && b.check_for_move { |m| m.contactMove? }
useless = false
# General preference
score += 7
@@ -964,8 +950,7 @@ Battle::AI::Handlers::MoveEffectScore.add("ProtectUserFromTargetingMovesSpikyShi
ai.each_foe_battler(user.side) do |b, i|
next if !b.can_attack?
next if !b.check_for_move { |m| m.canProtectAgainst? }
# TODO: Include b's Unseen Fist somehow? We don't know which move b will
# be using, so we don't know if Unseen Fist will apply.
next if b.has_active_ability?(:UNSEENFIST) && b.check_for_move { |m| m.contactMove? }
useless = false
# General preference
score += 7
@@ -1016,8 +1001,7 @@ Battle::AI::Handlers::MoveEffectScore.add("ProtectUserSideFromDamagingMovesIfUse
ai.each_foe_battler(user.side) do |b, i|
next if !b.can_attack?
next if !b.check_for_move { |m| m.damagingMove? && m.canProtectAgainst? }
# TODO: Include b's Unseen Fist somehow? We don't know which move b will
# be using, so we don't know if Unseen Fist will apply.
next if b.has_active_ability?(:UNSEENFIST) && b.check_for_move { |m| m.contactMove? }
useless = false
# General preference
score += 7
@@ -1252,8 +1236,9 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("RecoilThirdOfDamageDealt
score -= 25 * [dmg, user.hp].min / user.hp
end
# Score for paralysing
score = Battle::AI::Handlers.apply_move_effect_against_target_score("ParalyzeTarget",
score, move, user, target, ai, battle)
paralyze_score = Battle::AI::Handlers.apply_move_effect_against_target_score("ParalyzeTarget",
0, move, user, target, ai, battle)
score += paralyze_score if paralyze_score != Battle::AI::MOVE_USELESS_SCORE
next score
}
)
@@ -1274,8 +1259,9 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("RecoilThirdOfDamageDealt
score -= 25 * [dmg, user.hp].min / user.hp
end
# Score for burning
score = Battle::AI::Handlers.apply_move_effect_against_target_score("BurnTarget",
score, move, user, target, ai, battle)
burn_score = Battle::AI::Handlers.apply_move_effect_against_target_score("BurnTarget",
0, move, user, target, ai, battle)
score += burn_score if burn_score != Battle::AI::MOVE_USELESS_SCORE
next score
}
)
@@ -1359,7 +1345,7 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("EnsureNextMoveAlwaysHits
acc = m.pbBaseAccuracy(user.battler, target.battler) if ai.trainer.medium_skill?
score += 5 if acc < 90 && acc != 0
score += 8 if acc <= 50 && acc != 0
# TODO: Prefer more if m is a OHKO move.
score += 8 if m.is_a?(Battle::Move::OHKO)
end
# TODO: Prefer if target has increased evasion.
# Not worth it if the user or the target is at low HP
@@ -1535,10 +1521,7 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TargetMovesBecomeElectri
)
#===============================================================================
# TODO: This could check all other battlers, not just foes. It could check the
# effectivenesses of their Normal and Electric moves on all their foes,
# not just on the user. I think this is overkill, particularly as the
# effect only lasts for one round.
#
#===============================================================================
Battle::AI::Handlers::MoveEffectScore.add("NormalMovesBecomeElectric",
proc { |score, move, user, ai, battle|

View File

@@ -31,8 +31,9 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("HitTwoTimesPoisonTarget"
score = Battle::AI::Handlers.apply_move_effect_against_target_score("HitTwoTimes",
score, move, user, target, ai, battle)
# Score for poisoning
score = Battle::AI::Handlers.apply_move_effect_against_target_score("PoisonTarget",
score, move, user, target, ai, battle)
poison_score = Battle::AI::Handlers.apply_move_effect_against_target_score("PoisonTarget",
0, move, user, target, ai, battle)
score += poison_score if poison_score != Battle::AI::MOVE_USELESS_SCORE
next score
}
)
@@ -279,6 +280,7 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TwoTurnAttackParalyzeTar
# Score for being a two turn attack
score = Battle::AI::Handlers.apply_move_effect_against_target_score("TwoTurnAttack",
score, move, user, target, ai, battle)
next score if score == Battle::AI::MOVE_USELESS_SCORE
# Score for paralysing
score = Battle::AI::Handlers.apply_move_effect_against_target_score("ParalyzeTarget",
score, move, user, target, ai, battle)
@@ -294,6 +296,7 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TwoTurnAttackBurnTarget"
# Score for being a two turn attack
score = Battle::AI::Handlers.apply_move_effect_against_target_score("TwoTurnAttack",
score, move, user, target, ai, battle)
next score if score == Battle::AI::MOVE_USELESS_SCORE
# Score for burning
score = Battle::AI::Handlers.apply_move_effect_against_target_score("BurnTarget",
score, move, user, target, ai, battle)
@@ -309,6 +312,7 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TwoTurnAttackFlinchTarge
# Score for being a two turn attack
score = Battle::AI::Handlers.apply_move_effect_against_target_score("TwoTurnAttack",
score, move, user, target, ai, battle)
next score if score == Battle::AI::MOVE_USELESS_SCORE
# Score for flinching
score = Battle::AI::Handlers.apply_move_effect_against_target_score("FlinchTarget",
score, move, user, target, ai, battle)
@@ -323,11 +327,12 @@ Battle::AI::Handlers::MoveFailureCheck.copy("RaiseUserAtkDef1",
"TwoTurnAttackRaiseUserSpAtkSpDefSpd2")
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TwoTurnAttackRaiseUserSpAtkSpDefSpd2",
proc { |score, move, user, target, ai, battle|
# Score for raising user's stats
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)
next score if score == Battle::AI::MOVE_USELESS_SCORE
# Score for raising user's stats
score = ai.get_score_for_target_stat_raise(score, user, move.move.statUp)
next score
}
)
@@ -340,6 +345,7 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TwoTurnAttackChargeRaise
# Score for being a two turn attack
score = Battle::AI::Handlers.apply_move_effect_against_target_score("TwoTurnAttack",
score, move, user, target, ai, battle)
next score if score == Battle::AI::MOVE_USELESS_SCORE
# Score for raising the user's stat
score = Battle::AI::Handlers.apply_move_effect_score("RaiseUserDefense1",
score, move, user, ai, battle)
@@ -355,6 +361,7 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TwoTurnAttackChargeRaise
# Score for being a two turn attack
score = Battle::AI::Handlers.apply_move_effect_against_target_score("TwoTurnAttack",
score, move, user, target, ai, battle)
next score if score == Battle::AI::MOVE_USELESS_SCORE
# Score for raising the user's stat
score = Battle::AI::Handlers.apply_move_effect_score("RaiseUserSpAtk1",
score, move, user, ai, battle)
@@ -370,6 +377,7 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TwoTurnAttackInvulnerabl
# Score for being a two turn attack
score = Battle::AI::Handlers.apply_move_effect_against_target_score("TwoTurnAttack",
score, move, user, target, ai, battle)
next score if score == Battle::AI::MOVE_USELESS_SCORE
# Score for being semi-invulnerable underground
ai.each_foe_battler(user.side) do |b, i|
if b.check_for_move { |m| m.hitsDiggingTargets? }
@@ -390,6 +398,7 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TwoTurnAttackInvulnerabl
# Score for being a two turn attack
score = Battle::AI::Handlers.apply_move_effect_against_target_score("TwoTurnAttack",
score, move, user, target, ai, battle)
next score if score == Battle::AI::MOVE_USELESS_SCORE
# Score for being semi-invulnerable underwater
ai.each_foe_battler(user.side) do |b, i|
if b.check_for_move { |m| m.hitsDivingTargets? }
@@ -410,6 +419,7 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TwoTurnAttackInvulnerabl
# Score for being a two turn attack
score = Battle::AI::Handlers.apply_move_effect_against_target_score("TwoTurnAttack",
score, move, user, target, ai, battle)
next score if score == Battle::AI::MOVE_USELESS_SCORE
# Score for being semi-invulnerable in the sky
ai.each_foe_battler(user.side) do |b, i|
if b.check_for_move { |m| m.hitsFlyingTargets? }
@@ -430,6 +440,7 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TwoTurnAttackInvulnerabl
# Score for being a two turn attack and semi-invulnerable in the sky
score = Battle::AI::Handlers.apply_move_effect_against_target_score("TwoTurnAttackInvulnerableInSky",
score, move, user, target, ai, battle)
next score if score == Battle::AI::MOVE_USELESS_SCORE
# Score for paralyzing the target
score = Battle::AI::Handlers.apply_move_effect_against_target_score("ParalyzeTarget",
score, move, user, target, ai, battle)
@@ -460,6 +471,7 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TwoTurnAttackInvulnerabl
# Score for being a two turn attack
score = Battle::AI::Handlers.apply_move_effect_against_target_score("TwoTurnAttack",
score, move, user, target, ai, battle)
next score if score == Battle::AI::MOVE_USELESS_SCORE
# Score for being invulnerable
score += 8
# Score for removing protections

View File

@@ -97,11 +97,7 @@ Battle::AI::Handlers::MoveEffectScore.add("HealUserHalfOfTotalHPLoseFlyingTypeTh
score = Battle::AI::Handlers.apply_move_effect_score("HealUserHalfOfTotalHP",
score, move, user, ai, battle)
# User loses the Flying type this round
if user.has_type?(:FLYING)
# TODO: Decide whether losing the Flying type is good or bad. Look at
# type effectiveness changes against the user, and for foes' Ground
# moves (foe foes slower than the user). Anything else?
end
# NOTE: Not worth considering and scoring for.
next score
}
)
@@ -118,7 +114,7 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("CureTargetStatusHealUser
proc { |score, move, user, target, ai, battle|
# Consider how much HP will be restored
score = Battle::AI::Handlers.apply_move_effect_score("HealUserHalfOfTotalHP",
score, move, user, ai, battle)
score, move, user, ai, battle)
# Will cure target's status
score += (target.wants_status_problem?(target.status)) ? 10 : -8
next score

View File

@@ -217,8 +217,9 @@ Battle::AI::Handlers::MoveFailureCheck.add("UserConsumeBerryRaiseDefense2",
Battle::AI::Handlers::MoveEffectScore.add("UserConsumeBerryRaiseDefense2",
proc { |score, move, user, ai, battle|
# Score for raising the user's stat
score = Battle::AI::Handlers.apply_move_effect_score("RaiseUserDefense2",
score, move, user, ai, battle)
stat_raise_score = Battle::AI::Handlers.apply_move_effect_score("RaiseUserDefense2",
0, move, user, ai, battle)
score += stat_raise_score if stat_raise_score != Battle::AI::MOVE_USELESS_SCORE
# Score for the consumed berry's effect
score += user.get_score_change_for_consuming_item(user.item_id, true)
# Score for other results of consuming the berry

View File

@@ -1693,7 +1693,7 @@ Battle::AbilityEffects::OnBeingHit.add(:ANGERPOINT,
next if !target.damageState.critical
next if !target.pbCanRaiseStatStage?(:ATTACK, target)
battle.pbShowAbilitySplash(target)
target.stages[:ATTACK] = 6
target.stages[:ATTACK] = Battle::Battler::STAT_STAGE_MAXIMUM
target.statsRaisedThisRound = true
battle.pbCommonAnimation("StatUp", target)
if Battle::Scene::USE_ABILITY_SPLASH

View File

@@ -162,7 +162,7 @@ MenuHandlers.add(:battle_pokemon_debug_menu, :set_stat_stages, {
break if cmd < 0
if cmd < stat_ids.length # Set a stat
params = ChooseNumberParams.new
params.setRange(-6, 6)
params.setRange(-Battle::Battler::STAT_STAGE_MAXIMUM, Battle::Battler::STAT_STAGE_MAXIMUM)
params.setNegativesAllowed(true)
params.setDefaultValue(battler.stages[stat_ids[cmd]])
value = pbMessageChooseNumber(