AI changes for some multi-hit/multi-turn moves, improved AI's damage calculation

This commit is contained in:
Maruno17
2022-09-18 18:16:08 +01:00
parent eb8fc1d298
commit 63ec037481
4 changed files with 235 additions and 118 deletions

View File

@@ -291,11 +291,22 @@ end
# Special Defense and Speed by 2 stages each in the second turn. (Geomancy)
#===============================================================================
class Battle::Move::TwoTurnAttackRaiseUserSpAtkSpDefSpd2 < Battle::Move::TwoTurnMove
attr_reader :statUp
def initialize(battle, move)
super
@statUp = [:SPECIAL_ATTACK, 2, :SPECIAL_DEFENSE, 2, :SPEED, 2]
end
def pbMoveFailed?(user, targets)
return false if user.effects[PBEffects::TwoTurnAttack] # Charging turn
if !user.pbCanRaiseStatStage?(:SPECIAL_ATTACK, user, self) &&
!user.pbCanRaiseStatStage?(:SPECIAL_DEFENSE, user, self) &&
!user.pbCanRaiseStatStage?(:SPEED, user, self)
failed = true
(@statUp.length / 2).times do |i|
next if !user.pbCanRaiseStatStage?(@statUp[i * 2], user, self)
failed = false
break
end
if failed
@battle.pbDisplay(_INTL("{1}'s stats won't go any higher!", user.pbThis))
return true
end
@@ -309,9 +320,9 @@ class Battle::Move::TwoTurnAttackRaiseUserSpAtkSpDefSpd2 < Battle::Move::TwoTurn
def pbEffectGeneral(user)
return if !@damagingTurn
showAnim = true
[:SPECIAL_ATTACK, :SPECIAL_DEFENSE, :SPEED].each do |s|
next if !user.pbCanRaiseStatStage?(s, user, self)
if user.pbRaiseStatStage(s, 2, user, showAnim)
(@statUp.length / 2).times do |i|
next if !user.pbCanRaiseStatStage?(@statUp[i * 2], user, self)
if user.pbRaiseStatStage(@statUp[i * 2], @statUp[(i * 2) + 1], user, showAnim)
showAnim = false
end
end

View File

@@ -693,7 +693,7 @@ class Battle::Move::UseLastMoveUsed < Battle::Move
"ProtectUser", # Detect, Protect
"ProtectUserSideFromPriorityMoves", # Quick Guard # Not listed on Bulbapedia
"ProtectUserSideFromMultiTargetDamagingMoves", # Wide Guard # Not listed on Bulbapedia
"UserEnduresFaintingThisTurn", # Endure
"UserEnduresFaintingThisTurn", # Endure
"ProtectUserSideFromDamagingMovesIfUserFirstTurn", # Mat Block
"ProtectUserSideFromStatusMoves", # Crafty Shield # Not listed on Bulbapedia
"ProtectUserFromDamagingMovesKingsShield", # King's Shield

View File

