From 429068f3cbb46e998268416f1b80fd2ddb7f040c Mon Sep 17 00:00:00 2001 From: Maruno17 Date: Wed, 31 Aug 2022 19:02:54 +0100 Subject: [PATCH] More AI move effect calculation rewrites --- .../001_Battle/011_Battle_EndOfRoundPhase.rb | 1 + .../002_Battler/001_Battle_Battler.rb | 1 + .../002_Battler/002_Battler_Initialize.rb | 43 +-- .../002_Battler/003_Battler_ChangeSelf.rb | 1 + .../011_Battle/003_Move/002_Move_Usage.rb | 3 +- .../003_Move/005_MoveEffects_Misc.rb | 6 +- .../005_AI/051_AI_MoveHandlers_Misc.rb | 266 +++++++++++++----- .../011_Battle/005_AI/102_AIBattler.rb | 9 +- 8 files changed, 239 insertions(+), 91 deletions(-) diff --git a/Data/Scripts/011_Battle/001_Battle/011_Battle_EndOfRoundPhase.rb b/Data/Scripts/011_Battle/001_Battle/011_Battle_EndOfRoundPhase.rb index 6a03f5145..1c0e7e8ef 100644 --- a/Data/Scripts/011_Battle/001_Battle/011_Battle_EndOfRoundPhase.rb +++ b/Data/Scripts/011_Battle/001_Battle/011_Battle_EndOfRoundPhase.rb @@ -747,6 +747,7 @@ class Battle battler.lastHPLostFromFoe = 0 battler.droppedBelowHalfHP = false battler.statsDropped = false + battler.tookMoveDamageThisRound = false battler.tookDamageThisRound = false battler.tookPhysicalHit = false battler.statsRaisedThisRound = false diff --git a/Data/Scripts/011_Battle/002_Battler/001_Battle_Battler.rb b/Data/Scripts/011_Battle/002_Battler/001_Battle_Battler.rb index 09185db96..5cfb3a0ee 100644 --- a/Data/Scripts/011_Battle/002_Battler/001_Battle_Battler.rb +++ b/Data/Scripts/011_Battle/002_Battler/001_Battle_Battler.rb @@ -37,6 +37,7 @@ class Battle::Battler attr_accessor :currentMove # ID of multi-turn move currently being used attr_accessor :droppedBelowHalfHP # Used for Emergency Exit/Wimp Out attr_accessor :statsDropped # Used for Eject Pack + attr_accessor :tookMoveDamageThisRound # Boolean for Focus Punch attr_accessor :tookDamageThisRound # Boolean for whether self took damage this round attr_accessor :tookPhysicalHit attr_accessor :statsRaisedThisRound # Boolean for whether self's stat(s) raised this round diff --git a/Data/Scripts/011_Battle/002_Battler/002_Battler_Initialize.rb b/Data/Scripts/011_Battle/002_Battler/002_Battler_Initialize.rb index 6d31cece1..ddeeb5933 100644 --- a/Data/Scripts/011_Battle/002_Battler/002_Battler_Initialize.rb +++ b/Data/Scripts/011_Battle/002_Battler/002_Battler_Initialize.rb @@ -125,27 +125,28 @@ class Battle::Battler @effects[PBEffects::Substitute] = 0 @effects[PBEffects::Telekinesis] = 0 end - @fainted = (@hp == 0) - @lastAttacker = [] - @lastFoeAttacker = [] - @lastHPLost = 0 - @lastHPLostFromFoe = 0 - @droppedBelowHalfHP = false - @statsDropped = false - @tookDamageThisRound = false - @tookPhysicalHit = false - @statsRaisedThisRound = false - @statsLoweredThisRound = false - @canRestoreIceFace = false - @lastMoveUsed = nil - @lastMoveUsedType = nil - @lastRegularMoveUsed = nil - @lastRegularMoveTarget = -1 - @lastRoundMoved = -1 - @lastMoveFailed = false - @lastRoundMoveFailed = false - @movesUsed = [] - @turnCount = 0 + @fainted = (@hp == 0) + @lastAttacker = [] + @lastFoeAttacker = [] + @lastHPLost = 0 + @lastHPLostFromFoe = 0 + @droppedBelowHalfHP = false + @statsDropped = false + @tookMoveDamageThisRound = false + @tookDamageThisRound = false + @tookPhysicalHit = false + @statsRaisedThisRound = false + @statsLoweredThisRound = false + @canRestoreIceFace = false + @lastMoveUsed = nil + @lastMoveUsedType = nil + @lastRegularMoveUsed = nil + @lastRegularMoveTarget = -1 + @lastRoundMoved = -1 + @lastMoveFailed = false + @lastRoundMoveFailed = false + @movesUsed = [] + @turnCount = 0 @effects[PBEffects::Attract] = -1 @battle.allBattlers.each do |b| # Other battlers no longer attracted to self b.effects[PBEffects::Attract] = -1 if b.effects[PBEffects::Attract] == @index diff --git a/Data/Scripts/011_Battle/002_Battler/003_Battler_ChangeSelf.rb b/Data/Scripts/011_Battle/002_Battler/003_Battler_ChangeSelf.rb index cfaaf60de..c52c81306 100644 --- a/Data/Scripts/011_Battle/002_Battler/003_Battler_ChangeSelf.rb +++ b/Data/Scripts/011_Battle/002_Battler/003_Battler_ChangeSelf.rb @@ -15,6 +15,7 @@ class Battle::Battler if amt > 0 && registerDamage @droppedBelowHalfHP = true if @hp < @totalhp / 2 && @hp + amt >= @totalhp / 2 @tookDamageThisRound = true + @tookMoveDamageThisRound = true end return amt end diff --git a/Data/Scripts/011_Battle/003_Move/002_Move_Usage.rb b/Data/Scripts/011_Battle/003_Move/002_Move_Usage.rb index 62037a3e7..56a501eb3 100644 --- a/Data/Scripts/011_Battle/003_Move/002_Move_Usage.rb +++ b/Data/Scripts/011_Battle/003_Move/002_Move_Usage.rb @@ -383,7 +383,8 @@ class Battle::Move target.effects[PBEffects::BideTarget] = user.index end target.damageState.fainted = true if target.fainted? - target.lastHPLost = damage # For Focus Punch + target.lastHPLost = damage + target.tookMoveDamageThisRound = true if damage > 0 && !target.damageState.substitute # For Focus Punch target.tookDamageThisRound = true if damage > 0 # For Assurance target.lastAttacker.push(user.index) # For Revenge if target.opposes?(user) diff --git a/Data/Scripts/011_Battle/003_Move/005_MoveEffects_Misc.rb b/Data/Scripts/011_Battle/003_Move/005_MoveEffects_Misc.rb index 0641569ed..a002f5bbc 100644 --- a/Data/Scripts/011_Battle/003_Move/005_MoveEffects_Misc.rb +++ b/Data/Scripts/011_Battle/003_Move/005_MoveEffects_Misc.rb @@ -174,11 +174,11 @@ class Battle::Move::FailsIfUserDamagedThisTurn < Battle::Move end def pbDisplayUseMessage(user) - super if !user.effects[PBEffects::FocusPunch] || user.lastHPLost == 0 + super if !user.effects[PBEffects::FocusPunch] || !user.tookMoveDamageThisRound end def pbMoveFailed?(user, targets) - if user.effects[PBEffects::FocusPunch] && user.lastHPLost > 0 + if user.effects[PBEffects::FocusPunch] && user.tookMoveDamageThisRound @battle.pbDisplay(_INTL("{1} lost its focus and couldn't move!", user.pbThis)) return true end @@ -187,7 +187,7 @@ class Battle::Move::FailsIfUserDamagedThisTurn < Battle::Move end #=============================================================================== -# Fails if the target didn't chose a damaging move to use this round, or has +# Fails if the target didn't choose a damaging move to use this round, or has # already moved. (Sucker Punch) #=============================================================================== class Battle::Move::FailsIfTargetActed < Battle::Move diff --git a/Data/Scripts/011_Battle/005_AI/051_AI_MoveHandlers_Misc.rb b/Data/Scripts/011_Battle/005_AI/051_AI_MoveHandlers_Misc.rb index 9b9910ce5..6cd7679f9 100644 --- a/Data/Scripts/011_Battle/005_AI/051_AI_MoveHandlers_Misc.rb +++ b/Data/Scripts/011_Battle/005_AI/051_AI_MoveHandlers_Misc.rb @@ -1,25 +1,24 @@ #=============================================================================== -# TODO: Review score modifiers. +# #=============================================================================== # Struggle #=============================================================================== -# TODO: Review score modifiers. +# #=============================================================================== # None #=============================================================================== -# TODO: Review score modifiers. +# #=============================================================================== Battle::AI::Handlers::MoveEffectScore.add("DoesNothingCongratulations", proc { |score, move, user, target, ai, battle| - next 0 if ai.trainer.high_skill? - next score - 95 + next score - 60 } ) #=============================================================================== -# TODO: Review score modifiers. +# #=============================================================================== Battle::AI::Handlers::MoveFailureCheck.add("DoesNothingFailsIfNoAlly", proc { |move, user, target, ai, battle| @@ -30,33 +29,38 @@ Battle::AI::Handlers::MoveEffectScore.copy("DoesNothingCongratulations", "DoesNothingFailsIfNoAlly") #=============================================================================== -# TODO: Review score modifiers. +# #=============================================================================== Battle::AI::Handlers::MoveEffectScore.copy("DoesNothingCongratulations", "DoesNothingUnusableInGravity") #=============================================================================== -# TODO: Review score modifiers. +# +#=============================================================================== +# AddMoneyGainedFromBattle + +#=============================================================================== +# #=============================================================================== Battle::AI::Handlers::MoveEffectScore.copy("DoesNothingCongratulations", "DoubleMoneyGainedFromBattle") #=============================================================================== -# TODO: Review score modifiers. -#=============================================================================== -# AddMoneyGainedFromBattle - -#=============================================================================== -# TODO: Review score modifiers. +# #=============================================================================== Battle::AI::Handlers::MoveFailureCheck.add("FailsIfNotUserFirstTurn", proc { |move, user, target, ai, battle| next true if user.turnCount > 0 } ) +Battle::AI::Handlers::MoveEffectScore.add("FailsIfNotUserFirstTurn", + proc { |score, move, user, target, ai, battle| + next score + 25 # Use it or lose it + } +) #=============================================================================== -# TODO: Review score modifiers. +# #=============================================================================== Battle::AI::Handlers::MoveFailureCheck.add("FailsIfUserHasUnusedMove", proc { |move, user, target, ai, battle| @@ -74,7 +78,7 @@ Battle::AI::Handlers::MoveFailureCheck.add("FailsIfUserHasUnusedMove", ) #=============================================================================== -# TODO: Review score modifiers. +# #=============================================================================== Battle::AI::Handlers::MoveFailureCheck.add("FailsIfUserNotConsumedBerry", proc { |move, user, target, ai, battle| @@ -83,58 +87,92 @@ Battle::AI::Handlers::MoveFailureCheck.add("FailsIfUserNotConsumedBerry", ) #=============================================================================== -# TODO: Review score modifiers. +# #=============================================================================== Battle::AI::Handlers::MoveFailureCheck.add("FailsIfTargetHasNoItem", proc { |move, user, target, ai, battle| next true if !target.item || !target.item_active? } ) -Battle::AI::Handlers::MoveEffectScore.add("FailsIfTargetHasNoItem", - proc { |score, move, user, target, ai, battle| - next score + 50 if ai.trainer.medium_skill? - } -) #=============================================================================== -# TODO: Review score modifiers. +# #=============================================================================== Battle::AI::Handlers::MoveFailureCheck.add("FailsUnlessTargetSharesTypeWithUser", proc { |move, user, target, ai, battle| - user_types = user.battler.pbTypes(true) - target_types = target.battler.pbTypes(true) + user_types = user.pbTypes(true) + target_types = target.pbTypes(true) next true if (user_types & target_types).empty? } ) #=============================================================================== -# TODO: Review score modifiers. +# #=============================================================================== Battle::AI::Handlers::MoveEffectScore.add("FailsIfUserDamagedThisTurn", proc { |score, move, user, target, ai, battle| - score += 50 if target.effects[PBEffects::HyperBeam] > 0 - score -= 35 if target.hp <= target.totalhp / 2 # If target is weak, no - score -= 70 if target.hp <= target.totalhp / 4 # need to risk this move + # Check whether user is faster than its foe(s) and could use this move + user_faster_count = 0 + foe_faster_count = 0 + ai.battlers.each_with_index do |b, i| + next if !user.opposes?(b) || b.battler.fainted? + if user.faster_than?(b) + user_faster_count += 1 + else + foe_faster_count += 1 + end + end + next score - 40 if user_faster_count == 0 + score += 10 if foe_faster_count == 0 + # Effects that make the target unlikely to act before the user + if ai.trainer.high_skill? + if target.effects[PBEffects::HyperBeam] > 0 || + target.effects[PBEffects::Truant] || + (target.battler.asleep? && target.statusCount > 1) || + target.frozen? + score += 20 + elsif target.effects[PBEffects::Confusion] > 1 || + target.effects[PBEffects::Attract] == user.index + score += 10 + elsif target.paralyzed? + score += 5 + end + end + # Don't risk using this move if target is weak + score -= 10 if target.hp <= target.totalhp / 2 + score -= 10 if target.hp <= target.totalhp / 4 next score } ) #=============================================================================== -# TODO: Review score modifiers. +# #=============================================================================== -# FailsIfTargetActed - -#=============================================================================== -# TODO: Review score modifiers. -#=============================================================================== -Battle::AI::Handlers::MoveEffectScore.add("CrashDamageIfFailsUnusableInGravity", +Battle::AI::Handlers::MoveEffectScore.add("FailsIfTargetActed", proc { |score, move, user, target, ai, battle| - next score + 10 * (user.stages[:ACCURACY] - target.stages[:EVASION]) + # Check whether user is faster than its foe(s) and could use this move + next score - 40 if target.faster_than?(user) + score += 10 + # TODO: Predict the target switching/using an item. + # TODO: Predict the target using a damaging move or Me First. + # Don't risk using this move if target is weak + score -= 10 if target.hp <= target.totalhp / 2 + score -= 10 if target.hp <= target.totalhp / 4 + next score } ) #=============================================================================== -# TODO: Review score modifiers. +# +#=============================================================================== +Battle::AI::Handlers::MoveEffectScore.add("CrashDamageIfFailsUnusableInGravity", + proc { |score, move, user, target, ai, battle| + next score - (100 - move.rough_accuracy) if user.takesIndirectDamage? + } +) + +#=============================================================================== +# #=============================================================================== Battle::AI::Handlers::MoveFailureCheck.add("StartSunWeather", proc { |move, user, target, ai, battle| @@ -144,64 +182,166 @@ Battle::AI::Handlers::MoveFailureCheck.add("StartSunWeather", ) Battle::AI::Handlers::MoveEffectScore.add("StartSunWeather", proc { |score, move, user, target, ai, battle| - if battle.pbCheckGlobalAbility(:AIRLOCK) || - battle.pbCheckGlobalAbility(:CLOUDNINE) - next score - 50 - else - user.battler.eachMove do |m| - next if !m.damagingMove? || m.type != :FIRE - score += 20 + next score - 40 if battle.pbCheckGlobalAbility(:AIRLOCK) || + battle.pbCheckGlobalAbility(:CLOUDNINE) + score += 10 if battle.field.weather != :None # Prefer replacing another weather + score += 15 if user.has_active_item?(:HEATROCK) + score -= 10 if user.hp < user.totalhp / 2 # Not worth it at lower HP + # Check for Fire/Water moves + ai.battlers.each do |b| + next if !b || b.battler.fainted? + if b.check_for_move { |move| move.type == :FIRE && move.damagingMove? } + score += (b.opposes?(user)) ? -15 : 15 + end + if b.check_for_move { |move| move.type == :WATER && move.damagingMove? } + score += (b.opposes?(user)) ? 15 : -15 end - next score end + # TODO: Check for freezing moves. + # Check for abilities/other moves affected by sun + # TODO: Check other battlers for these as well? + if ai.trainer.medium_skill? && !user.has_active_item?(:UTILITYUMBRELLA) + if user.has_active_ability?([:CHLOROPHYLL, :FLOWERGIFT, :FORECAST, :HARVEST, :LEAFGUARD, :SOLARPOWER]) + score += 15 + elsif user.has_active_ability?(:DRYSKIN) + score -= 10 + end + if user.check_for_move { |move| ["HealUserDependingOnWeather", + "RaiseUserAtkSpAtk1Or2InSun", + "TwoTurnAttackOneTurnInSun", + "TypeAndPowerDependOnWeather"].include?(move.function) } + score += 10 + end + if user.check_for_move { |move| ["ConfuseTargetAlwaysHitsInRainHitsTargetInSky", + "ParalyzeTargetAlwaysHitsInRainHitsTargetInSky"].include?(move.function) } + score -= 10 + end + end + next score } ) #=============================================================================== -# TODO: Review score modifiers. +# #=============================================================================== Battle::AI::Handlers::MoveFailureCheck.copy("StartSunWeather", "StartRainWeather") Battle::AI::Handlers::MoveEffectScore.add("StartRainWeather", proc { |score, move, user, target, ai, battle| - if battle.pbCheckGlobalAbility(:AIRLOCK) || - battle.pbCheckGlobalAbility(:CLOUDNINE) - next score - 50 - else - user.battler.eachMove do |m| - next if !m.damagingMove? || m.type != :WATER - score += 20 + next score - 40 if battle.pbCheckGlobalAbility(:AIRLOCK) || + battle.pbCheckGlobalAbility(:CLOUDNINE) + score += 10 if battle.field.weather != :None # Prefer replacing another weather + score += 15 if user.has_active_item?(:DAMPROCK) + score -= 10 if user.hp < user.totalhp / 2 # Not worth it at lower HP + # Check for Fire/Water moves + ai.battlers.each do |b| + next if !b || b.battler.fainted? + if b.check_for_move { |move| move.type == :WATER && move.damagingMove? } + score += (b.opposes?(user)) ? -15 : 15 + end + if b.check_for_move { |move| move.type == :FIRE && move.damagingMove? } + score += (b.opposes?(user)) ? 15 : -15 end - next score end + # Check for abilities/other moves affected by rain + # TODO: Check other battlers for these as well? + if ai.trainer.medium_skill? && !user.has_active_item?(:UTILITYUMBRELLA) + if user.has_active_ability?([:DRYSKIN, :FORECAST, :HYDRATION, :RAINDISH, :SWIFTSWIM]) + score += 15 + end + if user.check_for_move { |move| ["ConfuseTargetAlwaysHitsInRainHitsTargetInSky", + "ParalyzeTargetAlwaysHitsInRainHitsTargetInSky", + "TypeAndPowerDependOnWeather"].include?(move.function) } + score += 10 + end + if user.check_for_move { |move| ["HealUserDependingOnWeather", + "TwoTurnAttackOneTurnInSun"].include?(move.function) } + score -= 10 + end + end + next score } ) #=============================================================================== -# TODO: Review score modifiers. +# #=============================================================================== Battle::AI::Handlers::MoveFailureCheck.copy("StartSunWeather", "StartSandstormWeather") Battle::AI::Handlers::MoveEffectScore.add("StartSandstormWeather", proc { |score, move, user, target, ai, battle| - if battle.pbCheckGlobalAbility(:AIRLOCK) || - battle.pbCheckGlobalAbility(:CLOUDNINE) - next score - 50 + next score - 40 if battle.pbCheckGlobalAbility(:AIRLOCK) || + battle.pbCheckGlobalAbility(:CLOUDNINE) + score += 10 if battle.field.weather != :None # Prefer replacing another weather + score += 15 if user.has_active_item?(:SMOOTHROCK) + score -= 10 if user.hp < user.totalhp / 2 # Not worth it at lower HP + # Check for battlers affected by sandstorm's effects + ai.battlers.each do |b| + next if !b || b.battler.fainted? + if b.battler.takesSandstormDamage? # End of round damage + score += (b.opposes?(user)) ? 15 : -15 + end + if b.has_type?(:ROCK) # +SpDef for Rock types + score += (b.opposes?(user)) ? -15 : 15 + end end + # Check for abilities/moves affected by sandstorm + # TODO: Check other battlers for these as well? + if ai.trainer.medium_skill? && !user.has_active_item?(:UTILITYUMBRELLA) + if user.has_active_ability?([:SANDFORCE, :SANDRUSH, :SANDVEIL]) + score += 15 + end + if user.check_for_move { |move| ["HealUserDependingOnSandstorm", + "TypeAndPowerDependOnWeather"].include?(move.function) } + score += 10 + end + if user.check_for_move { |move| ["HealUserDependingOnWeather", + "TwoTurnAttackOneTurnInSun"].include?(move.function) } + score -= 10 + end + end + next score } ) #=============================================================================== -# TODO: Review score modifiers. +# #=============================================================================== Battle::AI::Handlers::MoveFailureCheck.copy("StartSunWeather", "StartHailWeather") Battle::AI::Handlers::MoveEffectScore.add("StartHailWeather", proc { |score, move, user, target, ai, battle| - if battle.pbCheckGlobalAbility(:AIRLOCK) || - battle.pbCheckGlobalAbility(:CLOUDNINE) - next score - 50 + next score - 40 if battle.pbCheckGlobalAbility(:AIRLOCK) || + battle.pbCheckGlobalAbility(:CLOUDNINE) + score += 10 if battle.field.weather != :None # Prefer replacing another weather + score += 15 if user.has_active_item?(:ICYROCK) + score -= 10 if user.hp < user.totalhp / 2 # Not worth it at lower HP + # Check for battlers affected by hail's effects + ai.battlers.each do |b| + next if !b || b.battler.fainted? + if b.battler.takesHailDamage? # End of round damage + score += (b.opposes?(user)) ? 15 : -15 + end end + # Check for abilities/moves affected by hail + # TODO: Check other battlers for these as well? + if ai.trainer.medium_skill? && !user.has_active_item?(:UTILITYUMBRELLA) + if user.has_active_ability?([:FORECAST, :ICEBODY, :SLUSHRUSH, :SNOWCLOAK]) + score += 15 + elsif user.ability == :ICEFACE + score += 15 + end + if user.check_for_move { |move| ["FreezeTargetAlwaysHitsInHail", + "StartWeakenDamageAgainstUserSideIfHail", + "TypeAndPowerDependOnWeather"].include?(move.function) } + score += 10 + end + if user.check_for_move { |move| ["HealUserDependingOnWeather", + "TwoTurnAttackOneTurnInSun"].include?(move.function) } + score -= 10 + end + end + next score } ) diff --git a/Data/Scripts/011_Battle/005_AI/102_AIBattler.rb b/Data/Scripts/011_Battle/005_AI/102_AIBattler.rb index 03e98e425..65635c062 100644 --- a/Data/Scripts/011_Battle/005_AI/102_AIBattler.rb +++ b/Data/Scripts/011_Battle/005_AI/102_AIBattler.rb @@ -91,9 +91,12 @@ class Battle::AI::AIBattler #============================================================================= def types; return @battler.types; end + def pbTypes(withType3 = false); return @battler.pbTypes(withType3); end def has_type?(type) - return @battler.pbHasType?(type) + return false if !type + active_types = pbTypes(true) + return active_types.include?(GameData::Type.get(type).id) end def effectiveness_of_type_against_battler(type, user = nil) @@ -131,10 +134,10 @@ class Battle::AI::AIBattler return @battler.abilityActive? end - def has_active_ability?(ability, check_mold_breaker = false) + def has_active_ability?(ability, ignore_fainted = false) # Only a high skill AI knows what an opponent's ability is # return false if @ai.trainer.side != @side && !@ai.trainer.high_skill? - return @battler.hasActiveAbility?(ability) + return @battler.hasActiveAbility?(ability, ignore_fainted) end def has_mold_breaker?