From eb8fc1d298428f5e1860361d5b5b45fb43b62387 Mon Sep 17 00:00:00 2001 From: Maruno17 Date: Sat, 10 Sep 2022 15:26:38 +0100 Subject: [PATCH] More AI checks and fixes --- .../003_Move/006_MoveEffects_BattlerStats.rb | 19 +- .../{004_AI_Move.rb => 004_AI_ChooseMove.rb} | 36 +-- .../005_AI/008_AI_Move_Utilities.rb | 2 +- .../005_AI/020_AI_MoveEffectScores_Generic.rb | 70 +++-- .../052_AI_MoveHandlers_BattlerStats.rb | 278 +++++++++--------- .../070_AI_MoveHandlers_GeneralModifiers.rb | 52 +++- .../Scripts/999_Main/002_debug battle test.rb | 12 +- 7 files changed, 254 insertions(+), 215 deletions(-) rename Data/Scripts/011_Battle/005_AI/{004_AI_Move.rb => 004_AI_ChooseMove.rb} (92%) diff --git a/Data/Scripts/011_Battle/003_Move/006_MoveEffects_BattlerStats.rb b/Data/Scripts/011_Battle/003_Move/006_MoveEffects_BattlerStats.rb index 09eebfbf4..69c663759 100644 --- a/Data/Scripts/011_Battle/003_Move/006_MoveEffects_BattlerStats.rb +++ b/Data/Scripts/011_Battle/003_Move/006_MoveEffects_BattlerStats.rb @@ -71,15 +71,22 @@ end # (Belly Drum) #=============================================================================== class Battle::Move::MaxUserAttackLoseHalfOfTotalHP < Battle::Move + attr_reader :statUp + def canSnatch?; return true; end + def initialize(battle, move) + super + @statUp = [:ATTACK, 12] + end + def pbMoveFailed?(user, targets) hpLoss = [user.totalhp / 2, 1].max if user.hp <= hpLoss @battle.pbDisplay(_INTL("But it failed!")) return true end - return true if !user.pbCanRaiseStatStage?(:ATTACK, user, self, true) + return true if !user.pbCanRaiseStatStage?(@statUp[0], user, self, true) return false end @@ -87,16 +94,18 @@ class Battle::Move::MaxUserAttackLoseHalfOfTotalHP < Battle::Move hpLoss = [user.totalhp / 2, 1].max user.pbReduceHP(hpLoss, false, false) if user.hasActiveAbility?(:CONTRARY) - user.stages[:ATTACK] = -6 + user.stages[@statUp[0]] = -6 user.statsLoweredThisRound = true user.statsDropped = true @battle.pbCommonAnimation("StatDown", user) - @battle.pbDisplay(_INTL("{1} cut its own HP and minimized its Attack!", user.pbThis)) + @battle.pbDisplay(_INTL("{1} cut its own HP and minimized its {2}!", + user.pbThis, GameData::Stat.get(@statUp[0]).name)) else - user.stages[:ATTACK] = 6 + user.stages[@statUp[0]] = 6 user.statsRaisedThisRound = true @battle.pbCommonAnimation("StatUp", user) - @battle.pbDisplay(_INTL("{1} cut its own HP and maximized its Attack!", user.pbThis)) + @battle.pbDisplay(_INTL("{1} cut its own HP and maximized its {2}!", + user.pbThis, GameData::Stat.get(@statUp[0]).name)) end user.pbItemHPHealCheck end diff --git a/Data/Scripts/011_Battle/005_AI/004_AI_Move.rb b/Data/Scripts/011_Battle/005_AI/004_AI_ChooseMove.rb similarity index 92% rename from Data/Scripts/011_Battle/005_AI/004_AI_Move.rb rename to Data/Scripts/011_Battle/005_AI/004_AI_ChooseMove.rb index 049a78ec4..f1bd2b13b 100644 --- a/Data/Scripts/011_Battle/005_AI/004_AI_Move.rb +++ b/Data/Scripts/011_Battle/005_AI/004_AI_ChooseMove.rb @@ -99,9 +99,6 @@ class Battle::AI @target = (target) ? @battlers[target.index] : @user @target&.refresh_battler @battle.moldBreaker = @user.has_mold_breaker? - # Determine whether user or target is faster, and store that result so it - # doesn't need recalculating - @user_faster = @user.faster_than?(@target) end #============================================================================= @@ -110,7 +107,6 @@ class Battle::AI # TODO: Add skill checks in here for particular calculations? #============================================================================= def pbPredictMoveFailure - return false if !@trainer.has_skill_flag?("PredictMoveFailure") # TODO: Something involving user.usingMultiTurnAttack? (perhaps earlier than # this?). # User is asleep and will not wake up @@ -152,20 +148,22 @@ class Battle::AI #============================================================================= def pbGetMoveScore(move, target = nil) set_up_move_check(move, target) - user_battler = @user.battler - target_battler = @target.battler # Predict whether the move will fail - return 25 if pbPredictMoveFailure + if @trainer.has_skill_flag?("PredictMoveFailure") + return 25 if pbPredictMoveFailure + end # Get the base score for the move - if @move.damagingMove? - # Is also the predicted damage amount as a percentage of target's current HP - score = pbGetDamagingMoveBaseScore - else # Status moves - # Depends on the move's effect - score = pbGetStatusMoveBaseScore - end + score = 100 +# if @move.damagingMove? +# # Is also the predicted damage amount as a percentage of target's current HP +# score = pbGetDamagingMoveBaseScore +# else # Status moves +# # Depends on the move's effect +# score = pbGetStatusMoveBaseScore +# end + # Modify the score according to the move's effect score = Battle::AI::Handlers.apply_move_effect_score(@move.function, score, @move, @user, @target, self, @battle) @@ -200,10 +198,10 @@ class Battle::AI if @trainer.high_skill? && @user.can_switch_lax? badMoves = false if (max_score <= 25 && user_battler.turnCount > 2) || - (max_score <= 50 && user_battler.turnCount > 5) + (max_score <= 60 && user_battler.turnCount > 4) badMoves = true if pbAIRandom(100) < 80 end - if !badMoves && max_score < 50 && user_battler.turnCount >= 1 + if !badMoves && max_score <= 60 && user_battler.turnCount >= 1 badMoves = choices.none? { |c| user_battler.moves[c[0]].damagingMove? } badMoves = false if badMoves && pbAIRandom(100) < 10 end @@ -222,11 +220,7 @@ class Battle::AI if $INTERNAL PBDebug.log("[AI] Move choices for #{user_battler.pbThis(true)} (#{user_battler.index}):") choices.each_with_index do |c, i| - chance = "0" - chance = sprintf("%.1f", 100.0 * c[3] / total_score) if c[3] > 0 - while chance.length < 5 - chance = " " + chance - end + chance = sprintf("%5.1f", (c[3] > 0) ? 100.0 * c[3] / total_score : 0) log_msg = " * #{chance}% chance: #{user_battler.moves[c[0]].name}" log_msg += " (against target #{c[2]})" if c[2] >= 0 log_msg += " = score #{c[1]}" diff --git a/Data/Scripts/011_Battle/005_AI/008_AI_Move_Utilities.rb b/Data/Scripts/011_Battle/005_AI/008_AI_Move_Utilities.rb index 8880a6407..8704e6954 100644 --- a/Data/Scripts/011_Battle/005_AI/008_AI_Move_Utilities.rb +++ b/Data/Scripts/011_Battle/005_AI/008_AI_Move_Utilities.rb @@ -31,7 +31,7 @@ class Battle::AI mod2 = Effectiveness::NORMAL_EFFECTIVE if pkmn.types.length > 1 mod2 = Effectiveness.calculate(pkmn.types[1], target_battler.types[0], target_battler.types[1]) - mod2 = mod2.to_f / Effectivenesss::NORMAL_EFFECTIVE + mod2 = mod2.to_f / Effectiveness::NORMAL_EFFECTIVE end return mod1 * mod2 end diff --git a/Data/Scripts/011_Battle/005_AI/020_AI_MoveEffectScores_Generic.rb b/Data/Scripts/011_Battle/005_AI/020_AI_MoveEffectScores_Generic.rb index 4c7864bea..676f4e30c 100644 --- a/Data/Scripts/011_Battle/005_AI/020_AI_MoveEffectScores_Generic.rb +++ b/Data/Scripts/011_Battle/005_AI/020_AI_MoveEffectScores_Generic.rb @@ -120,14 +120,14 @@ class Battle::AI # Prefer if move is a status move and it's the user's first/second turn if @user.turnCount < 2 && @move.statusMove? - score += total_increment * 5 + score += total_increment * 4 end # Prefer if user is at high HP, don't prefer if user is at low HP if @user.hp >= @user.totalhp * 0.7 - score += 10 * total_increment + score += 4 * total_increment else - score += total_increment * ((100 * @user.hp / @user.totalhp) - 50) / 2 # +10 to -25 per stage + score += total_increment * ((100 * @user.hp / @user.totalhp) - 50) / 4 # +5 to -12 per stage end # Don't prefer if user is about to faint due to EOR damage @@ -234,69 +234,81 @@ class Battle::AI # Make score changes based on the raising of a specific stat. #============================================================================= def get_user_stat_raise_score_one(score, stat, increment) + # 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] + 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] + end + old_stage = @user.stages[stat] + new_stage = old_stage + increment + inc_mult = (stage_mul[new_stage].to_f * stage_div[old_stage]) / (stage_div[new_stage] * stage_mul[old_stage]) + inc_mult -= 1 + # Stat-based score changes case stat when :ATTACK # Modify score depending on current stat stage # More strongly prefer if the user has no special moves - if @user.stages[stat] >= 3 + if old_stage >= 3 score -= 20 else has_special_moves = @user.check_for_move { |m| m.specialMove?(m.type) } inc = (has_special_moves) ? 5 : 10 - score += inc * (3 - @user.stages[stat]) * increment # 5 to 45 - score += 5 * increment if @user.hp == @user.totalhp + score += inc * (3 - old_stage) * inc_mult + score += 5 * inc_mult if @user.hp == @user.totalhp end when :DEFENSE # Modify score depending on current stat stage - if @user.stages[stat] >= 3 + if old_stage >= 3 score -= 20 else - score += 5 * (3 - @user.stages[stat]) * increment # 5 to 45 - score += 5 * increment if @user.hp == @user.totalhp + score += 5 * (3 - old_stage) * inc_mult + score += 5 * inc_mult if @user.hp == @user.totalhp end when :SPECIAL_ATTACK # Modify score depending on current stat stage # More strongly prefer if the user has no physical moves - if @user.stages[stat] >= 3 + if old_stage >= 3 score -= 20 else has_physical_moves = @user.check_for_move { |m| m.physicalMove?(m.type) && m.function != "UseUserBaseDefenseInsteadOfUserBaseAttack" && m.function != "UseTargetAttackInsteadOfUserAttack" } inc = (has_physical_moves) ? 5 : 10 - score += inc * (3 - @user.stages[stat]) * increment # 5 to 45 - score += 5 * increment if @user.hp == @user.totalhp + score += inc * (3 - old_stage) * inc_mult + score += 5 * inc_mult if @user.hp == @user.totalhp end when :SPECIAL_DEFENSE # Modify score depending on current stat stage - if @user.stages[stat] >= 3 + if old_stage >= 3 score -= 20 else - score += 5 * (3 - @user.stages[stat]) * increment # 5 to 45 - score += 5 * increment if @user.hp == @user.totalhp + score += 5 * (3 - old_stage) * inc_mult + score += 5 * inc_mult if @user.hp == @user.totalhp end when :SPEED # Prefer if user is slower than a foe each_foe_battler(@user.side) do |b, i| next if @user.faster_than?(b) - score += 15 * increment + score += 15 * inc_mult break end # Don't prefer if any foe has Gyro Ball each_foe_battler(@user.side) do |b, i| next if !b.check_for_move { |m| m.function == "PowerHigherWithTargetFasterThanUser" } - score -= 10 * increment + score -= 8 * inc_mult end # Don't prefer if user has Speed Boost (will be gaining Speed anyway) score -= 20 if @user.has_active_ability?(:SPEEDBOOST) when :ACCURACY # Modify score depending on current stat stage - if @user.stages[stat] >= 3 + if old_stage >= 3 score -= 20 else min_accuracy = 100 @@ -304,12 +316,10 @@ class Battle::AI next if m.accuracy == 0 || m.is_a?(Battle::Move::OHKO) min_accuracy = m.accuracy if m.accuracy < min_accuracy end - 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] - min_accuracy *= stageMul[@user.stages[stat]] / stageDiv[@user.stages[stat]] + min_accuracy *= stage_mul[old_stage] / stage_div[old_stage] if min_accuracy < 90 - score += 5 * (3 - @user.stages[stat]) * increment # 5 to 45 - score += 5 * increment if @user.hp == @user.totalhp + score += 5 * (3 - old_stage) * inc_mult + score += 5 * inc_mult if @user.hp == @user.totalhp end end @@ -323,26 +333,26 @@ class Battle::AI score += 60 * eor_damage / b.totalhp if eor_damage > 0 end # Modify score depending on current stat stage - if @user.stages[stat] >= 3 + if old_stage >= 3 score -= 20 else - score += 5 * (3 - @user.stages[stat]) * increment # 5 to 45 - score += 5 * increment if @user.hp == @user.totalhp + score += 5 * (3 - old_stage) * inc_mult + score += 5 * inc_mult if @user.hp == @user.totalhp end end # Check impact on moves of gaining stat stages - pos_change = [@user.stages[stat] + increment, increment].min + pos_change = [old_stage + increment, increment].min if pos_change > 0 # Prefer if user has Stored Power if @user.check_for_move { |m| m.function == "PowerHigherWithUserPositiveStatStages" } - score += 10 * pos_change + score += 5 * pos_change end # Don't prefer if any foe has Punishment each_foe_battler(@user.side) do |b, i| next if !b.check_for_move { |m| m.function == "PowerHigherWithTargetPositiveStatStages" } - score -= 10 * pos_change + score -= 5 * pos_change end end @@ -543,7 +553,7 @@ class Battle::AI # TODO: Prefer if user is faster than the target. # TODO: Is 1.3x for RaiseUserAtkDefAcc1 Coil (+Atk, +Def, +acc). - mini_score *= 1.5 if @user_faster + mini_score *= 1.5 if @user.faster_than?(@target) # TODO: Don't prefer if target is a higher level than the user if @target.level > @user.level + 5 mini_score *= 0.6 diff --git a/Data/Scripts/011_Battle/005_AI/052_AI_MoveHandlers_BattlerStats.rb b/Data/Scripts/011_Battle/005_AI/052_AI_MoveHandlers_BattlerStats.rb index bf669a5b1..b96ad17e2 100644 --- a/Data/Scripts/011_Battle/005_AI/052_AI_MoveHandlers_BattlerStats.rb +++ b/Data/Scripts/011_Battle/005_AI/052_AI_MoveHandlers_BattlerStats.rb @@ -22,10 +22,15 @@ Battle::AI::Handlers::MoveEffectScore.copy("RaiseUserAttack1", "RaiseUserAttack2") #=============================================================================== -# TODO: Review score modifiers. +# #=============================================================================== -Battle::AI::Handlers::MoveEffectScore.copy("RaiseUserAttack2", - "RaiseUserAttack2IfTargetFaints") +Battle::AI::Handlers::MoveEffectScore.add("RaiseUserAttack2IfTargetFaints", + proc { |score, move, user, target, ai, battle| + if move.rough_damage >= target.hp * 0.9 + next ai.get_score_for_user_stat_raise(score) + end + } +) #=============================================================================== # @@ -36,13 +41,13 @@ Battle::AI::Handlers::MoveEffectScore.copy("RaiseUserAttack2", "RaiseUserAttack3") #=============================================================================== -# TODO: Review score modifiers. +# #=============================================================================== Battle::AI::Handlers::MoveEffectScore.copy("RaiseUserAttack2IfTargetFaints", "RaiseUserAttack3IfTargetFaints") #=============================================================================== -# TODO: Review score modifiers. +# #=============================================================================== Battle::AI::Handlers::MoveFailureCheck.add("MaxUserAttackLoseHalfOfTotalHP", proc { |move, user, target, ai, battle| @@ -52,20 +57,9 @@ Battle::AI::Handlers::MoveFailureCheck.add("MaxUserAttackLoseHalfOfTotalHP", ) Battle::AI::Handlers::MoveEffectScore.add("MaxUserAttackLoseHalfOfTotalHP", proc { |score, move, user, target, ai, battle| - score += (6 - user.stages[:ATTACK]) * 10 - if ai.trainer.medium_skill? - hasPhysicalAttack = false - user.battler.eachMove do |m| - next if !m.physicalMove?(m.type) - hasPhysicalAttack = true - break - end - if hasPhysicalAttack - score += 40 - elsif ai.trainer.high_skill? - score -= 90 - end - end + score = ai.get_score_for_user_stat_raise(score) + # Don't prefer the lower the user's HP is + score -= 80 * (1 - (@user.hp.to_f / @user.totalhp)) # 0 to -40 next score } ) @@ -190,12 +184,37 @@ Battle::AI::Handlers::MoveEffectScore.copy("RaiseUserSpeed1", "RaiseUserSpeed2") #=============================================================================== -# TODO: Review score modifiers. +# #=============================================================================== Battle::AI::Handlers::MoveFailureCheck.copy("RaiseUserSpeed2", "RaiseUserSpeed2LowerUserWeight") -Battle::AI::Handlers::MoveEffectScore.copy("RaiseUserSpeed2", - "RaiseUserSpeed2LowerUserWeight") +Battle::AI::Handlers::MoveEffectScore.add("RaiseUserSpeed2LowerUserWeight", + proc { |score, move, user, target, ai, battle| + score = ai.get_score_for_user_stat_raise(score) + if ai.trainer.medium_skill? + # TODO: Take into account weight-modifying items/abilities? This "> 1" + # line can probably ignore them, but these moves' powers will change + # because of those modifiers, and the score changes may need to be + # different accordingly. + if user.battler.pokemon.weight - user.effects[PBEffects::WeightChange] > 1 + if user.check_for_move { |m| m.function == "PowerHigherWithUserHeavierThanTarget" } + score -= 10 + end + ai.each_foe_battler(user.side) do |b| + if b.check_for_move { |m| m.function == "PowerHigherWithUserHeavierThanTarget" } + score -= 10 + end + if b.check_for_move { |m| m.function == "PowerHigherWithTargetWeight" } + score += 10 + end + # TODO: Check foes for Sky Drop and whether the user is too heavy for it + # but the weight reduction will make it susceptible. + end + end + end + next score + } +) #=============================================================================== # @@ -246,12 +265,24 @@ Battle::AI::Handlers::MoveEffectScore.copy("RaiseUserEvasion1", "RaiseUserEvasion2") #=============================================================================== -# TODO: Review score modifiers. +# #=============================================================================== Battle::AI::Handlers::MoveFailureCheck.copy("RaiseUserEvasion2", "RaiseUserEvasion2MinimizeUser") -Battle::AI::Handlers::MoveEffectScore.copy("RaiseUserEvasion2", - "RaiseUserEvasion2MinimizeUser") +Battle::AI::Handlers::MoveEffectScore.add("RaiseUserEvasion2MinimizeUser", + proc { |score, move, user, target, ai, battle| + score = ai.get_score_for_user_stat_raise(score) + if ai.trainer.medium_skill? && !user.effects[PBEffects::Minimize] + ai.each_foe_battler(user.side) do |b| + # Moves that do double damage and (in Gen 6+) have perfect accuracy + if b.check_for_move { |m| m.tramplesMinimize? } + score -= (Settings::MECHANICS_GENERATION >= 6) ? 15 : 10 + end + end + end + next score + } +) #=============================================================================== # @@ -262,7 +293,7 @@ Battle::AI::Handlers::MoveEffectScore.copy("RaiseUserEvasion2", "RaiseUserEvasion3") #=============================================================================== -# TODO: Review score modifiers. +# #=============================================================================== Battle::AI::Handlers::MoveFailureCheck.add("RaiseUserCriticalHitRate2", proc { |move, user, target, ai, battle| @@ -271,9 +302,22 @@ Battle::AI::Handlers::MoveFailureCheck.add("RaiseUserCriticalHitRate2", ) Battle::AI::Handlers::MoveEffectScore.add("RaiseUserCriticalHitRate2", proc { |score, move, user, target, ai, battle| - if move.statusMove? || user.effects[PBEffects::FocusEnergy] < 2 - next score + 30 + next score - 40 if !user.check_for_move { |m| m.damagingMove? } + score += 15 + if ai.trainer.medium_skill? + # Other effects that raise the critical hit rate + if user.item_active? + if [:RAZORCLAW, :SCOPELENS].include?(user.item_id) || + (user.item_id == :LUCKYPUNCH && user.battler.isSpecies?(:CHANSEY)) || + ([:LEEK, :STICK].include?(user.item_id) && + (user.battler.isSpecies?(:FARFETCHD) || user.battler.isSpecies?(:SIRFETCHD))) + score += 10 + end + end + # Critical hits do more damage + score += 10 if user.has_active_ability?(:SNIPER) end + next score } ) @@ -1438,44 +1482,31 @@ Battle::AI::Handlers::MoveEffectScore.add("RaiseGrassBattlersDef1", #=============================================================================== Battle::AI::Handlers::MoveEffectScore.add("UserTargetSwapAtkSpAtkStages", proc { |score, move, user, target, ai, battle| - if ai.trainer.medium_skill? - aatk = user.stages[:ATTACK] - aspa = user.stages[:SPECIAL_ATTACK] - oatk = target.stages[:ATTACK] - ospa = target.stages[:SPECIAL_ATTACK] - if aatk >= oatk && aspa >= ospa - score -= 80 - else - score += (oatk - aatk) * 10 - score += (ospa - aspa) * 10 - end - else - score -= 50 - end + user_attack = user.stages[:ATTACK] + user_spatk = user.stages[:SPECIAL_ATTACK] + target_attack = target.stages[:ATTACK] + target_spatk = target.stages[:SPECIAL_ATTACK] + next score - 40 if user_attack >= target_attack && user_spatk >= target_spatk + next score - 20 if user_attack + user_spatk <= target_attack + target_spatk + score += (target_attack - user_attack) * 10 + score += (target_spatk - user_spatk) * 10 next score } ) #=============================================================================== # TODO: Review score modifiers. -# TODO: Review score modifiers. #=============================================================================== Battle::AI::Handlers::MoveEffectScore.add("UserTargetSwapDefSpDefStages", proc { |score, move, user, target, ai, battle| - if ai.trainer.medium_skill? - adef = user.stages[:DEFENSE] - aspd = user.stages[:SPECIAL_DEFENSE] - odef = target.stages[:DEFENSE] - ospd = target.stages[:SPECIAL_DEFENSE] - if adef >= odef && aspd >= ospd - score -= 80 - else - score += (odef - adef) * 10 - score += (ospd - aspd) * 10 - end - else - score -= 50 - end + user_def = user.stages[:DEFENSE] + user_spdef = user.stages[:SPECIAL_DEFENSE] + target_def = target.stages[:DEFENSE] + target_spdef = target.stages[:SPECIAL_DEFENSE] + next score - 40 if user_def >= target_def && user_spdef >= target_spdef + next score - 20 if user_def + user_spdef <= target_def + target_spdef + score += (target_def - user_def) * 10 + score += (target_spdef - user_spdef) * 10 next score } ) @@ -1485,17 +1516,16 @@ Battle::AI::Handlers::MoveEffectScore.add("UserTargetSwapDefSpDefStages", #=============================================================================== Battle::AI::Handlers::MoveEffectScore.add("UserTargetSwapStatStages", proc { |score, move, user, target, ai, battle| - if ai.trainer.medium_skill? - userStages = 0 - targetStages = 0 - GameData::Stat.each_battle do |s| - userStages += user.stages[s.id] - targetStages += target.stages[s.id] - end - score += (targetStages - userStages) * 10 - else - score -= 50 + user_stages = 0 + target_stages = 0 + target_stage_better = false + GameData::Stat.each_battle do |s| + user_stages += user.stages[s.id] + target_stages += target.stages[s.id] + target_stage_better = true if target.stages[s.id] > user.stages[s.id] end + next score - 40 if !target_stage_better + score += (target_stages - user_stages) * 10 next score } ) @@ -1505,17 +1535,13 @@ Battle::AI::Handlers::MoveEffectScore.add("UserTargetSwapStatStages", #=============================================================================== Battle::AI::Handlers::MoveEffectScore.add("UserCopyTargetStatStages", proc { |score, move, user, target, ai, battle| - if ai.trainer.medium_skill? - equal = true - GameData::Stat.each_battle do |s| - stagediff = target.stages[s.id] - user.stages[s.id] - score += stagediff * 10 - equal = false if stagediff != 0 - end - score -= 80 if equal - else - score -= 50 + equal = true + GameData::Stat.each_battle do |s| + stagediff = target.stages[s.id] - user.stages[s.id] + score += stagediff * 10 + equal = false if stagediff != 0 end + next 60 if equal # No stat changes next score } ) @@ -1584,19 +1610,17 @@ Battle::AI::Handlers::MoveFailureCheck.add("ResetAllBattlersStatStages", ) Battle::AI::Handlers::MoveEffectScore.add("ResetAllBattlersStatStages", proc { |score, move, user, target, ai, battle| - if ai.trainer.medium_skill? - stages = 0 - battle.allBattlers.each do |b| - totalStages = 0 - GameData::Stat.each_battle { |s| totalStages += b.stages[s.id] } - if b.opposes?(user.battler) - stages += totalStages - else - stages -= totalStages - end + stages = 0 + battle.allBattlers.each do |b| + totalStages = 0 + GameData::Stat.each_battle { |s| totalStages += b.stages[s.id] } + if b.opposes?(user.battler) + stages += totalStages + else + stages -= totalStages end - next score + stages * 10 end + next score + stages * 10 } ) @@ -1614,19 +1638,13 @@ Battle::AI::Handlers::MoveFailureCheck.add("StartUserSideImmunityToStatStageLowe #=============================================================================== Battle::AI::Handlers::MoveEffectScore.add("UserSwapBaseAtkDef", proc { |score, move, user, target, ai, battle| - if ai.trainer.medium_skill? - aatk = user.rough_stat(:ATTACK) - adef = user.rough_stat(:DEFENSE) - if aatk == adef || - user.effects[PBEffects::PowerTrick] # No flip-flopping - score -= 90 - elsif adef > aatk # Prefer a higher Attack - score += 30 - else - score -= 30 - end + aatk = user.rough_stat(:ATTACK) + adef = user.rough_stat(:DEFENSE) + next score - 40 if aatk == adef || user.effects[PBEffects::PowerTrick] # No flip-flopping + if adef > aatk # Prefer a higher Attack + score += 20 else - score -= 30 + score -= 20 end next score } @@ -1637,12 +1655,10 @@ Battle::AI::Handlers::MoveEffectScore.add("UserSwapBaseAtkDef", #=============================================================================== Battle::AI::Handlers::MoveEffectScore.add("UserTargetSwapBaseSpeed", proc { |score, move, user, target, ai, battle| - if ai.trainer.medium_skill? - if user.speed > target.speed - score += 50 - else - score -= 70 - end + if user.speed > target.speed + score += 25 + else + score -= 25 end next score } @@ -1653,20 +1669,15 @@ Battle::AI::Handlers::MoveEffectScore.add("UserTargetSwapBaseSpeed", #=============================================================================== Battle::AI::Handlers::MoveEffectScore.add("UserTargetAverageBaseAtkSpAtk", proc { |score, move, user, target, ai, battle| - if ai.trainer.medium_skill? - aatk = user.rough_stat(:ATTACK) - aspatk = user.rough_stat(:SPECIAL_ATTACK) - oatk = target.rough_stat(:ATTACK) - ospatk = target.rough_stat(:SPECIAL_ATTACK) - if aatk < oatk && aspatk < ospatk - score += 50 - elsif aatk + aspatk < oatk + ospatk - score += 30 - else - score -= 50 - end + user_atk = user.battler.attack + user_spatk = user.battler.spatk + target_atk = target.battler.attack + target_spatk = target.battler.spatk + next score - 40 if user_atk > target_atk && user_spatk > target_spatk + if user_atk + user_spatk < target_atk + target_spatk + score += 20 else - score -= 30 + score -= 20 end next score } @@ -1677,20 +1688,15 @@ Battle::AI::Handlers::MoveEffectScore.add("UserTargetAverageBaseAtkSpAtk", #=============================================================================== Battle::AI::Handlers::MoveEffectScore.add("UserTargetAverageBaseDefSpDef", proc { |score, move, user, target, ai, battle| - if ai.trainer.medium_skill? - adef = user.rough_stat(:DEFENSE) - aspdef = user.rough_stat(:SPECIAL_DEFENSE) - odef = target.rough_stat(:DEFENSE) - ospdef = target.rough_stat(:SPECIAL_DEFENSE) - if adef < odef && aspdef < ospdef - score += 50 - elsif adef + aspdef < odef + ospdef - score += 30 - else - score -= 50 - end + user_def = user.rough_stat(:DEFENSE) + user_spdef = user.rough_stat(:SPECIAL_DEFENSE) + target_def = target.rough_stat(:DEFENSE) + target_spdef = target.rough_stat(:SPECIAL_DEFENSE) + next score - 40 if user_def > target_def && user_spdef > target_spdef + if user_def + user_spdef < target_def + target_spdef + score += 20 else - score -= 30 + score -= 20 end next score } @@ -1701,12 +1707,10 @@ Battle::AI::Handlers::MoveEffectScore.add("UserTargetAverageBaseDefSpDef", #=============================================================================== Battle::AI::Handlers::MoveEffectScore.add("UserTargetAverageHP", proc { |score, move, user, target, ai, battle| - if target.effects[PBEffects::Substitute] > 0 - score -= 90 - elsif user.hp >= (user.hp + target.hp) / 2 - score -= 90 + if user.hp >= (user.hp + target.hp) / 2 + score -= 25 else - score += 40 + score += 25 end next score } diff --git a/Data/Scripts/011_Battle/005_AI/070_AI_MoveHandlers_GeneralModifiers.rb b/Data/Scripts/011_Battle/005_AI/070_AI_MoveHandlers_GeneralModifiers.rb index c29717c49..2f41d24df 100644 --- a/Data/Scripts/011_Battle/005_AI/070_AI_MoveHandlers_GeneralModifiers.rb +++ b/Data/Scripts/011_Battle/005_AI/070_AI_MoveHandlers_GeneralModifiers.rb @@ -56,11 +56,25 @@ Battle::AI::Handlers::GeneralMoveScore.add(:dance_move_against_dancer, # lowered offences (Atk/Def or SpAtk/SpDef, whichever is relevant). #=============================================================================== +# Don't prefer damaging moves that will knock out the target if they are using +# Destiny Bond. # TODO: Review score modifier. -#=============================================================================== -# TODO: Don't prefer damaging moves if target is Destiny Bonding. # => 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::GeneralMoveScore.add(:avoid_knocking_out_destiny_bonder, + proc { |score, move, user, target, ai, battle| + if ai.trainer.medium_skill? && move.damagingMove? && + target && target.effects[PBEffects::DestinyBond] + dmg = move.rough_damage + if dmg > target.hp * 1.05 # Predicted to KO the target + score -= 25 + score -= 20 if battle.pbAbleNonActiveCount(user.idxOwnSide) == 0 + end + next score + end + } +) #=============================================================================== # TODO: Review score modifier. @@ -88,24 +102,26 @@ Battle::AI::Handlers::GeneralMoveScore.add(:dance_move_against_dancer, # an effect that's good for the user (Poison Touch/Pickpocket). #=============================================================================== +# 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. #=============================================================================== -# TODO: Don't prefer a status move if user has a damaging move that will KO -# the target. -# => 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. - -#=============================================================================== -# Prefer damaging moves if AI has no more Pokémon or AI is less clever. -# TODO: Review score modifier. -#=============================================================================== -Battle::AI::Handlers::GeneralMoveScore.add(:damaging_moves_if_last_pokemon, +Battle::AI::Handlers::GeneralMoveScore.add(:prefer_damaging_moves_if_last_pokemon, proc { |score, move, user, target, ai, battle| - if ai.trainer.medium_skill? && battle.pbAbleNonActiveCount(user.idxOwnSide) == 0 && - !(ai.trainer.high_skill? && target && battle.pbAbleNonActiveCount(target.idxOwnSide) > 0) - next score * 0.9 if move.statusMove? - next score * 1.1 if target && target.battler.hp <= target.battler.totalhp / 2 + if ai.trainer.medium_skill? && move.damagingMove? + reserves = battle.pbAbleNonActiveCount(user.idxOwnSide) + foes = battle.pbAbleNonActiveCount(user.idxOpposingSide) + # Don't mess with scores just because a move is damaging; need to play well + next score if ai.trainer.high_skill? && foes > reserves # AI is outnumbered + # Prefer damaging moves depending on remaining Pokémon + if foes == 0 # Foe is down to their last Pokémon + score *= 1.1 # => Go for the kill + elsif reserves == 0 # AI is down to its last Pokémon, foe has reserves + score *= 1.05 # => Go out with a bang + end end + next score } ) @@ -256,7 +272,11 @@ Battle::AI::Handlers::GeneralMoveScore.add(:flinching_effects, #=============================================================================== # 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. #=============================================================================== Battle::AI::Handlers::GeneralMoveScore.add(:add_predicted_damage, proc { |score, move, user, target, ai, battle| diff --git a/Data/Scripts/999_Main/002_debug battle test.rb b/Data/Scripts/999_Main/002_debug battle test.rb index cb24e578f..7ee33cef5 100644 --- a/Data/Scripts/999_Main/002_debug battle test.rb +++ b/Data/Scripts/999_Main/002_debug battle test.rb @@ -48,14 +48,14 @@ def debug_test_auto_battle(logging = false) player_trainers, ally_items, player_party, player_party_starts = debug_set_up_trainer # Log the combatants echo_participant = lambda do |trainer, party, index| - trainer_txt = "Trainer #{index}: #{trainer.full_name}" + trainer_txt = "[Trainer #{index}] #{trainer.full_name} [skill: #{trainer.skill_level}]" ($INTERNAL) ? PBDebug.log_header(trainer_txt) : echoln(trainer_txt) party.each do |pkmn| - pkmn_txt = " #{pkmn.name}, Lv.#{pkmn.level}\r\n" - pkmn_txt += " Ability: #{pkmn.ability&.name || "---"}\r\n" - pkmn_txt += " Held item: #{pkmn.item&.name || "---"}" + pkmn_txt = "* #{pkmn.name}, Lv.#{pkmn.level}" + pkmn_txt += " [Ability: #{pkmn.ability&.name || "---"}]" + pkmn_txt += " [Item: #{pkmn.item&.name || "---"}]" ($INTERNAL) ? PBDebug.log(pkmn_txt) : echoln(pkmn_txt) - moves_msg = " Moves: " + moves_msg = " Moves: " pkmn.moves.each_with_index do |move, i| moves_msg += ", " if i > 0 moves_msg += move.name @@ -64,6 +64,8 @@ def debug_test_auto_battle(logging = false) end end echo_participant.call(player_trainers[0], player_party, 1) + PBDebug.log("") + echoln "" if !$INTERNAL echo_participant.call(foe_trainers[0], foe_party, 2) echoln "" if !$INTERNAL # Create the battle scene (the visual side of it)