@@ -1,11 +1,23 @@
#===============================================================================
# TODO: Review score modifiers.
#
#===============================================================================
Battle::AI::Handlers::MoveBasePower.add("HitTwoTimes",
proc { |power, move, user, target, ai, battle|
next power * move.move.pbNumHits(user.battler, [target.battler])
}
)
Battle::AI::Handlers::MoveEffectScore.add("HitTwoTimes",
proc { |score, move, user, target, ai, battle|
# Prefer if the target has a Substitute and the first hit can break it
if target.effects[PBEffects::Substitute] > 0 && !move.move.ignoresSubstitute?(user.battler)
dmg = move.rough_damage
num_hits = move.move.pbNumHits(user.battler, [target.battler])
score += 10 if target.effects[PBEffects::Substitute] < dmg * (num_hits - 1) / num_hits
end
# TODO: Consider effects that trigger per hit.
next score
}
)
#===============================================================================
# TODO: Review score modifiers.
@@ -14,7 +26,11 @@ Battle::AI::Handlers::MoveBasePower.copy("HitTwoTimes",
"HitTwoTimesPoisonTarget")
Battle::AI::Handlers::MoveEffectScore.add("HitTwoTimesPoisonTarget",
proc { |score, move, user, target, ai, battle|
next 0 if !target.battler.pbCanPoison?(user.battler, false)
# Score for hitting multiple times
score = Battle::AI::Handlers.apply_move_effect_score("HitTwoTimes",
score, move, user, target, ai, battle)
next score if !target.battler.pbCanPoison?(user.battler, false)
# Poisoning
score += 30
if ai.trainer.medium_skill?
score += 30 if target.hp <= target.totalhp / 4
@@ -37,7 +53,19 @@ Battle::AI::Handlers::MoveBasePower.copy("HitTwoTimes",
"HitTwoTimesFlinchTarget")
Battle::AI::Handlers::MoveEffectScore.add("HitTwoTimesFlinchTarget",
proc { |score, move, user, target, ai, battle|
next score + 30 if target.effects[PBEffects::Minimize]
dmg = move.rough_damage
num_hits = move.move.pbNumHits(user.battler, [target.battler])
# Prefer if the target has a Substitute and the first hit can break it
if target.effects[PBEffects::Substitute] > 0 && !move.move.ignoresSubstitute?(user.battler)
score += 10 if target.effects[PBEffects::Substitute] < dmg / num_hits
end
# Flinching
if user.faster_than?(target) && target.effects[PBEffects::Substitute] < dmg / num_hits &&
(battle.moldBreaker || !target.has_active_ability?(:INNERFOCUS))
score += 10
end
# TODO: Consider effects that trigger per hit.
next score
}
)
@@ -51,33 +79,35 @@ Battle::AI::Handlers::MoveBasePower.add("HitTwoTimesTargetThenTargetAlly",
)
#===============================================================================
# TODO: Review score modifiers.
#
#===============================================================================
Battle::AI::Handlers::MoveBasePower.add("HitThreeTimesPowersUpWithEachHit",
proc { |power, move, user, target, ai, battle|
next power * 6 # Hits do x1, x2, x3 ret in turn, for x6 in total
}
)
#===============================================================================
# TODO: Review score modifiers.
#===============================================================================
Battle::AI::Handlers::MoveBasePower.add("HitThreeTimesAlwaysCriticalHit",
proc { |power, move, user, target, ai, battle|
next power * move.move.pbNumHits(user.battler, [target.battler])
}
)
Battle::AI::Handlers::MoveEffectScore.add("HitThreeTimesAlwaysCriticalHit",
Battle::AI::Handlers::MoveEffectScore.add("HitThreeTimesPowersUpWithEachHit",
proc { |score, move, user, target, ai, battle|
if ai.trainer.high_skill?
stat = (move.physicalMove?) ? :DEFENSE : :SPECIAL_DEFENSE
next score + 50 if target.stages[stat] > 1
# Prefer if the target has a Substitute and the first or second hit can break it
if target.effects[PBEffects::Substitute] > 0 && !move.move.ignoresSubstitute?(user.battler)
dmg = move.rough_damage
score += 10 if target.effects[PBEffects::Substitute] < dmg / 2
end
# TODO: Consider effects that trigger per hit.
next score
}
)
#===============================================================================
# TODO: Review score modifiers.
#
#===============================================================================
Battle::AI::Handlers::MoveBasePower.copy("HitTwoTimes",
"HitThreeTimesAlwaysCriticalHit")
Battle::AI::Handlers::MoveEffectScore.copy("HitTwoTimes",
"HitThreeTimesAlwaysCriticalHit")
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveBasePower.add("HitTwoToFiveTimes",
proc { |power, move, user, target, ai, battle|
@@ -85,9 +115,21 @@ Battle::AI::Handlers::MoveBasePower.add("HitTwoToFiveTimes",
next power * 31 / 10 # Average damage dealt
}
)
Battle::AI::Handlers::MoveEffectScore.add("HitTwoToFiveTimes",
proc { |score, move, user, target, ai, battle|
# Prefer if the target has a Substitute and the first hit(s) can break it
if target.effects[PBEffects::Substitute] > 0 && !move.move.ignoresSubstitute?(user.battler)
dmg = move.rough_damage
num_hits = (user.has_active_ability?(:SKILLLINK)) ? 5 : 3 # 3 is about average
score += 10 if target.effects[PBEffects::Substitute] < dmg * (num_hits - 1) / num_hits
end
# TODO: Consider effects that trigger per hit.
next score
}
)
#===============================================================================
# TODO: Review score modifiers.
#
#===============================================================================
Battle::AI::Handlers::MoveBasePower.add("HitTwoToFiveTimesOrThreeForAshGreninja",
proc { |power, move, user, target, ai, battle|
@@ -98,32 +140,32 @@ Battle::AI::Handlers::MoveBasePower.add("HitTwoToFiveTimesOrThreeForAshGreninja"
next power * 31 / 10 # Average damage dealt
}
)
Battle::AI::Handlers::MoveEffectScore.copy("HitTwoToFiveTimes",
"HitTwoToFiveTimesOrThreeForAshGreninja")
#===============================================================================
# TODO: Review score modifiers.
#===============================================================================
Battle::AI::Handlers::MoveBasePower.add("HitTwoToFiveTimesRaiseUserSpd1LowerUserDef1",
proc { |power, move, user, target, ai, battle|
next power * 5 if user.has_active_ability?(:SKILLLINK)
next power * 31 / 10 # Average damage dealt
}
)
Battle::AI::Handlers::MoveBasePower.copy("HitTwoToFiveTimes",
"HitTwoToFiveTimesRaiseUserSpd1LowerUserDef1")
Battle::AI::Handlers::MoveEffectScore.add("HitTwoToFiveTimesRaiseUserSpd1LowerUserDef1",
proc { |score, move, user, target, ai, battle|
# Score for being a multi-hit attack
score = Battle::AI::Handlers.apply_move_effect_score("HitTwoToFiveTimes",
score, move, user, target, ai, battle)
# User's stat changes
aspeed = user.rough_stat(:SPEED)
ospeed = target.rough_stat(:SPEED)
if aspeed > ospeed && aspeed * 2 / 3 < ospeed
score -= 50
elsif aspeed < ospeed && aspeed * 1.5 > ospeed
score += 50
if aspeed < ospeed && aspeed * 1.5 > ospeed
score += 15 # Will become faster than the target
end
score += user.stages[:DEFENSE] * 30
score += user.stages[:DEFENSE] * 10
next score
}
)
#===============================================================================
# TODO: Review score modifiers.
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("HitOncePerUserTeamMember",
proc { |move, user, target, ai, battle|
@@ -139,51 +181,126 @@ Battle::AI::Handlers::MoveFailureCheck.add("HitOncePerUserTeamMember",
Battle::AI::Handlers::MoveBasePower.add("HitOncePerUserTeamMember",
proc { |power, move, user, target, ai, battle|
ret = 0
ai.battle.eachInTeamFromBattlerIndex(user.index) do |pkmn, _i|
ret += 5 + (pkmn.baseStats[:ATTACK] / 10)
battle.eachInTeamFromBattlerIndex(user.index) do |pkmn, _i|
ret += 5 + (pkmn.baseStats[:ATTACK] / 10) if pkmn.able? && pkmn.status == :NONE
end
next ret
}
)
Battle::AI::Handlers::MoveEffectScore.add("HitOncePerUserTeamMember",
proc { |score, move, user, target, ai, battle|
# Prefer if the target has a Substitute and the first hit(s) can break it
if target.effects[PBEffects::Substitute] > 0 && !move.move.ignoresSubstitute?(user.battler)
dmg = move.rough_damage
num_hits = 0
battle.eachInTeamFromBattlerIndex(user.index) do |pkmn, _i|
num_hits += 1 if pkmn.able? && pkmn.status == :NONE
end
score += 10 if target.effects[PBEffects::Substitute] < dmg * (num_hits - 1) / num_hits
end
# TODO: Consider effects that trigger per hit.
next score
}
)
#===============================================================================
# TODO: Review score modifiers.
#
#===============================================================================
# AttackAndSkipNextTurn
Battle::AI::Handlers::MoveEffectScore.add("AttackAndSkipNextTurn",
proc { |score, move, user, target, ai, battle|
# Don't prefer because it uses up two turns
score -= 10 if !user.has_active_ability?(:TRUANT)
# Don't prefer if user is at a high HP (treat this move as a last resort)
score -= 10 if user.hp >= user.totalhp / 2
# TODO: Don't prefer if another of the user's moves could KO the target.
next score
}
)
#===============================================================================
# TODO: Review score modifiers.
#
#===============================================================================
# TwoTurnAttack
Battle::AI::Handlers::MoveEffectScore.add("TwoTurnAttack",
proc { |score, move, user, target, ai, battle|
# Power Herb makes this a 1 turn move, the same as a move with no effect
next score if user.has_active_item?(:POWERHERB)
# Treat as a failure if user has Truant (the charging turn has no effect)
next 25 if user.has_active_ability?(:TRUANT)
# Don't prefer because it uses up two turns
score -= 15
# Don't prefer if user is at a low HP (time is better spent on quicker moves)
score -= 10 if user.hp < user.totalhp / 2
# Don't prefer if target has a protecting move
if ai.trainer.high_skill? && !(user.has_active_ability?(:UNSEENFIST) && move.move.contactMove?)
has_protect_move = false
if move.move.pbTarget(user).num_targets > 1 &&
(Settings::MECHANICS_GENERATION >= 7 || move.damagingMove?)
if target.check_for_move { |m| m.function == "ProtectUserSideFromMultiTargetDamagingMoves" }
has_protect_move = true
end
end
if move.move.canProtectAgainst?
if target.check_for_move { |m| ["ProtectUser",
"ProtectUserFromTargetingMovesSpikyShield",
"ProtectUserBanefulBunker"].include?(m.function) }
has_protect_move = true
end
if move.damagingMove?
# NOTE: Doesn't check for Mat Block because it only works on its
# user's first turn in battle, so it can't be used in response
# to this move charging up.
if target.check_for_move { |m| ["ProtectUserFromDamagingMovesKingsShield",
"ProtectUserFromDamagingMovesObstruct"].include?(m.function) }
has_protect_move = true
end
end
if move.rough_priority > 0
if target.check_for_move { |m| m.function == "ProtectUserSideFromPriorityMoves" }
has_protect_move = true
end
end
end
score -= 15 if has_protect_move
end
next score
}
)
#===============================================================================
# TODO: Review score modifiers.
#
#===============================================================================
Battle::AI::Handlers::MoveBasePower.add("TwoTurnAttackOneTurnInSun",
proc { |power, move, user, target, ai, battle|
next move.move.pbBaseDamageMultiplier(power, user.battler, target.battler)
}
)
Battle::AI::Handlers::MoveEffectScore.add("TwoTurnAttackOneTurnInSun",
proc { |score, move, user, target, ai, battle|
# Sunny weather this a 1 turn move, the same as a move with no effect
next score if [:Sun, :HarshSun].include?(user.battler.effectiveWeather)
# Score for being a two turn attack
next Battle::AI::Handlers.apply_move_effect_score("TwoTurnAttack",
score, move, user, target, ai, battle)
}
)
#===============================================================================
# TODO: Review score modifiers.
#===============================================================================
Battle::AI::Handlers::MoveEffectScore.add("TwoTurnAttackParalyzeTarget",
proc { |score, move, user, target, ai, battle|
if target.battler.pbCanParalyze?(user.battler, false)
score += 30
# Score for being a two turn attack
score = Battle::AI::Handlers.apply_move_effect_score("TwoTurnAttack",
score, move, user, target, ai, battle)
# Paralyze the target
if !target.battler.pbCanParalyze?(user.battler, false)
score += 10
if ai.trainer.medium_skill?
aspeed = user.rough_stat(:SPEED)
ospeed = target.rough_stat(:SPEED)
if aspeed < ospeed
score += 30
elsif aspeed > ospeed
score -= 40
end
score += 10 if !user.faster_than?(target)
end
score -= 40 if target.has_active_ability?([:GUTS, :MARVELSCALE, :QUICKFEET])
elsif ai.trainer.medium_skill?
score -= 90 if move.statusMove?
score -= 15 if target.has_active_ability?([:GUTS, :MARVELSCALE, :QUICKFEET])
else
next 25 if move.statusMove?
end
next score
}
@@ -194,9 +311,16 @@ Battle::AI::Handlers::MoveEffectScore.add("TwoTurnAttackParalyzeTarget",
#===============================================================================
Battle::AI::Handlers::MoveEffectScore.add("TwoTurnAttackBurnTarget",
proc { |score, move, user, target, ai, battle|
next score - 40 if move.statusMove? && !target.battler.pbCanBurn?(user.battler, false)
score += 10
score -= 20 if target.has_active_ability?([:GUTS, :MARVELSCALE, :QUICKFEET, :FLAREBOOST])
# Score for being a two turn attack
score = Battle::AI::Handlers.apply_move_effect_score("TwoTurnAttack",
score, move, user, target, ai, battle)
# Burn the target
if target.battler.pbCanBurn?(user.battler, false)
score += 10
score -= 15 if target.has_active_ability?([:GUTS, :MARVELSCALE, :QUICKFEET, :FLAREBOOST])
else
next 25 if move.statusMove?
end
next score
}
)
@@ -206,47 +330,30 @@ Battle::AI::Handlers::MoveEffectScore.add("TwoTurnAttackBurnTarget",
#===============================================================================
Battle::AI::Handlers::MoveEffectScore.add("TwoTurnAttackFlinchTarget",
proc { |score, move, user, target, ai, battle|
score += 20 if user.effects[PBEffects::FocusEnergy] > 0
score += 20 if (battle.moldBreaker || !target.has_active_ability?(:INNERFOCUS)) &&
target.effects[PBEffects::Substitute] == 0
# Score for being a two turn attack
score = Battle::AI::Handlers.apply_move_effect_score("TwoTurnAttack",
score, move, user, target, ai, battle)
# Flinching
if user.faster_than?(target) && target.effects[PBEffects::Substitute] == 0 &&
(battle.moldBreaker || !target.has_active_ability?(:INNERFOCUS))
score += 10
end
next score
}
)
#===============================================================================
# TODO: Review score modifiers.
# TODO: This code shouldn't make use of target.
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("TwoTurnAttackRaiseUserSpAtkSpDefSpd2",
proc { |move, user, target, ai, battle|
next true if !user.battler.pbCanRaiseStatStage?(:SPECIAL_ATTACK, user.battler, move.move) &&
!user.battler.pbCanRaiseStatStage?(:SPECIAL_DEFENSE, user.battler, move.move) &&
!user.battler.pbCanRaiseStatStage?(:SPEED, user.battler, move.move)
}
)
Battle::AI::Handlers::MoveFailureCheck.copy("RaiseUserAtkDef1",
"TwoTurnAttackRaiseUserSpAtkSpDefSpd2")
Battle::AI::Handlers::MoveEffectScore.add("TwoTurnAttackRaiseUserSpAtkSpDefSpd2",
proc { |score, move, user, target, ai, battle|
score -= user.stages[:SPECIAL_ATTACK] * 10 # Only *10 instead of *20
score -= user.stages[:SPECIAL_DEFENSE] * 10 # because two-turn attack
score -= user.stages[:SPEED] * 10
if ai.trainer.medium_skill?
hasSpecialAttack = false
user.battler.eachMove do |m|
next if !m.specialMove?(m.type)
hasSpecialAttack = true
break
end
if hasSpecialAttack
score += 20
elsif ai.trainer.high_skill?
score -= 90
end
end
if ai.trainer.high_skill?
aspeed = user.rough_stat(:SPEED)
ospeed = target.rough_stat(:SPEED)
score += 30 if aspeed < ospeed && aspeed * 2 > ospeed
end
# Score for raising user's stats
score = ai.get_score_for_user_stat_raise(score)
# Score for being a two turn attack
score = Battle::AI::Handlers.apply_move_effect_score("TwoTurnAttack",
score, move, user, target, ai, battle)
next score
}
)
@@ -256,15 +363,7 @@ Battle::AI::Handlers::MoveEffectScore.add("TwoTurnAttackRaiseUserSpAtkSpDefSpd2"
#===============================================================================
Battle::AI::Handlers::MoveEffectScore.add("TwoTurnAttackChargeRaiseUserDefense1",
proc { |score, move, user, target, ai, battle|
if move.statusMove?
if user.statStageAtMax?(:DEFENSE)
score -= 90
else
score -= user.stages[:DEFENSE] * 20
end
elsif user.stages[:DEFENSE] < 0
score += 20
end
score += 20 if user.stages[:DEFENSE] < 0
next score
}
)

View File

@@ -94,6 +94,8 @@ class Battle::AI::AIMove
def rough_damage
power = base_power
return power 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]
# Get the user and target of this move
user = @ai.user
user_battler = user.battler
@@ -103,24 +105,21 @@ class Battle::AI::AIMove
# 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
##### Calculate user's attack stat #####
atk = user.rough_stat(:ATTACK)
if function == "UseTargetAttackInsteadOfUserAttack" # Foul Play
atk = target.rough_stat(:ATTACK)
elsif function == "UseUserBaseDefenseInsteadOfUserBaseAttack" # Body Press
atk = user.rough_stat(:DEFENSE)
elsif specialMove?(calc_type)
if function == "UseTargetAttackInsteadOfUserAttack" # Foul Play
atk = target.rough_stat(:SPECIAL_ATTACK)
else
atk = user.rough_stat(:SPECIAL_ATTACK)
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 = (atk.to_f * stage_mul[atk_stage] / stage_div[atk_stage]).floor
end
##### Calculate target's defense stat #####
defense = target.rough_stat(:DEFENSE)
if specialMove?(calc_type) && function != "UseTargetDefenseInsteadOfTargetSpDef" # Psyshock
defense = target.rough_stat(:SPECIAL_DEFENSE)
defense, def_stage = pbGetDefenseStats(user, target)
if !user.has_active_ability?(:UNAWARE) || @ai.battle.moldBreaker
def_stage = 6 if is_critical && def_stage > 6
defense = (defense.to_f * stage_mul[def_stage] / stage_div[def_stage]).floor
end
##### Calculate all multiplier effects #####
@@ -297,7 +296,14 @@ class Battle::AI::AIMove
end
end
# Critical hits - n/a
# Critical hits
if is_critical
if Settings::NEW_CRITICAL_HIT_RATE_MECHANICS
multipliers[:final_damage_multiplier] *= 1.5
else
multipliers[:final_damage_multiplier] *= 2
end
end
# Random variance - n/a
@@ -321,7 +327,8 @@ class Battle::AI::AIMove
end
# Aurora Veil, Reflect, Light Screen
if @ai.trainer.medium_skill? && !@move.ignoresReflect? && !user.has_active_ability?(:INFILTRATOR)
if @ai.trainer.medium_skill? && !@move.ignoresReflect? && !is_critical &&
!user.has_active_ability?(:INFILTRATOR)
if target.pbOwnSide.effects[PBEffects::AuroraVeil] > 0
if @ai.battle.pbSideBattlerCount(target_battler) > 1
multipliers[:final_damage_multiplier] *= 2 / 3.0