From f33184d4135ea7176262c8505d19241be2d07fcd Mon Sep 17 00:00:00 2001 From: Maruno17 Date: Sun, 18 Dec 2022 20:51:16 +0000 Subject: [PATCH] Generalised AI code for scoring stat changes --- .../003_Move/004_Move_BaseEffects.rb | 2 + .../003_Move/006_MoveEffects_BattlerStats.rb | 5 +- .../011_Battle/005_AI/003_AI_Switch.rb | 3 +- .../011_Battle/005_AI/004_AI_ChooseMove.rb | 108 +++-- ....rb => 020_AI_Move_EffectScoresGeneric.rb} | 375 ++++++++++-------- .../005_AI/021_AI_MoveBaseScores.rb | 305 -------------- .../052_AI_MoveHandlers_BattlerStats.rb | 225 +++++------ .../053_AI_MoveHandlers_BattlerOther.rb | 5 - .../005_AI/055_AI_MoveHandlers_MultiHit.rb | 2 +- .../058_AI_MoveHandlers_ChangeMoveEffect.rb | 3 +- .../059_AI_MoveHandlers_SwitchingActing.rb | 13 +- .../070_AI_MoveHandlers_GeneralModifiers.rb | 10 +- .../011_Battle/005_AI/102_AIBattler.rb | 51 --- Data/Scripts/011_Battle/005_AI/103_AIMove.rb | 4 + 14 files changed, 382 insertions(+), 729 deletions(-) rename Data/Scripts/011_Battle/005_AI/{020_AI_MoveEffectScores_Generic.rb => 020_AI_Move_EffectScoresGeneric.rb} (77%) delete mode 100644 Data/Scripts/011_Battle/005_AI/021_AI_MoveBaseScores.rb diff --git a/Data/Scripts/011_Battle/003_Move/004_Move_BaseEffects.rb b/Data/Scripts/011_Battle/003_Move/004_Move_BaseEffects.rb index 108b4dea0..2a644046c 100644 --- a/Data/Scripts/011_Battle/003_Move/004_Move_BaseEffects.rb +++ b/Data/Scripts/011_Battle/003_Move/004_Move_BaseEffects.rb @@ -155,6 +155,8 @@ end # Lower multiple of user's stats. #=============================================================================== class Battle::Move::StatDownMove < Battle::Move + attr_reader :statDown + def pbEffectWhenDealingDamage(user, target) return if @battle.pbAllFainted?(target.idxOwnSide) showAnim = true 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 87a6fac6b..35cf01bde 100644 --- a/Data/Scripts/011_Battle/003_Move/006_MoveEffects_BattlerStats.rb +++ b/Data/Scripts/011_Battle/003_Move/006_MoveEffects_BattlerStats.rb @@ -907,11 +907,12 @@ class Battle::Move::RaiseTargetAtkSpAtk2 < Battle::Move end def pbEffectAgainstTarget(user, target) + showAnim = true if target.pbCanRaiseStatStage?(:ATTACK, user, self) - target.pbRaiseStatStage(:ATTACK, 2, user) + showAnim = false if target.pbRaiseStatStage(:ATTACK, 2, user, showAnim) end if target.pbCanRaiseStatStage?(:SPECIAL_ATTACK, user, self) - target.pbRaiseStatStage(:SPECIAL_ATTACK, 2, user) + target.pbRaiseStatStage(:SPECIAL_ATTACK, 2, user, showAnim) end end end diff --git a/Data/Scripts/011_Battle/005_AI/003_AI_Switch.rb b/Data/Scripts/011_Battle/005_AI/003_AI_Switch.rb index b6da6cad1..3b1e2856a 100644 --- a/Data/Scripts/011_Battle/005_AI/003_AI_Switch.rb +++ b/Data/Scripts/011_Battle/005_AI/003_AI_Switch.rb @@ -65,7 +65,8 @@ class Battle::AI scoreSum = 0 scoreCount = 0 battler.allOpposing.each do |b| - scoreSum += pbGetMoveScore(battler.moves[idxEncoredMove], [b]) + set_up_move_check(battler.moves[idxEncoredMove]) + scoreSum += pbGetMoveScore([b]) scoreCount += 1 end if scoreCount > 0 && scoreSum / scoreCount <= 20 diff --git a/Data/Scripts/011_Battle/005_AI/004_AI_ChooseMove.rb b/Data/Scripts/011_Battle/005_AI/004_AI_ChooseMove.rb index aa0014949..9e9dd542c 100644 --- a/Data/Scripts/011_Battle/005_AI/004_AI_ChooseMove.rb +++ b/Data/Scripts/011_Battle/005_AI/004_AI_ChooseMove.rb @@ -11,6 +11,11 @@ class Battle::AI #============================================================================= # Get scores for the user's moves (done before any action is assessed). + # NOTE: For any move with a target type that can target a foe (or which + # includes a foe(s) if it has multiple targets), the score calculated + # for a target ally will be inverted. The MoveHandlers for those moves + # should therefore treat an ally as a foe when calculating a score + # against it. #============================================================================= def pbGetMoveScores choices = [] @@ -37,7 +42,7 @@ class Battle::AI when 0 # No targets, affects the user or a side or the whole field # Includes: BothSides, FoeSide, None, User, UserSide score = MOVE_BASE_SCORE - PBDebug.logonerr { score = pbGetMoveScore(move) } + PBDebug.logonerr { score = pbGetMoveScore } add_move_to_choices(choices, idxMove, score) when 1 # One target to be chosen by the trainer # Includes: Foe, NearAlly, NearFoe, NearOther, Other, RandomNearFoe, UserOrNearAlly @@ -46,16 +51,11 @@ class Battle::AI # Lightning Rod. Skip any battlers that can't be targeted. @battle.allBattlers.each do |b| next if !@battle.pbMoveCanTarget?(@user.battler.index, b.index, target_data) - # TODO: This should consider targeting an ally if possible. Scores will - # need to distinguish between harmful and beneficial to target. - # def pbGetMoveScore uses "175 - score" if the target is an ally; - # is this okay? - # Noticeably affects a few moves like Heal Pulse, as well as moves - # that the target can be immune to by an ability (you may want to - # attack the ally anyway so it gains the effect of that ability). + # TODO: Should this sometimes consider targeting an ally? See def + # pbGetMoveScoreAgainstTarget for more information. next if target_data.targets_foe && !@user.battler.opposes?(b) score = MOVE_BASE_SCORE - PBDebug.logonerr { score = pbGetMoveScore(move, [b]) } + PBDebug.logonerr { score = pbGetMoveScore([b]) } add_move_to_choices(choices, idxMove, score, b.index) end else # Multiple targets at once @@ -66,7 +66,7 @@ class Battle::AI targets.push(b) end score = MOVE_BASE_SCORE - PBDebug.logonerr { score = pbGetMoveScore(move, targets) } + PBDebug.logonerr { score = pbGetMoveScore(targets) } add_move_to_choices(choices, idxMove, score) end end @@ -93,8 +93,7 @@ class Battle::AI end def set_up_move_check_target(target) - # TODO: Set @target to nil if there isn't one? - @target = (target) ? @battlers[target.index] : nil # @user + @target = (target) ? @battlers[target.index] : nil @target&.refresh_battler end @@ -104,11 +103,12 @@ class Battle::AI # TODO: Add skill checks in here for particular calculations? #============================================================================= def pbPredictMoveFailure - # TODO: Something involving user.usingMultiTurnAttack? (perhaps earlier than - # this?). + # TODO: Something involving user.battler.usingMultiTurnAttack? (perhaps + # earlier than this?). # User is asleep and will not wake up - return true if @trainer.medium_skill? && @user.battler.asleep? && - @user.statusCount > 1 && !@move.move.usableWhenAsleep? + return true if @user.battler.asleep? && @user.statusCount > 1 && !@move.move.usableWhenAsleep? + # User is awake and can't use moves that are only usable when asleep + return true if !@user.battler.asleep? && @move.move.usableWhenAsleep? # User will be truanting return true if @user.has_active_ability?(:TRUANT) && @user.effects[PBEffects::Truant] # Primal weather @@ -142,8 +142,8 @@ class Battle::AI typeMod = @move.move.pbCalcTypeMod(calc_type, @user.battler, @target.battler) return true if @move.move.pbDamagingMove? && Effectiveness.ineffective?(typeMod) # Dark-type immunity to moves made faster by Prankster - return true if Settings::MECHANICS_GENERATION >= 7 && @user.has_active_ability?(:PRANKSTER) && - @target.has_type?(:DARK) && @target.opposes?(@user) + return true if Settings::MECHANICS_GENERATION >= 7 && @move.statusMove? && + @user.has_active_ability?(:PRANKSTER) && @target.has_type?(:DARK) && @target.opposes?(@user) # Airborne-based immunity to Ground moves return true if @move.damagingMove? && calc_type == :GROUND && @target.battler.airborne? && !@move.move.hitsFlyingTargets? @@ -157,8 +157,9 @@ class Battle::AI #============================================================================= # Get a score for the given move being used against the given target. + # Assumes def set_up_move_check has previously been called. #============================================================================= - def pbGetMoveScore(move, targets = nil) + def pbGetMoveScore(targets = nil) # Get the base score for the move score = MOVE_BASE_SCORE # Scores for each target in turn @@ -170,32 +171,17 @@ class Battle::AI # Get a score for the move against each target in turn targets.each do |target| set_up_move_check_target(target) - # Predict whether the move will fail against the target - if @trainer.has_skill_flag?("PredictMoveFailure") - next if pbPredictMoveFailureAgainstTarget - end + t_score = pbGetMoveScoreAgainstTarget + next if t_score < 0 + score += t_score affected_targets += 1 - # Score the move - t_score = MOVE_BASE_SCORE - if @trainer.has_skill_flag?("ScoreMoves") - # Modify the score according to the move's effect against the target - t_score = Battle::AI::Handlers.apply_move_effect_against_target_score(@move.function, - MOVE_BASE_SCORE, @move, @user, @target, self, @battle) - # Modify the score according to various other effects against the target - score = Battle::AI::Handlers.apply_general_move_against_target_score_modifiers( - score, @move, @user, @target, self, @battle) - end - score += (@target.opposes?(@user)) ? t_score : 175 - t_score end # Check if any targets were affected - if affected_targets == 0 - if @trainer.has_skill_flag?("PredictMoveFailure") - return MOVE_FAIL_SCORE if !@move.move.worksWithNoTargets? - score = MOVE_USELESS_SCORE - else - score = MOVE_BASE_SCORE - end + if affected_targets == 0 && @trainer.has_skill_flag?("PredictMoveFailure") + return MOVE_FAIL_SCORE if !@move.move.worksWithNoTargets? + score = MOVE_USELESS_SCORE else + affected_targets = 1 if affected_targets == 0 # To avoid dividing by 0 # TODO: Can this accounting for multiple targets be improved somehow? score /= affected_targets # Average the score against multiple targets # Bonus for affecting multiple targets @@ -220,6 +206,46 @@ class Battle::AI return score end + #============================================================================= + # Returns the score of @move being used against @target. A return value of -1 + # means the move will fail or do nothing against the target. + # Assumes def set_up_move_check and def set_up_move_check_target have + # previously been called. + # TODO: Add something in here (I think) to specially score moves used against + # an ally and the ally has an ability that will benefit from being hit + # by the move. + # TODO: The above also applies if the move is Heal Pulse or a few other moves + # like that, which CAN target a foe but you'd never do so. Maybe use a + # move flag to determine such moves? The implication is that such moves + # wouldn't apply the "175 - score" bit, which would make their + # MoveHandlers do the opposite calculations to other moves with the same + # targets, but is this desirable? + #============================================================================= + def pbGetMoveScoreAgainstTarget + # Predict whether the move will fail against the target + if @trainer.has_skill_flag?("PredictMoveFailure") + return -1 if pbPredictMoveFailureAgainstTarget + end + # Score the move + score = MOVE_BASE_SCORE + if @trainer.has_skill_flag?("ScoreMoves") + # Modify the score according to the move's effect against the target + score = Battle::AI::Handlers.apply_move_effect_against_target_score(@move.function, + MOVE_BASE_SCORE, @move, @user, @target, self, @battle) + # Modify the score according to various other effects against the target + score = Battle::AI::Handlers.apply_general_move_against_target_score_modifiers( + score, @move, @user, @target, self, @battle) + end + # Add the score against the target to the overall score + target_data = @move.pbTarget(@user.battler) + if target_data.targets_foe && !@target.opposes?(@user) && @target.index != @user.index + return -1 if score == MOVE_USELESS_SCORE + # TODO: Is this reversal of the score okay? + score = 175 - score + end + return score + end + #============================================================================= # Make the final choice of which move to use depending on the calculated # scores for each move. Moves with higher scores are more likely to be chosen. diff --git a/Data/Scripts/011_Battle/005_AI/020_AI_MoveEffectScores_Generic.rb b/Data/Scripts/011_Battle/005_AI/020_AI_Move_EffectScoresGeneric.rb similarity index 77% rename from Data/Scripts/011_Battle/005_AI/020_AI_MoveEffectScores_Generic.rb rename to Data/Scripts/011_Battle/005_AI/020_AI_Move_EffectScoresGeneric.rb index 5431e1c98..684510aaa 100644 --- a/Data/Scripts/011_Battle/005_AI/020_AI_MoveEffectScores_Generic.rb +++ b/Data/Scripts/011_Battle/005_AI/020_AI_Move_EffectScoresGeneric.rb @@ -1,88 +1,100 @@ class Battle::AI #============================================================================= - # Main method for calculating the score for moves that raise the user's stat(s). + # Main method for calculating the score for moves that raise a battler's + # stat(s). + # By default, assumes that a stat raise is a good thing. However, this score + # is inverted (by desire_mult) if the target opposes the user. If the move + # could target a foe but is targeting an ally, the score is also inverted, but + # only because it is inverted again in def pbGetMoveScoreAgainstTarget. #============================================================================= - def get_score_for_user_stat_raise(score) - # Discard status move/don't prefer damaging move if user has Contrary - if !@battle.moldBreaker && @user.has_active_ability?(:CONTRARY) - return (@move.statusMove?) ? MOVE_USELESS_SCORE : score - 20 + def get_score_for_target_stat_raise(score, target, stat_changes, whole_effect = true) + whole_effect = false if @move.damagingMove? + # Decide whether the target raising its stat(s) is a good thing + desire_mult = 1 + if target.opposes?(@user) || + (@move.pbTarget(@user.battler).targets_foe && target.index != @user.index) + desire_mult = -1 end - # Don't make score changes if user will faint from EOR damage - if @user.rough_end_of_round_damage > @user.hp - return (@move.statusMove?) ? MOVE_USELESS_SCORE : score + # Discard status move/don't prefer damaging move if target has Contrary + # TODO: Maybe this should return get_score_for_target_stat_drop if Contrary + # applies and desire_mult < 1. + if !@battle.moldBreaker && target.has_active_ability?(:CONTRARY) && desire_mult > 1 + return (whole_effect) ? MOVE_USELESS_SCORE : score - 20 end - # Don't make score changes if foes have Unaware and user can't make use of + # Don't make score changes if target will faint from EOR damage + if target.rough_end_of_round_damage > target.hp + return (whole_effect) ? MOVE_USELESS_SCORE : score + end + # Don't make score changes if foes have Unaware and target can't make use of # extra stat stages - if !@user.check_for_move { |m| m.function == "PowerHigherWithUserPositiveStatStages" } + if !target.check_for_move { |m| m.function == "PowerHigherWithUserPositiveStatStages" } foe_is_aware = false - each_foe_battler(@user.side) do |b, i| + each_foe_battler(target.side) do |b, i| foe_is_aware = true if !b.has_active_ability?(:UNAWARE) end if !foe_is_aware - return (@move.statusMove?) ? MOVE_USELESS_SCORE : score + return (whole_effect) ? MOVE_USELESS_SCORE : score end end # Figure out which stat raises can happen - stat_changes = [] - @move.move.statUp.each_with_index do |stat, idx| + real_stat_changes = [] + stat_changes.each_with_index do |stat, idx| next if idx.odd? - next if !stat_raise_worthwhile?(stat) + next if !stat_raise_worthwhile?(target, stat) # Calculate amount that stat will be raised by - increment = @move.move.statUp[idx + 1] + increment = stat_changes[idx + 1] if @move.function == "RaiseUserAtkSpAtk1Or2InSun" increment = 1 - increment = 2 if [:Sun, :HarshSun].include?(@user.battler.effectiveWeather) + increment = 2 if [:Sun, :HarshSun].include?(target.battler.effectiveWeather) end - increment *= 2 if !@battle.moldBreaker && @user.has_active_ability?(:SIMPLE) - increment = [increment, 6 - @user.stages[stat]].min # The actual stages gained + increment *= 2 if !@battle.moldBreaker && target.has_active_ability?(:SIMPLE) + increment = [increment, 6 - target.stages[stat]].min # The actual stages gained # Count this as a valid stat raise - stat_changes.push([stat, increment]) if increment > 0 + real_stat_changes.push([stat, increment]) if increment > 0 end # Discard move if it can't raise any stats - if stat_changes.length == 0 - # TODO: Have a parameter that decides whether to reduce the score here - # (for moves where this is just part of the effect). - return (@move.statusMove?) ? MOVE_USELESS_SCORE : score + if real_stat_changes.length == 0 + return (whole_effect) ? MOVE_USELESS_SCORE : score end # Make score changes based on the general concept of raising stats at all - score = get_user_stat_raise_score_generic(score, stat_changes) + score = get_target_stat_raise_score_generic(score, target, real_stat_changes, desire_mult) # Make score changes based on the specific changes to each stat that will be # raised - stat_changes.each do |change| - score = get_user_stat_raise_score_one(score, change[0], change[1]) + real_stat_changes.each do |change| + score = get_target_stat_raise_score_one(score, target, change[0], change[1], desire_mult) end return score end #============================================================================= - # Returns whether the user raising the given stat will have any impact. + # Returns whether the target raising the given stat will have any impact. # TODO: Make sure the move's actual damage category is taken into account, # i.e. CategoryDependsOnHigherDamagePoisonTarget and # CategoryDependsOnHigherDamageIgnoreTargetAbility. #============================================================================= - def stat_raise_worthwhile?(stat) - return false if !@user.battler.pbCanRaiseStatStage?(stat, @user.battler, @move.move) - # Check if user won't benefit from the stat being raised - # TODO: Exception if user knows Baton Pass/Stored Power? + def stat_raise_worthwhile?(target, stat) + return false if !target.battler.pbCanRaiseStatStage?(stat, @user.battler, @move.move) + # Check if target won't benefit from the stat being raised + # TODO: Exception if target knows Baton Pass/Stored Power? case stat when :ATTACK - return false if !@user.check_for_move { |m| m.physicalMove?(m.type) && - m.function != "UseUserDefenseInsteadOfUserAttack" && - m.function != "UseTargetAttackInsteadOfUserAttack" } + return false if !target.check_for_move { |m| m.physicalMove?(m.type) && + m.function != "UseUserDefenseInsteadOfUserAttack" && + m.function != "UseTargetAttackInsteadOfUserAttack" } when :DEFENSE - each_foe_battler(@user.side) do |b, i| + each_foe_battler(target.side) do |b, i| return true if b.check_for_move { |m| m.physicalMove?(m.type) || m.function == "UseTargetDefenseInsteadOfTargetSpDef" } end return false when :SPECIAL_ATTACK - return false if !@user.check_for_move { |m| m.specialMove?(m.type) } + return false if !target.check_for_move { |m| m.specialMove?(m.type) } when :SPECIAL_DEFENSE - each_foe_battler(@user.side) do |b, i| + each_foe_battler(target.side) do |b, i| return true if b.check_for_move { |m| m.specialMove?(m.type) && m.function != "UseTargetDefenseInsteadOfTargetSpDef" } end @@ -92,9 +104,9 @@ class Battle::AI "PowerHigherWithUserFasterThanTarget", "PowerHigherWithUserPositiveStatStages" ] - if !@user.check_for_move { |m| moves_that_prefer_high_speed.include?(m.function) } - each_foe_battler(@user.side) do |b, i| - return true if b.faster_than?(@user) + if !target.check_for_move { |m| moves_that_prefer_high_speed.include?(m.function) } + each_foe_battler(target.side) do |b, i| + return true if b.faster_than?(target) end return false end @@ -107,27 +119,35 @@ class Battle::AI #============================================================================= # Make score changes based on the general concept of raising stats at all. #============================================================================= - def get_user_stat_raise_score_generic(score, stat_changes) + def get_target_stat_raise_score_generic(score, target, stat_changes, desire_mult = 1) total_increment = stat_changes.sum { |change| change[1] } - # TODO: Just return if foe is predicted to use a phazing move (one that - # switches the user out). - # TODO: Don't prefer if foe is faster than user and is predicted to deal + # TODO: Just return if the target's foe is predicted to use a phazing move + # (one that switches the target out). + # TODO: Don't prefer if foe is faster than target and is predicted to deal # lethal damage. - # TODO: Don't prefer if foe is slower than user but is predicted to be able - # to 2HKO user. - # TODO: Prefer if foe is semi-invulnerable and user is faster (can't hit + # TODO: Don't prefer if foe is slower than target but is predicted to be + # able to 2HKO the target. + # TODO: Prefer if foe is semi-invulnerable and target is faster (can't hit # the foe anyway). # Prefer if move is a status move and it's the user's first/second turn if @user.turnCount < 2 && @move.statusMove? - score += total_increment * 4 + score += total_increment * desire_mult * 4 end # Prefer if user is at high HP, don't prefer if user is at low HP - if @user.hp >= @user.totalhp * 0.7 - score += 4 * total_increment + if target.index != @user.index + if @user.hp >= @user.totalhp * 0.7 + score += total_increment * desire_mult * 3 + else + score += total_increment * desire_mult * ((100 * @user.hp / @user.totalhp) - 50) / 6 # +3 to -8 per stage + end + end + # Prefer if target is at high HP, don't prefer if target is at low HP + if target.hp >= target.totalhp * 0.7 + score += total_increment * desire_mult * 4 else - score += total_increment * ((100 * @user.hp / @user.totalhp) - 50) / 4 # +5 to -12 per stage + score += total_increment * desire_mult * ((100 * target.hp / target.totalhp) - 50) / 4 # +5 to -12 per stage end # TODO: Look at abilities that trigger upon stat raise. There are none. @@ -225,7 +245,7 @@ class Battle::AI #============================================================================= # Make score changes based on the raising of a specific stat. #============================================================================= - def get_user_stat_raise_score_one(score, stat, increment) + def get_target_stat_raise_score_one(score, target, stat, increment, desire_mult = 1) # Figure out how much the stat will actually change by stage_mul = [2, 2, 2, 2, 2, 2, 2, 3, 4, 5, 6, 7, 8] stage_div = [8, 7, 6, 5, 4, 3, 2, 2, 2, 2, 2, 2, 2] @@ -233,97 +253,95 @@ class Battle::AI stage_mul = [3, 3, 3, 3, 3, 3, 3, 4, 5, 6, 7, 8, 9] stage_div = [9, 8, 7, 6, 5, 4, 3, 3, 3, 3, 3, 3, 3] end - old_stage = @user.stages[stat] + old_stage = target.stages[stat] new_stage = old_stage + increment inc_mult = (stage_mul[new_stage].to_f * stage_div[old_stage]) / (stage_div[new_stage] * stage_mul[old_stage]) inc_mult -= 1 + inc_mult *= desire_mult # Stat-based score changes case stat when :ATTACK # Modify score depending on current stat stage - # More strongly prefer if the user has no special moves + # More strongly prefer if the target has no special moves if old_stage >= 2 - score -= 20 + score -= 20 * ((target.opposes?(@user)) ? 1 : desire_mult) else - has_special_moves = @user.check_for_move { |m| m.specialMove?(m.type) } + has_special_moves = target.check_for_move { |m| m.specialMove?(m.type) } inc = (has_special_moves) ? 10 : 20 score += inc * inc_mult - score += 8 * inc_mult if @user.hp == @user.totalhp end when :DEFENSE # Modify score depending on current stat stage if old_stage >= 2 - score -= 20 + score -= 20 * ((target.opposes?(@user)) ? 1 : desire_mult) else score += 10 * inc_mult - score += 8 * inc_mult if @user.hp == @user.totalhp end when :SPECIAL_ATTACK # Modify score depending on current stat stage - # More strongly prefer if the user has no physical moves + # More strongly prefer if the target has no physical moves if old_stage >= 2 - score -= 20 + score -= 20 * ((target.opposes?(@user)) ? 1 : desire_mult) else - has_physical_moves = @user.check_for_move { |m| m.physicalMove?(m.type) && - m.function != "UseUserDefenseInsteadOfUserAttack" && - m.function != "UseTargetAttackInsteadOfUserAttack" } + has_physical_moves = target.check_for_move { |m| m.physicalMove?(m.type) && + m.function != "UseUserDefenseInsteadOfUserAttack" && + m.function != "UseTargetAttackInsteadOfUserAttack" } inc = (has_physical_moves) ? 10 : 20 score += inc * inc_mult - score += 8 * inc_mult if @user.hp == @user.totalhp end when :SPECIAL_DEFENSE # Modify score depending on current stat stage if old_stage >= 2 - score -= 20 + score -= 20 * ((target.opposes?(@user)) ? 1 : desire_mult) else score += 10 * inc_mult - score += 8 * inc_mult if @user.hp == @user.totalhp end when :SPEED - # Prefer if user is slower than a foe - # TODO: Don't prefer if the user is too much slower than any foe that it + # Prefer if target is slower than a foe + # TODO: Don't prefer if the target is too much slower than any foe that it # can't catch up. - each_foe_battler(@user.side) do |b, i| - next if @user.faster_than?(b) + each_foe_battler(target.side) do |b, i| + next if target.faster_than?(b) score += 15 * inc_mult break end - # TODO: Prefer if the user is able to cause flinching (moves that flinch, - # or has King's Rock/Stench). - # Prefer if the user has Electro Ball or Power Trip/Stored Power + # TODO: Prefer if the target is able to cause flinching (moves that + # flinch, or has King's Rock/Stench). + # Prefer if the target has Electro Ball or Power Trip/Stored Power moves_that_prefer_high_speed = [ "PowerHigherWithUserFasterThanTarget", "PowerHigherWithUserPositiveStatStages" ] - if @user.check_for_move { |m| moves_that_prefer_high_speed.include?(m.function) } + if target.check_for_move { |m| moves_that_prefer_high_speed.include?(m.function) } score += 8 * inc_mult end # Don't prefer if any foe has Gyro Ball - each_foe_battler(@user.side) do |b, i| + each_foe_battler(target.side) do |b, i| next if !b.check_for_move { |m| m.function == "PowerHigherWithTargetFasterThanUser" } score -= 8 * inc_mult end - # Don't prefer if user has Speed Boost (will be gaining Speed anyway) - score -= 20 if @user.has_active_ability?(:SPEEDBOOST) + # Don't prefer if target has Speed Boost (will be gaining Speed anyway) + if target.has_active_ability?(:SPEEDBOOST) + score -= 20 * ((target.opposes?(@user)) ? 1 : desire_mult) + end when :ACCURACY # Modify score depending on current stat stage if old_stage >= 2 - score -= 20 + score -= 20 * ((target.opposes?(@user)) ? 1 : desire_mult) else min_accuracy = 100 - @user.battler.moves.each do |m| + target.battler.moves.each do |m| next if m.accuracy == 0 || m.is_a?(Battle::Move::OHKO) min_accuracy = m.accuracy if m.accuracy < min_accuracy end min_accuracy = min_accuracy * stage_mul[old_stage] / stage_div[old_stage] if min_accuracy < 90 score += 10 * inc_mult - score += 8 * inc_mult if @user.hp == @user.totalhp end end @@ -331,29 +349,28 @@ class Battle::AI # Prefer if a foe will (probably) take damage at the end of the round # TODO: Should this take into account EOR healing, one-off damage and # damage-causing effects that wear off naturally (like Sea of Fire)? - # TODO: Emerald AI also prefers if user is rooted via Ingrain. - each_foe_battler(@user.side) do |b, i| + # TODO: Emerald AI also prefers if target is rooted via Ingrain. + each_foe_battler(target.side) do |b, i| eor_damage = b.rough_end_of_round_damage - score += 60 * eor_damage / b.totalhp if eor_damage > 0 + next if eor_damage <= 0 end # Modify score depending on current stat stage if old_stage >= 2 - score -= 20 + score -= 20 * ((target.opposes?(@user)) ? 1 : desire_mult) else - score += 10 * (2 - old_stage) * inc_mult - score += 8 * inc_mult if @user.hp == @user.totalhp + score += 10 * inc_mult end end - # Prefer if user has Stored Power - if @user.check_for_move { |m| m.function == "PowerHigherWithUserPositiveStatStages" } - score += 5 * pos_change + # Prefer if target has Stored Power + if target.check_for_move { |m| m.function == "PowerHigherWithUserPositiveStatStages" } + score += 5 * increment * desire_mult end # Don't prefer if any foe has Punishment - each_foe_battler(@user.side) do |b, i| + each_foe_battler(target.side) do |b, i| next if !b.check_for_move { |m| m.function == "PowerHigherWithTargetPositiveStatStages" } - score -= 5 * pos_change + score -= 5 * increment * desire_mult end return score @@ -363,9 +380,6 @@ class Battle::AI mini_score = 1.0 case stat when :ATTACK - # TODO: Don't prefer if target has previously used a move that benefits - # from user's Attack being boosted. -# mini_score *= 0.3 if @target.check_for_move { |m| m.function == "UseTargetAttackInsteadOfUserAttack" } # Foul Play # Prefer if user can definitely survive a hit no matter how powerful, and # it won't be hurt by weather # if @user.hp == @user.totalhp && @@ -567,55 +581,67 @@ class Battle::AI end #============================================================================= - # Main method for calculating the score for moves that lower the target's + # Main method for calculating the score for moves that lower a battler's # stat(s). + # By default, assumes that a stat drop is a good thing. However, this score + # is inverted (by desire_mult) if the target is the user or an ally. This + # inversion does not happen if the move could target a foe but is targeting an + # ally, but only because it is inverted in def pbGetMoveScoreAgainstTarget + # instead. # TODO: Revisit this method as parts may need rewriting. #============================================================================= - def get_score_for_target_stat_drop(score) + def get_score_for_target_stat_drop(score, target, stat_changes, whole_effect = true) + whole_effect = false if @move.damagingMove? + # Decide whether the target raising its stat(s) is a good thing + desire_mult = -1 + if target.opposes?(@user) || + (@move.pbTarget(@user.battler).targets_foe && target.index != @user.index) + desire_mult = 1 + end # Discard status move/don't prefer damaging move if target has Contrary - if !@battle.moldBreaker && @target.has_active_ability?(:CONTRARY) - return (@move.statusMove?) ? MOVE_USELESS_SCORE : score - 20 + # TODO: Maybe this should return get_score_for_target_stat_raise if Contrary + # applies and desire_mult < 1. + if !@battle.moldBreaker && target.has_active_ability?(:CONTRARY) && desire_mult > 1 + return (whole_effect) ? MOVE_USELESS_SCORE : score - 20 end # Don't make score changes if target will faint from EOR damage - if @target.rough_end_of_round_damage > @target.hp - return (@move.statusMove?) ? MOVE_USELESS_SCORE : score + if target.rough_end_of_round_damage > target.hp + return (whole_effect) ? MOVE_USELESS_SCORE : score end - # Don't make score changes if allies have Unaware and can't make use of - # target's lowered stat stages - ally_is_aware = false - each_foe_battler(@target.side) do |b, i| - ally_is_aware = true if !b.has_active_ability?(:UNAWARE) + # Don't make score changes if foes have Unaware and target can't make use of + # its lowered stat stages + foe_is_aware = false + each_foe_battler(target.side) do |b, i| + foe_is_aware = true if !b.has_active_ability?(:UNAWARE) end - if !ally_is_aware - return (@move.statusMove?) ? MOVE_USELESS_SCORE : score + if !foe_is_aware + return (whole_effect) ? MOVE_USELESS_SCORE : score end # Figure out which stat raises can happen - stat_changes = [] - @move.move.statDown.each_with_index do |stat, idx| + real_stat_changes = [] + stat_changes.each_with_index do |stat, idx| next if idx.odd? - next if !stat_drop_worthwhile?(stat) + next if !stat_drop_worthwhile?(target, stat) # Calculate amount that stat will be raised by - decrement = @move.move.statDown[idx + 1] + decrement = stat_changes[idx + 1] decrement *= 2 if !@battle.moldBreaker && @user.has_active_ability?(:SIMPLE) - decrement = [decrement, 6 + @target.stages[stat]].min # The actual stages lost + decrement = [decrement, 6 + target.stages[stat]].min # The actual stages lost # Count this as a valid stat drop - stat_changes.push([stat, decrement]) if decrement > 0 + real_stat_changes.push([stat, decrement]) if decrement > 0 end # Discard move if it can't lower any stats - if stat_changes.length == 0 - # TODO: Have a parameter that decides whether to reduce the score here - # (for moves where this is just part of the effect). - return (@move.statusMove?) ? MOVE_USELESS_SCORE : score + if real_stat_changes.length == 0 + return (whole_effect) ? MOVE_USELESS_SCORE : score end # Make score changes based on the general concept of lowering stats at all - score = get_target_stat_drop_score_generic(score, stat_changes) + score = get_target_stat_drop_score_generic(score, target, real_stat_changes, desire_mult) # Make score changes based on the specific changes to each stat that will be # lowered - stat_changes.each do |change| - score = get_target_stat_drop_score_one(score, change[0], change[1]) + real_stat_changes.each do |change| + score = get_target_stat_drop_score_one(score, target, change[0], change[1], desire_mult) end return score @@ -628,24 +654,24 @@ class Battle::AI # CategoryDependsOnHigherDamageIgnoreTargetAbility. # TODO: Revisit this method as parts may need rewriting. #============================================================================= - def stat_drop_worthwhile?(stat) - return false if !@target.battler.pbCanLowerStatStage?(stat, @user.battler, @move.move) + def stat_drop_worthwhile?(target, stat) + return false if !target.battler.pbCanLowerStatStage?(stat, @user.battler, @move.move) # Check if target won't benefit from the stat being lowered case stat when :ATTACK - return false if !@target.check_for_move { |m| m.physicalMove?(m.type) && - m.function != "UseUserDefenseInsteadOfUserAttack" && - m.function != "UseTargetAttackInsteadOfUserAttack" } + return false if !target.check_for_move { |m| m.physicalMove?(m.type) && + m.function != "UseUserDefenseInsteadOfUserAttack" && + m.function != "UseTargetAttackInsteadOfUserAttack" } when :DEFENSE - each_foe_battler(@target.side) do |b, i| + each_foe_battler(target.side) do |b, i| return true if b.check_for_move { |m| m.physicalMove?(m.type) || m.function == "UseTargetDefenseInsteadOfTargetSpDef" } end return false when :SPECIAL_ATTACK - return false if !@target.check_for_move { |m| m.specialMove?(m.type) } + return false if !target.check_for_move { |m| m.specialMove?(m.type) } when :SPECIAL_DEFENSE - each_foe_battler(@target.side) do |b, i| + each_foe_battler(target.side) do |b, i| return true if b.check_for_move { |m| m.specialMove?(m.type) && m.function != "UseTargetDefenseInsteadOfTargetSpDef" } end @@ -655,9 +681,9 @@ class Battle::AI "PowerHigherWithUserFasterThanTarget", "PowerHigherWithUserPositiveStatStages" ] - if !@target.check_for_move { |m| moves_that_prefer_high_speed.include?(m.function) } - each_foe_battler(@target.side) do |b, i| - return true if !b.faster_than?(@target) + if !target.check_for_move { |m| moves_that_prefer_high_speed.include?(m.function) } + each_foe_battler(target.side) do |b, i| + return true if !b.faster_than?(target) end return false end @@ -672,7 +698,7 @@ class Battle::AI # TODO: Revisit this method as parts may need rewriting. # TODO: All comments in this method may be inaccurate. #============================================================================= - def get_target_stat_drop_score_generic(score, stat_changes) + def get_target_stat_drop_score_generic(score, target, stat_changes, desire_mult = 1) total_decrement = stat_changes.sum { |change| change[1] } # TODO: Just return if target is predicted to switch out (except via Baton Pass). # TODO: Don't prefer if target is faster than user and is predicted to deal @@ -683,20 +709,22 @@ class Battle::AI # Prefer if move is a status move and it's the user's first/second turn if @user.turnCount < 2 && @move.statusMove? - score += total_decrement * 4 + score += total_decrement * desire_mult * 4 end # Prefer if user is at high HP, don't prefer if user is at low HP - if @user.hp >= @user.totalhp * 0.7 - score += 3 * total_decrement - else - score += total_decrement * ((100 * @user.hp / @user.totalhp) - 50) / 6 # +3 to -8 per stage + if target.index != @user.index + if @user.hp >= @user.totalhp * 0.7 + score += total_decrement * desire_mult * 3 + else + score += total_decrement * desire_mult * ((100 * @user.hp / @user.totalhp) - 50) / 6 # +3 to -8 per stage + end end # Prefer if target is at high HP, don't prefer if target is at low HP - if @target.hp >= @target.totalhp * 0.7 - score += 3 * total_decrement + if target.hp >= target.totalhp * 0.7 + score += total_decrement * desire_mult * 3 else - score += total_decrement * ((100 * @target.hp / @target.totalhp) - 50) / 6 # +3 to -8 per stage + score += total_decrement * desire_mult * ((100 * target.hp / target.totalhp) - 50) / 6 # +3 to -8 per stage end # TODO: Look at abilities that trigger upon stat lowering. @@ -708,7 +736,7 @@ class Battle::AI # Make score changes based on the lowering of a specific stat. # TODO: Revisit this method as parts may need rewriting. #============================================================================= - def get_target_stat_drop_score_one(score, stat, decrement) + def get_target_stat_drop_score_one(score, target, stat, decrement, desire_mult = 1) # Figure out how much the stat will actually change by stage_mul = [2, 2, 2, 2, 2, 2, 2, 3, 4, 5, 6, 7, 8] stage_div = [8, 7, 6, 5, 4, 3, 2, 2, 2, 2, 2, 2, 2] @@ -716,102 +744,99 @@ class Battle::AI stage_mul = [3, 3, 3, 3, 3, 3, 3, 4, 5, 6, 7, 8, 9] stage_div = [9, 8, 7, 6, 5, 4, 3, 3, 3, 3, 3, 3, 3] end - old_stage = @target.stages[stat] + old_stage = target.stages[stat] new_stage = old_stage - decrement dec_mult = (stage_mul[old_stage].to_f * stage_div[new_stage]) / (stage_div[old_stage] * stage_mul[new_stage]) dec_mult -= 1 + dec_mult *= desire_mult # Stat-based score changes case stat when :ATTACK # Modify score depending on current stat stage # More strongly prefer if the target has no special moves if old_stage <= -2 - score -= 20 + score -= 20 * ((target.opposes?(@user)) ? 1 : desire_mult) else - has_special_moves = @target.check_for_move { |m| m.specialMove?(m.type) } + has_special_moves = target.check_for_move { |m| m.specialMove?(m.type) } dec = (has_special_moves) ? 5 : 10 - score += dec * (2 + old_stage) * dec_mult - score += 4 * dec_mult if @user.hp == @user.totalhp + score += dec * dec_mult end when :DEFENSE # Modify score depending on current stat stage if old_stage <= -2 - score -= 20 + score -= 20 * ((target.opposes?(@user)) ? 1 : desire_mult) else - score += 5 * (2 + old_stage) * dec_mult - score += 4 * dec_mult if @user.hp == @user.totalhp + score += 5 * dec_mult end when :SPECIAL_ATTACK # Modify score depending on current stat stage # More strongly prefer if the target has no physical moves if old_stage <= -2 - score -= 20 + score -= 20 * ((target.opposes?(@user)) ? 1 : desire_mult) else - has_physical_moves = @target.check_for_move { |m| m.physicalMove?(m.type) && - m.function != "UseUserDefenseInsteadOfUserAttack" && - m.function != "UseTargetAttackInsteadOfUserAttack" } + has_physical_moves = target.check_for_move { |m| m.physicalMove?(m.type) && + m.function != "UseUserDefenseInsteadOfUserAttack" && + m.function != "UseTargetAttackInsteadOfUserAttack" } dec = (has_physical_moves) ? 5 : 10 - score += dec * (2 + old_stage) * dec_mult - score += 4 * dec_mult if @user.hp == @user.totalhp + score += dec * dec_mult end when :SPECIAL_DEFENSE # Modify score depending on current stat stage if old_stage <= -2 - score -= 20 + score -= 20 * ((target.opposes?(@user)) ? 1 : desire_mult) else - score += 5 * (2 + old_stage) * dec_mult - score += 4 * dec_mult if @user.hp == @user.totalhp + score += 5 * dec_mult end when :SPEED # Prefer if target is faster than an ally # TODO: Don't prefer if the target is too much faster than any ally and # can't be brought slow enough. - each_foe_battler(@target.side) do |b, i| - next if b.faster_than?(@target) + each_foe_battler(target.side) do |b, i| + next if b.faster_than?(target) score += 15 * dec_mult break end # Prefer if any ally has Electro Ball - each_foe_battler(@target.side) do |b, i| + each_foe_battler(target.side) do |b, i| next if !b.check_for_move { |m| m.function == "PowerHigherWithUserFasterThanTarget" } score += 8 * dec_mult end # Don't prefer if target has Speed Boost (will be gaining Speed anyway) - score -= 20 if @target.has_active_ability?(:SPEEDBOOST) + if target.has_active_ability?(:SPEEDBOOST) + score -= 20 * ((target.opposes?(@user)) ? 1 : desire_mult) + end when :ACCURACY # Modify score depending on current stat stage if old_stage <= -2 - score -= 20 + score -= 20 * ((target.opposes?(@user)) ? 1 : desire_mult) else - score += 5 * (2 + old_stage) * dec_mult - score += 4 * dec_mult if @user.hp == @user.totalhp + score += 5 * dec_mult end # TODO: Prefer if target is poisoned/toxiced/Leech Seeded/cursed. when :EVASION # Modify score depending on current stat stage if old_stage <= -2 - score -= 20 + score -= 20 * ((target.opposes?(@user)) ? 1 : desire_mult) else - score += 5 * (2 + old_stage) * dec_mult - score += 4 * dec_mult if @user.hp == @user.totalhp + score += 5 * dec_mult end end # Prefer if target has Stored Power - if @target.check_for_move { |m| m.function == "PowerHigherWithUserPositiveStatStages" } - score += 5 * decrement + if target.check_for_move { |m| m.function == "PowerHigherWithUserPositiveStatStages" } + score += 5 * decrement * desire_mult end - # Don't prefer if any ally has Punishment - each_foe_battler(@target.side) do |b, i| + # Don't prefer if any foe has Punishment + each_foe_battler(target.side) do |b, i| next if !b.check_for_move { |m| m.function == "PowerHigherWithTargetPositiveStatStages" } - score -= 5 * decrement + score -= 5 * decrement * desire_mult end return score diff --git a/Data/Scripts/011_Battle/005_AI/021_AI_MoveBaseScores.rb b/Data/Scripts/011_Battle/005_AI/021_AI_MoveBaseScores.rb deleted file mode 100644 index 8458184a0..000000000 --- a/Data/Scripts/011_Battle/005_AI/021_AI_MoveBaseScores.rb +++ /dev/null @@ -1,305 +0,0 @@ -class Battle::AI - #============================================================================= - # Calculate how much damage a move is likely to do to a given target (as a - # percentage of the target's current HP) - # TODO: How much is this going to be used? Should the predicted percentage of - # damage be used as the initial score for damaging moves? - #============================================================================= - def pbGetDamagingMoveBaseScore - return 100 - -=begin - # Don't prefer moves that are ineffective because of abilities or effects - return 0 if @target.immune_to_move? - user_battler = @user.battler - target_battler = @target.battler - - # Calculate how much damage the move will do (roughly) - calc_damage = @move.rough_damage - - # TODO: Maybe move this check elsewhere? Note that Reborn's base score does - # not include this halving, but the predicted damage does. - # Two-turn attacks waste 2 turns to deal one lot of damage - calc_damage /= 2 if @move.move.chargingTurnMove? - - # TODO: Maybe move this check elsewhere? - # Increased critical hit rate - if @trainer.medium_skill? - crit_stage = @move.rough_critical_hit_stage - if crit_stage >= 0 - crit_fraction = (crit_stage > 50) ? 1 : Battle::Move::CRITICAL_HIT_RATIOS[crit_stage] - crit_mult = (Settings::NEW_CRITICAL_HIT_RATE_MECHANICS) ? 0.5 : 1 - calc_damage *= (1 + crit_mult / crit_fraction) - end - end - - # Convert damage to percentage of target's remaining HP - damage_percentage = calc_damage * 100.0 / target_battler.hp - - # Don't prefer weak attacks -# damage_percentage /= 2 if damage_percentage < 20 - - # Prefer damaging attack if level difference is significantly high -# damage_percentage *= 1.2 if user_battler.level - 10 > target_battler.level - - # Adjust score - damage_percentage = 110 if damage_percentage > 110 # Treat all lethal moves the same - damage_percentage += 40 if damage_percentage > 100 # Prefer moves likely to be lethal - - return damage_percentage.to_i -=end - end - - #============================================================================= - # TODO: Remove this method. If we're keeping any score changes inherent to a - # move's effect, they will go in MoveEffectScore handlers instead. - #============================================================================= - def pbGetStatusMoveBaseScore - return 100 - -=begin - # TODO: Call @target.immune_to_move? here too, not just for damaging moves - # (only if this status move will be affected). - - # TODO: Make sure all status moves are accounted for. - # TODO: Duplicates in Reborn's AI: - # "SleepTarget" Grass Whistle (15), Hypnosis (15), Sing (15), - # Lovely Kiss (20), Sleep Powder (20), Spore (60) - # "PoisonTarget" - Poison Powder (15), Poison Gas (20) - # "ParalyzeTarget" - Stun Spore (25), Glare (30) - # "ConfuseTarget" - Teeter Dance (5), Supersonic (10), - # Sweet Kiss (20), Confuse Ray (25) - # "RaiseUserAttack1" - Howl (10), Sharpen (10), Medicate (15) - # "RaiseUserSpeed2" - Agility (15), Rock Polish (25) - # "LowerTargetAttack1" - Growl (10), Baby-Doll Eyes (15) - # "LowerTargetAccuracy1" - Sand Attack (5), Flash (10), Kinesis (10), Smokescreen (10) - # "LowerTargetAttack2" - Charm (10), Feather Dance (15) - # "LowerTargetSpeed2" - String Shot (10), Cotton Spore (15), Scary Face (15) - # "LowerTargetSpDef2" - Metal Sound (10), Fake Tears (15) - case @move.move.function - when "ConfuseTarget", - "LowerTargetAccuracy1", - "LowerTargetEvasion1RemoveSideEffects", - "UserTargetSwapAtkSpAtkStages", - "UserTargetSwapDefSpDefStages", - "UserSwapBaseAtkDef", - "UserTargetAverageBaseAtkSpAtk", - "UserTargetAverageBaseDefSpDef", - "SetUserTypesToUserMoveType", - "SetTargetTypesToWater", - "SetUserTypesToTargetTypes", - "SetTargetAbilityToUserAbility", - "UserTargetSwapAbilities", - "PowerUpAllyMove", - "StartWeakenElectricMoves", - "StartWeakenFireMoves", - "EnsureNextMoveAlwaysHits", - "StartNegateTargetEvasionStatStageAndGhostImmunity", - "StartNegateTargetEvasionStatStageAndDarkImmunity", - "ProtectUserSideFromPriorityMoves", - "ProtectUserSideFromMultiTargetDamagingMoves", - "BounceBackProblemCausingStatusMoves", - "StealAndUseBeneficialStatusMove", - "DisableTargetMovesKnownByUser", - "DisableTargetHealingMoves", - "SetAttackerMovePPTo0IfUserFaints", - "UserEnduresFaintingThisTurn", - "RestoreUserConsumedItem", - "StartNegateHeldItems", - "StartDamageTargetEachTurnIfTargetAsleep", - "HealUserDependingOnUserStockpile", - "StartGravity", - "StartUserAirborne", - "UserSwapsPositionsWithAlly", - "StartSwapAllBattlersBaseDefensiveStats", - "RaiseTargetSpDef1", - "RaiseGroundedGrassBattlersAtkSpAtk1", - "RaiseGrassBattlersDef1", - "AddGrassTypeToTarget", - "TrapAllBattlersInBattleForOneTurn", - "EnsureNextCriticalHit", - "UserTargetSwapBaseSpeed", - "RedirectAllMovesToTarget", - "TargetUsesItsLastUsedMoveAgain" - return 55 - when "RaiseUserAttack1", - "RaiseUserDefense1", - "RaiseUserDefense1CurlUpUser", - "RaiseUserCriticalHitRate2", - "RaiseUserAtkSpAtk1", - "RaiseUserAtkSpAtk1Or2InSun", - "RaiseUserAtkAcc1", - "RaiseTargetRandomStat2", - "LowerTargetAttack1", - "LowerTargetDefense1", - "LowerTargetAccuracy1", - "LowerTargetAttack2", - "LowerTargetSpeed2", - "LowerTargetSpDef2", - "ResetAllBattlersStatStages", - "UserCopyTargetStatStages", - "SetUserTypesBasedOnEnvironment", - "DisableTargetUsingSameMoveConsecutively", - "StartTargetCannotUseItem", - "LowerTargetAttack1BypassSubstitute", - "LowerTargetAtkSpAtk1", - "LowerTargetSpAtk1", - "TargetNextFireMoveDamagesTarget" - return 60 - when "SleepTarget", - "SleepTargetIfUserDarkrai", - "SleepTargetChangeUserMeloettaForm", - "PoisonTarget", - "CureUserBurnPoisonParalysis", - "RaiseUserAttack1", - "RaiseUserSpDef1PowerUpElectricMove", - "RaiseUserEvasion1", - "RaiseUserSpeed2", - "LowerTargetAttack1", - "LowerTargetAtkDef1", - "LowerTargetAttack2", - "LowerTargetDefense2", - "LowerTargetSpeed2", - "LowerTargetSpAtk2IfCanAttract", - "LowerTargetSpDef2", - "ReplaceMoveThisBattleWithTargetLastMoveUsed", - "ReplaceMoveWithTargetLastMoveUsed", - "SetUserAbilityToTargetAbility", - "UseMoveTargetIsAboutToUse", - "UseRandomMoveFromUserParty", - "StartHealUserEachTurnTrapUserInBattle", - "HealTargetHalfOfTotalHP", - "UserFaintsHealAndCureReplacement", - "UserFaintsHealAndCureReplacementRestorePP", - "StartSunWeather", - "StartRainWeather", - "StartSandstormWeather", - "StartHailWeather", - "RaisePlusMinusUserAndAlliesDefSpDef1", - "LowerTargetSpAtk2", - "LowerPoisonedTargetAtkSpAtkSpd1", - "AddGhostTypeToTarget", - "LowerTargetAtkSpAtk1SwitchOutUser", - "RaisePlusMinusUserAndAlliesAtkSpAtk1", - "HealTargetDependingOnGrassyTerrain" - return 65 - when "SleepTarget", - "SleepTargetChangeUserMeloettaForm", - "SleepTargetNextTurn", - "PoisonTarget", - "ConfuseTarget", - "RaiseTargetSpAtk1ConfuseTarget", - "RaiseTargetAttack2ConfuseTarget", - "UserTargetSwapStatStages", - "StartUserSideImmunityToStatStageLowering", - "SetUserTypesToResistLastAttack", - "SetTargetAbilityToSimple", - "SetTargetAbilityToInsomnia", - "NegateTargetAbility", - "TransformUserIntoTarget", - "UseLastMoveUsedByTarget", - "UseLastMoveUsed", - "UseRandomMove", - "HealUserFullyAndFallAsleep", - "StartHealUserEachTurn", - "StartPerishCountsForAllBattlers", - "SwitchOutTargetStatusMove", - "TrapTargetInBattle", - "TargetMovesBecomeElectric", - "NormalMovesBecomeElectric", - "PoisonTargetLowerTargetSpeed1" - return 70 - when "BadPoisonTarget", - "ParalyzeTarget", - "BurnTarget", - "ConfuseTarget", - "AttractTarget", - "GiveUserStatusToTarget", - "RaiseUserDefSpDef1", - "RaiseUserDefense2", - "RaiseUserSpeed2", - "RaiseUserSpeed2LowerUserWeight", - "RaiseUserSpDef2", - "RaiseUserEvasion2MinimizeUser", - "RaiseUserDefense3", - "MaxUserAttackLoseHalfOfTotalHP", - "UserTargetAverageHP", - "ProtectUser", - "DisableTargetLastMoveUsed", - "DisableTargetStatusMoves", - "HealUserHalfOfTotalHP", - "HealUserHalfOfTotalHPLoseFlyingTypeThisTurn", - "HealUserPositionNextTurn", - "HealUserDependingOnWeather", - "StartLeechSeedTarget", - "AttackerFaintsIfUserFaints", - "UserTargetSwapItems", - "UserMakeSubstitute", - "UserAddStockpileRaiseDefSpDef1", - "RedirectAllMovesToUser", - "InvertTargetStatStages", - "HealUserByTargetAttackLowerTargetAttack1", - "HealUserDependingOnSandstorm" - return 75 - when "ParalyzeTarget", - "ParalyzeTargetIfNotTypeImmune", - "RaiseUserAtkDef1", - "RaiseUserAtkDefAcc1", - "RaiseUserSpAtkSpDef1", - "UseMoveDependingOnEnvironment", - "UseRandomUserMoveIfAsleep", - "DisableTargetUsingDifferentMove", - "SwitchOutUserPassOnEffects", - "AddSpikesToFoeSide", - "AddToxicSpikesToFoeSide", - "AddStealthRocksToFoeSide", - "CurseTargetOrLowerUserSpd1RaiseUserAtkDef1", - "StartSlowerBattlersActFirst", - "ProtectUserFromTargetingMovesSpikyShield", - "StartElectricTerrain", - "StartGrassyTerrain", - "StartMistyTerrain", - "StartPsychicTerrain", - "CureTargetStatusHealUserHalfOfTotalHP" - return 80 - when "CureUserPartyStatus", - "RaiseUserAttack2", - "RaiseUserSpAtk2", - "RaiseUserSpAtk3", - "StartUserSideDoubleSpeed", - "StartWeakenPhysicalDamageAgainstUserSide", - "StartWeakenSpecialDamageAgainstUserSide", - "ProtectUserSideFromDamagingMovesIfUserFirstTurn", - "ProtectUserFromDamagingMovesKingsShield", - "ProtectUserBanefulBunker" - return 85 - when "RaiseUserAtkSpd1", - "RaiseUserSpAtkSpDefSpd1", - "LowerUserDefSpDef1RaiseUserAtkSpAtkSpd2", - "RaiseUserAtk1Spd2", - "TwoTurnAttackRaiseUserSpAtkSpDefSpd2" - return 90 - when "SleepTarget", - "SleepTargetChangeUserMeloettaForm", - "AddStickyWebToFoeSide", - "StartWeakenDamageAgainstUserSideIfHail" - return 100 - end - # "DoesNothingUnusableInGravity", - # "StartUserSideImmunityToInflictedStatus", - # "LowerTargetEvasion1", - # "LowerTargetEvasion2", - # "StartPreventCriticalHitsAgainstUserSide", - # "UserFaintsLowerTargetAtkSpAtk2", - # "FleeFromBattle", - # "SwitchOutUserStatusMove" - # "TargetTakesUserItem", - # "LowerPPOfTargetLastMoveBy4", - # "StartTargetAirborneAndAlwaysHitByMoves", - # "TargetActsNext", - # "TargetActsLast", - # "ProtectUserSideFromStatusMoves" - return 100 -=end - end -end 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 a1390450f..d021b4252 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 @@ -10,7 +10,7 @@ Battle::AI::Handlers::MoveFailureCheck.add("RaiseUserAttack1", ) Battle::AI::Handlers::MoveEffectScore.add("RaiseUserAttack1", proc { |score, move, user, ai, battle| - next ai.get_score_for_user_stat_raise(score) + next ai.get_score_for_target_stat_raise(score, user, move.move.statUp) } ) @@ -28,7 +28,7 @@ Battle::AI::Handlers::MoveEffectScore.copy("RaiseUserAttack1", Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("RaiseUserAttack2IfTargetFaints", proc { |score, move, user, target, ai, battle| if move.rough_damage >= target.hp * 0.9 - next ai.get_score_for_user_stat_raise(score) + next ai.get_score_for_target_stat_raise(score, user, move.move.statUp) end } ) @@ -58,7 +58,7 @@ Battle::AI::Handlers::MoveFailureCheck.add("MaxUserAttackLoseHalfOfTotalHP", ) Battle::AI::Handlers::MoveEffectScore.add("MaxUserAttackLoseHalfOfTotalHP", proc { |score, move, user, ai, battle| - score = ai.get_score_for_user_stat_raise(score) + score = ai.get_score_for_target_stat_raise(score, user, move.move.statUp) # Don't prefer the lower the user's HP is score -= 80 * (1 - (user.hp.to_f / user.totalhp)) # 0 to -40 next score @@ -81,7 +81,7 @@ Battle::AI::Handlers::MoveFailureCheck.copy("RaiseUserDefense1", "RaiseUserDefense1CurlUpUser") Battle::AI::Handlers::MoveEffectScore.add("RaiseUserDefense1CurlUpUser", proc { |score, move, user, ai, battle| - score = ai.get_score_for_user_stat_raise(score) + score = ai.get_score_for_target_stat_raise(score, user, move.move.statUp) if !user.effects[PBEffects::DefenseCurl] && user.check_for_move { |m| m.function == "MultiTurnAttackPowersUpEachTurn" } score += 10 @@ -146,7 +146,7 @@ Battle::AI::Handlers::MoveFailureCheck.copy("RaiseUserSpDef1", "RaiseUserSpDef1PowerUpElectricMove") Battle::AI::Handlers::MoveEffectScore.add("RaiseUserSpDef1PowerUpElectricMove", proc { |score, move, user, ai, battle| - score = ai.get_score_for_user_stat_raise(score) + score = ai.get_score_for_target_stat_raise(score, user, move.move.statUp) if user.check_for_move { |m| m.damagingMove? && m.type == :ELECTRIC } score += 10 end @@ -193,7 +193,7 @@ Battle::AI::Handlers::MoveFailureCheck.copy("RaiseUserSpeed2", "RaiseUserSpeed2LowerUserWeight") Battle::AI::Handlers::MoveEffectScore.add("RaiseUserSpeed2LowerUserWeight", proc { |score, move, user, ai, battle| - score = ai.get_score_for_user_stat_raise(score) + score = ai.get_score_for_target_stat_raise(score, user, move.move.statUp) if ai.trainer.medium_skill? # TODO: Take into account weight-modifying items/abilities? This "> 1" # line can probably ignore them, but these moves' powers will change @@ -274,7 +274,7 @@ Battle::AI::Handlers::MoveFailureCheck.copy("RaiseUserEvasion2", "RaiseUserEvasion2MinimizeUser") Battle::AI::Handlers::MoveEffectScore.add("RaiseUserEvasion2MinimizeUser", proc { |score, move, user, ai, battle| - score = ai.get_score_for_user_stat_raise(score) + score = ai.get_score_for_target_stat_raise(score, user, move.move.statUp) if ai.trainer.medium_skill? && !user.effects[PBEffects::Minimize] ai.each_foe_battler(user.side) do |b, i| # Moves that do double damage and (in Gen 6+) have perfect accuracy @@ -368,7 +368,7 @@ Battle::AI::Handlers::MoveEffectScore.copy("RaiseUserAtkSpAtk1", "RaiseUserAtkSpAtk1Or2InSun") #=============================================================================== -# TODO: Review score modifiers. +# #=============================================================================== Battle::AI::Handlers::MoveFailureCheck.add("LowerUserDefSpDef1RaiseUserAtkSpAtkSpd2", proc { |move, user, ai, battle| @@ -388,21 +388,9 @@ Battle::AI::Handlers::MoveFailureCheck.add("LowerUserDefSpDef1RaiseUserAtkSpAtkS ) Battle::AI::Handlers::MoveEffectScore.add("LowerUserDefSpDef1RaiseUserAtkSpAtkSpd2", proc { |score, move, user, ai, battle| - score -= user.stages[:ATTACK] * 20 - score -= user.stages[:SPEED] * 20 - score -= user.stages[:SPECIAL_ATTACK] * 20 - score += user.stages[:DEFENSE] * 10 - score += user.stages[:SPECIAL_DEFENSE] * 10 - if ai.trainer.medium_skill? - hasDamagingAttack = false - user.battler.eachMove do |m| - next if !m.damagingMove? - hasDamagingAttack = true - break - end - score += 20 if hasDamagingAttack - end - next score + score = ai.get_score_for_target_stat_raise(score, user, move.move.statUp) + next score if score == Battle::AI::MOVE_USELESS_SCORE + next ai.get_score_for_target_stat_drop(score, user, move.move.statDown, false) } ) @@ -463,7 +451,7 @@ Battle::AI::Handlers::MoveEffectScore.copy("RaiseUserAtkSpAtk1", "RaiseUserMainStats1") #=============================================================================== -# TODO: Review score modifiers. +# #=============================================================================== Battle::AI::Handlers::MoveFailureCheck.add("RaiseUserMainStats1LoseThirdOfTotalHP", proc { |move, user, ai, battle| @@ -479,12 +467,12 @@ Battle::AI::Handlers::MoveFailureCheck.add("RaiseUserMainStats1LoseThirdOfTotalH ) Battle::AI::Handlers::MoveEffectScore.add("RaiseUserMainStats1LoseThirdOfTotalHP", proc { |score, move, user, ai, battle| - next 0 if !battle.moldBreaker && user.has_active_ability?(:CONTRARY) - score += 30 if ai.trainer.high_skill? && user.hp >= user.totalhp * 0.75 - GameData::Stat.each_main_battle { |s| score += 10 if user.stages[s.id] <= 0 } - if ai.trainer.medium_skill? - hasDamagingAttack = user.battler.moves.any? { |m| next m&.damagingMove? } - score += 20 if hasDamagingAttack + # Score for stat increase + score = ai.get_score_for_target_stat_raise(score, user, move.move.statUp) + next score if score == Battle::AI::MOVE_USELESS_SCORE + # Score for losing HP + if user.hp <= user.totalhp * 0.75 + score -= 30 * (user.totalhp - user.hp) / user.totalhp end next score } @@ -507,15 +495,11 @@ Battle::AI::Handlers::MoveFailureCheck.add("RaiseUserMainStats1TrapUserInBattle" ) Battle::AI::Handlers::MoveEffectScore.add("RaiseUserMainStats1TrapUserInBattle", proc { |score, move, user, ai, battle| - next 0 if !battle.moldBreaker && user.has_active_ability?(:CONTRARY) - if ai.trainer.high_skill? - score -= 50 if user.hp <= user.totalhp / 2 - score += 30 if user.battler.trappedInBattle? - end - GameData::Stat.each_main_battle { |s| score += 10 if user.stages[s.id] <= 0 } - if ai.trainer.medium_skill? - hasDamagingAttack = user.battler.moves.any? { |m| next m&.damagingMove? } - score += 20 if hasDamagingAttack + # Score for stat increase + score = ai.get_score_for_target_stat_raise(score, user, move.move.statUp) + # Score for trapping user in battle + if ai.trainer.medium_skill? && !user.battler.trappedInBattle? + score -= 10 if user.hp <= user.totalhp / 2 end next score } @@ -531,113 +515,85 @@ Battle::AI::Handlers::MoveEffectScore.add("StartRaiseUserAtk1WhenDamaged", ) #=============================================================================== -# TODO: Review score modifiers. +# #=============================================================================== Battle::AI::Handlers::MoveEffectScore.add("LowerUserAttack1", proc { |score, move, user, ai, battle| - next score + user.stages[:ATTACK] * 10 + next ai.get_score_for_target_stat_drop(score, user, move.move.statDown) } ) #=============================================================================== -# TODO: Review score modifiers. +# #=============================================================================== Battle::AI::Handlers::MoveEffectScore.copy("LowerUserAttack1", "LowerUserAttack2") #=============================================================================== -# TODO: Review score modifiers. +# #=============================================================================== -Battle::AI::Handlers::MoveEffectScore.add("LowerUserDefense1", - proc { |score, move, user, ai, battle| - next score + user.stages[:DEFENSE] * 10 - } -) +Battle::AI::Handlers::MoveEffectScore.copy("LowerUserAttack1", + "LowerUserDefense1") #=============================================================================== -# TODO: Review score modifiers. +# #=============================================================================== Battle::AI::Handlers::MoveEffectScore.copy("LowerUserDefense1", "LowerUserDefense2") #=============================================================================== -# TODO: Review score modifiers. +# #=============================================================================== -Battle::AI::Handlers::MoveEffectScore.add("LowerUserSpAtk1", - proc { |score, move, user, ai, battle| - next score + user.stages[:SPECIAL_ATTACK] * 10 - } -) +Battle::AI::Handlers::MoveEffectScore.copy("LowerUserAttack1", + "LowerUserSpAtk1") #=============================================================================== -# TODO: Review score modifiers. +# #=============================================================================== Battle::AI::Handlers::MoveEffectScore.copy("LowerUserSpAtk1", "LowerUserSpAtk2") #=============================================================================== -# TODO: Review score modifiers. +# #=============================================================================== -Battle::AI::Handlers::MoveEffectScore.add("LowerUserSpDef1", - proc { |score, move, user, ai, battle| - next score + user.stages[:SPECIAL_DEFENSE] * 10 - } -) +Battle::AI::Handlers::MoveEffectScore.copy("LowerUserDefense1", + "LowerUserSpDef1") #=============================================================================== -# TODO: Review score modifiers. +# #=============================================================================== Battle::AI::Handlers::MoveEffectScore.copy("LowerUserSpDef1", "LowerUserSpDef2") #=============================================================================== -# TODO: Review score modifiers. +# #=============================================================================== -Battle::AI::Handlers::MoveEffectScore.add("LowerUserSpeed1", - proc { |score, move, user, ai, battle| - next score + user.stages[:SPECIAL_DEFENSE] * 10 - } -) +Battle::AI::Handlers::MoveEffectScore.copy("LowerUserAttack1", + "LowerUserSpeed1") #=============================================================================== -# TODO: Review score modifiers. +# #=============================================================================== Battle::AI::Handlers::MoveEffectScore.copy("LowerUserSpeed1", "LowerUserSpeed2") #=============================================================================== -# TODO: Review score modifiers. +# #=============================================================================== -Battle::AI::Handlers::MoveEffectScore.add("LowerUserAtkDef1", - proc { |score, move, user, ai, battle| - avg = user.stages[:ATTACK] * 10 - avg += user.stages[:DEFENSE] * 10 - next score + avg / 2 - } -) +Battle::AI::Handlers::MoveEffectScore.copy("LowerUserAttack1", + "LowerUserAtkDef1") #=============================================================================== -# TODO: Review score modifiers. +# #=============================================================================== -Battle::AI::Handlers::MoveEffectScore.add("LowerUserDefSpDef1", - proc { |score, move, user, ai, battle| - avg = user.stages[:DEFENSE] * 10 - avg += user.stages[:SPECIAL_DEFENSE] * 10 - next score + avg / 2 - } -) +Battle::AI::Handlers::MoveEffectScore.copy("LowerUserAttack1", + "LowerUserDefSpDef1") #=============================================================================== -# TODO: Review score modifiers. +# #=============================================================================== -Battle::AI::Handlers::MoveEffectScore.add("LowerUserDefSpDefSpd1", - proc { |score, move, user, ai, battle| - avg = user.stages[:DEFENSE] * 10 - avg += user.stages[:SPEED] * 10 - avg += user.stages[:SPECIAL_DEFENSE] * 10 - next score + (avg / 3).floor - } -) +Battle::AI::Handlers::MoveEffectScore.copy("LowerUserAttack1", + "LowerUserDefSpDefSpd1") #=============================================================================== # TODO: Review score modifiers. @@ -648,6 +604,11 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("RaiseTargetAttack1", !target.battler.pbCanRaiseStatStage?(:ATTACK, user.battler, move.move) } ) +Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("RaiseTargetAttack1", + proc { |score, move, user, target, ai, battle| + next ai.get_score_for_target_stat_raise(score, target, [:ATTACK, 1]) + } +) #=============================================================================== # TODO: Review score modifiers. @@ -660,8 +621,12 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("RaiseTargetAttack2Confu ) Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("RaiseTargetAttack2ConfuseTarget", proc { |score, move, user, target, ai, battle| - next Battle::AI::MOVE_USELESS_SCORE if !target.battler.pbCanConfuse?(user.battler, false) - next score + 30 if target.stages[:ATTACK] < 0 + next Battle::AI::MOVE_USELESS_SCORE if !target.battler.pbCanConfuse?(user.battler, false, move.move) + # Score for stat raise + stat_score = ai.get_score_for_target_stat_raise(score, target, [:ATTACK, 2], false) + # Score for confusing the target + next Battle::AI::Handlers.apply_move_effect_against_target_score( + "ConfuseTarget", score, move, user, target, ai, battle) } ) @@ -676,13 +641,17 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("RaiseTargetSpAtk1Confus ) Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("RaiseTargetSpAtk1ConfuseTarget", proc { |score, move, user, target, ai, battle| - next Battle::AI::MOVE_USELESS_SCORE if !target.battler.pbCanConfuse?(user.battler, false) - next score + 30 if target.stages[:SPECIAL_ATTACK] < 0 + next Battle::AI::MOVE_USELESS_SCORE if !target.battler.pbCanConfuse?(user.battler, false, move.move) + # Score for stat raise + stat_score = ai.get_score_for_target_stat_raise(score, target, [:SPECIAL_ATTACK, 1], false) + # Score for confusing the target + next Battle::AI::Handlers.apply_move_effect_against_target_score( + "ConfuseTarget", score, move, user, target, ai, battle) } ) #=============================================================================== -# TODO: Review score modifiers. +# #=============================================================================== Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("RaiseTargetSpDef1", proc { |move, user, target, ai, battle| @@ -691,7 +660,7 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("RaiseTargetSpDef1", ) Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("RaiseTargetSpDef1", proc { |score, move, user, target, ai, battle| - next score - target.stages[:SPECIAL_DEFENSE] * 10 + next ai.get_score_for_target_stat_raise(score, target, [:SPECIAL_DEFENSE, 1]) } ) @@ -720,7 +689,7 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("RaiseTargetRandomStat2", ) #=============================================================================== -# TODO: Review score modifiers. +# #=============================================================================== Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("RaiseTargetAtkSpAtk2", proc { |move, user, target, ai, battle| @@ -731,10 +700,7 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("RaiseTargetAtkSpAtk2", Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("RaiseTargetAtkSpAtk2", proc { |score, move, user, target, ai, battle| next Battle::AI::MOVE_USELESS_SCORE if target.opposes?(user) - next Battle::AI::MOVE_USELESS_SCORE if !battle.moldBreaker && target.has_active_ability?(:CONTRARY) - score -= target.stages[:ATTACK] * 10 - score -= target.stages[:SPECIAL_ATTACK] * 10 - next score + next ai.get_score_for_target_stat_raise(score, target, [:ATTACK, 2, :SPECIAL_ATTACK, 2]) } ) @@ -749,7 +715,7 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("LowerTargetAttack1", ) Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("LowerTargetAttack1", proc { |score, move, user, target, ai, battle| - next ai.get_score_for_target_stat_drop(score) + next ai.get_score_for_target_stat_drop(score, target, move.move.statDown) } ) @@ -911,7 +877,7 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("LowerTargetSpeed1MakeTa Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("LowerTargetSpeed1MakeTargetWeakerToFire", proc { |score, move, user, target, ai, battle| # Score for stat drop - score = ai.get_score_for_target_stat_drop(score) + score = ai.get_score_for_target_stat_drop(score, target, move.move.statDown) # Score for adding weakness to Fire if !target.effects[PBEffects::TarShot] score += 20 if user.battler.moves.any? { |m| m.damagingMove? && m.pbCalcType(user.battler) == :FIRE } @@ -997,7 +963,7 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("LowerTargetEvasion1Remo Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("LowerTargetEvasion1RemoveSideEffects", proc { |score, move, user, target, ai, battle| # Score for stat drop - score = ai.get_score_for_target_stat_drop(score) + score = ai.get_score_for_target_stat_drop(score, target, move.move.statDown) # Score for removing side effects/terrain score += 30 if target.pbOwnSide.effects[PBEffects::AuroraVeil] > 0 || target.pbOwnSide.effects[PBEffects::Reflect] > 0 || @@ -1089,13 +1055,7 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("RaiseAlliesAtkDef1", Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("RaiseAlliesAtkDef1", proc { |score, move, user, target, ai, battle| user.battler.allAllies.each do |b| - if !battle.moldBreaker && b.hasActiveAbility?(:CONTRARY) - score -= 40 - else - score += 10 - score -= b.stages[:ATTACK] * 10 - score -= b.stages[:SPECIAL_ATTACK] * 10 - end + score = ai.get_score_for_target_stat_raise(score, b, [:ATTACK, 1, :DEFENSE, 1]) end next score } @@ -1125,13 +1085,10 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("RaisePlusMinusUserAndAl ) Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("RaisePlusMinusUserAndAlliesAtkSpAtk1", proc { |score, move, user, target, ai, battle| +# score = ai.get_score_for_target_stat_raise(score, user, [:ATTACK, 1, :SPECIAL_ATTACK, 1], false) user.battler.allAllies.each do |b| - next if b.statStageAtMax?(:ATTACK) && b.statStageAtMax?(:SPECIAL_ATTACK) - score -= b.stages[:ATTACK] * 10 - score -= b.stages[:SPECIAL_ATTACK] * 10 + score = ai.get_score_for_target_stat_raise(score, b, [:ATTACK, 1, :SPECIAL_ATTACK, 1], false) end - score -= user.stages[:ATTACK] * 10 - score -= user.stages[:SPECIAL_ATTACK] * 10 next score } ) @@ -1250,9 +1207,11 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("UserTargetSwapAtkSpAtkSt target_attack = target.stages[:ATTACK] target_spatk = target.stages[:SPECIAL_ATTACK] next Battle::AI::MOVE_USELESS_SCORE if user_attack >= target_attack && user_spatk >= target_spatk - next score - 20 if user_attack + user_spatk <= target_attack + target_spatk - score += (target_attack - user_attack) * 10 - score += (target_spatk - user_spatk) * 10 + next score - 20 if user_attack + user_spatk >= target_attack + target_spatk + # TODO: Check whether the user has physical/special moves that will be + # stronger after the swap, and vice versa for the target? + score += (target_attack - user_attack) * 5 + score += (target_spatk - user_spatk) * 5 next score } ) @@ -1270,9 +1229,11 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("UserTargetSwapDefSpDefSt target_def = target.stages[:DEFENSE] target_spdef = target.stages[:SPECIAL_DEFENSE] next Battle::AI::MOVE_USELESS_SCORE if user_def >= target_def && user_spdef >= target_spdef - next score - 20 if user_def + user_spdef <= target_def + target_spdef - score += (target_def - user_def) * 10 - score += (target_spdef - user_spdef) * 10 + next score - 20 if user_def + user_spdef >= target_def + target_spdef + # TODO: Check whether the target has physical/special moves that will be + # more resisted after the swap, and vice versa for the user? + score += (target_def - user_def) * 5 + score += (target_spdef - user_spdef) * 5 next score } ) @@ -1493,15 +1454,13 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("UserTargetAverageBaseDef ) #=============================================================================== -# TODO: Review score modifiers. +# #=============================================================================== Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("UserTargetAverageHP", proc { |score, move, user, target, ai, battle| - if user.hp >= (user.hp + target.hp) / 2 - score -= 25 - else - score += 25 - end + next Battle::AI::MOVE_USELESS_SCORE if user.hp >= (user.hp + target.hp) / 2 + mult = (user.hp + target.hp) / (2.0 * user.hp) + score += 10 * mult if mult >= 1.2 next score } ) diff --git a/Data/Scripts/011_Battle/005_AI/053_AI_MoveHandlers_BattlerOther.rb b/Data/Scripts/011_Battle/005_AI/053_AI_MoveHandlers_BattlerOther.rb index ec01821e9..fb0d0504f 100644 --- a/Data/Scripts/011_Battle/005_AI/053_AI_MoveHandlers_BattlerOther.rb +++ b/Data/Scripts/011_Battle/005_AI/053_AI_MoveHandlers_BattlerOther.rb @@ -591,11 +591,6 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("FlinchTarget", #=============================================================================== # #=============================================================================== -Battle::AI::Handlers::MoveFailureCheck.add("FlinchTargetFailsIfUserNotAsleep", - proc { |move, user, ai, battle| - next true if !user.battler.asleep? - } -) Battle::AI::Handlers::MoveEffectAgainstTargetScore.copy("FlinchTarget", "FlinchTargetFailsIfUserNotAsleep") diff --git a/Data/Scripts/011_Battle/005_AI/055_AI_MoveHandlers_MultiHit.rb b/Data/Scripts/011_Battle/005_AI/055_AI_MoveHandlers_MultiHit.rb index 5a7144186..fb83ac0cb 100644 --- a/Data/Scripts/011_Battle/005_AI/055_AI_MoveHandlers_MultiHit.rb +++ b/Data/Scripts/011_Battle/005_AI/055_AI_MoveHandlers_MultiHit.rb @@ -321,7 +321,7 @@ Battle::AI::Handlers::MoveFailureCheck.copy("RaiseUserAtkDef1", Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TwoTurnAttackRaiseUserSpAtkSpDefSpd2", proc { |score, move, user, target, ai, battle| # Score for raising user's stats - score = ai.get_score_for_user_stat_raise(score) + score = ai.get_score_for_target_stat_raise(score, user, move.move.statUp) # Score for being a two turn attack score = Battle::AI::Handlers.apply_move_effect_against_target_score("TwoTurnAttack", score, move, user, target, ai, battle) diff --git a/Data/Scripts/011_Battle/005_AI/058_AI_MoveHandlers_ChangeMoveEffect.rb b/Data/Scripts/011_Battle/005_AI/058_AI_MoveHandlers_ChangeMoveEffect.rb index 3f50589c8..88a07541f 100644 --- a/Data/Scripts/011_Battle/005_AI/058_AI_MoveHandlers_ChangeMoveEffect.rb +++ b/Data/Scripts/011_Battle/005_AI/058_AI_MoveHandlers_ChangeMoveEffect.rb @@ -440,11 +440,10 @@ Battle::AI::Handlers::MoveFailureCheck.add("UseRandomMoveFromUserParty", ) #=============================================================================== -# TODO: Review score modifiers. +# #=============================================================================== Battle::AI::Handlers::MoveFailureCheck.add("UseRandomUserMoveIfAsleep", proc { |move, user, ai, battle| - next true if !user.battler.asleep? will_fail = true user.battler.eachMoveWithIndex do |m, i| next if move.move.moveBlacklist.include?(m.function) diff --git a/Data/Scripts/011_Battle/005_AI/059_AI_MoveHandlers_SwitchingActing.rb b/Data/Scripts/011_Battle/005_AI/059_AI_MoveHandlers_SwitchingActing.rb index 9b06b50ec..fd8eecf7f 100644 --- a/Data/Scripts/011_Battle/005_AI/059_AI_MoveHandlers_SwitchingActing.rb +++ b/Data/Scripts/011_Battle/005_AI/059_AI_MoveHandlers_SwitchingActing.rb @@ -322,7 +322,7 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("TargetUsesItsLastUsedMo proc { |move, user, target, ai, battle| next true if !target.battler.lastRegularMoveUsed || !target.battler.pbHasMove?(target.battler.lastRegularMoveUsed) - next true if target.usingMultiTurnAttack? + next true if target.battler.usingMultiTurnAttack? next true if move.move.moveBlacklist.include?(GameData::Move.get(target.battler.lastRegularMoveUsed).function_code) idxMove = -1 target.battler.eachMoveWithIndex do |m, i| @@ -522,11 +522,6 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("AllBattlersLoseHalfHPUs next true if target.hp <= 1 } ) -Battle::AI::Handlers::MoveEffectScore.add("AllBattlersLoseHalfHPUserSkipsNextTurn", - proc { |score, move, user, ai, battle| - next score + 10 # Shadow moves are more preferable - } -) Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("AllBattlersLoseHalfHPUserSkipsNextTurn", proc { |score, move, user, target, ai, battle| next score + 20 if target.hp >= target.totalhp / 2 @@ -538,9 +533,7 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("AllBattlersLoseHalfHPUse #=============================================================================== Battle::AI::Handlers::MoveEffectScore.add("UserLosesHalfHP", proc { |score, move, user, ai, battle| - score += 20 # Shadow moves are more preferable - score -= 40 - next score + next score - 40 } ) @@ -551,7 +544,6 @@ Battle::AI::Handlers::MoveFailureCheck.copy("StartSunWeather", "StartShadowSkyWeather") Battle::AI::Handlers::MoveEffectScore.add("StartShadowSkyWeather", proc { |score, move, user, ai, battle| - score += 20 # Shadow moves are more preferable next Battle::AI::MOVE_USELESS_SCORE if battle.pbCheckGlobalAbility(:AIRLOCK) || battle.pbCheckGlobalAbility(:CLOUDNINE) score += 10 if battle.field.weather != :None # Prefer replacing another weather @@ -577,7 +569,6 @@ Battle::AI::Handlers::MoveFailureCheck.add("RemoveAllScreens", ) Battle::AI::Handlers::MoveEffectScore.add("RemoveAllScreens", proc { |score, move, user, ai, battle| - score += 20 # Shadow moves are more preferable if user.pbOpposingSide.effects[PBEffects::AuroraVeil] > 0 || user.pbOpposingSide.effects[PBEffects::Reflect] > 0 || user.pbOpposingSide.effects[PBEffects::LightScreen] > 0 || 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 174c06318..ee4add95f 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 @@ -46,8 +46,8 @@ Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:add_predicted_damage, proc { |score, move, user, target, ai, battle| if move.damagingMove? dmg = move.rough_damage - score += [15.0 * dmg / target.hp, 20].min - score += 15 if dmg > target.hp * 1.1 # Predicted to KO the target + score += [20.0 * dmg / target.hp, 25].min + score += 10 if dmg > target.hp * 1.1 # Predicted to KO the target next score.to_i end } @@ -93,6 +93,12 @@ Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:target_semi_invulnerabl } ) +#=============================================================================== +# TODO: Review score modifier. +#=============================================================================== +# TODO: Less prefer two-turn moves, as the foe can see it coming and prepare for +# it. + #=============================================================================== # If target is frozen, don't prefer moves that could thaw them. # TODO: Review score modifier. diff --git a/Data/Scripts/011_Battle/005_AI/102_AIBattler.rb b/Data/Scripts/011_Battle/005_AI/102_AIBattler.rb index b5106562f..39cf3f052 100644 --- a/Data/Scripts/011_Battle/005_AI/102_AIBattler.rb +++ b/Data/Scripts/011_Battle/005_AI/102_AIBattler.rb @@ -267,57 +267,6 @@ class Battle::AI::AIBattler return ret end - def immune_to_move? - user = @ai.user - user_battler = user.battler - move = @ai.move - # TODO: Add consideration of user's Mold Breaker. - move_type = move.rough_type - typeMod = effectiveness_of_type_against_battler(move_type, user) - # Type effectiveness - return true if move.damagingMove? && Effectiveness.ineffective?(typeMod) - # Immunity due to ability/item/other effects - if @ai.trainer.medium_skill? - case move_type - when :GROUND - # TODO: Split target.airborne? into separate parts to allow different - # skill levels to apply to each part. - return true if @battler.airborne? && !move.move.hitsFlyingTargets? - when :FIRE - return true if has_active_ability?(:FLASHFIRE) - when :WATER - return true if has_active_ability?([:DRYSKIN, :STORMDRAIN, :WATERABSORB]) - when :GRASS - return true if has_active_ability?(:SAPSIPPER) - when :ELECTRIC - return true if has_active_ability?([:LIGHTNINGROD, :MOTORDRIVE, :VOLTABSORB]) - end - return true if move.damagingMove? && Effectiveness.not_very_effective?(typeMod) && - has_active_ability?(:WONDERGUARD) - return true if move.damagingMove? && user.index != @index && !opposes?(user) && - has_active_ability?(:TELEPATHY) - return true if move.statusMove? && move.move.canMagicCoat? && - !@ai.battle.moldBreaker && has_active_ability?(:MAGICBOUNCE) && - opposes?(user) - return true if move.move.soundMove? && !@ai.battle.moldBreaker && has_active_ability?(:SOUNDPROOF) - return true if move.move.bombMove? && has_active_ability?(:BULLETPROOF) - if move.move.powderMove? - return true if has_type?(:GRASS) - return true if !@ai.battle.moldBreaker && has_active_ability?(:OVERCOAT) - return true if has_active_ability?(:SAFETYGOGGLES) - end - return true if move.move.statusMove? && @battler.effects[PBEffects::Substitute] > 0 && - !move.move.ignoresSubstitute?(user) && user.index != @index - return true if move.move.statusMove? && Settings::MECHANICS_GENERATION >= 7 && - user.has_active_ability?(:PRANKSTER) && has_type?(:DARK) && - opposes?(user) - return true if move.move.priority > 0 && @ai.battle.field.terrain == :Psychic && - @battler.affectedByTerrain? && opposes?(user) - # TODO: Dazzling/Queenly Majesty go here. - end - return false - end - #============================================================================= def can_switch_lax? diff --git a/Data/Scripts/011_Battle/005_AI/103_AIMove.rb b/Data/Scripts/011_Battle/005_AI/103_AIMove.rb index a4adf2f39..9cd04ab5d 100644 --- a/Data/Scripts/011_Battle/005_AI/103_AIMove.rb +++ b/Data/Scripts/011_Battle/005_AI/103_AIMove.rb @@ -40,6 +40,10 @@ class Battle::AI::AIMove #============================================================================= + def pbTarget(_user) + return @move.pbTarget(_user) + end + # Returns whether this move targets multiple battlers. def targets_multiple_battlers? user_battler = @ai_battler.battler