From a22c5ea89ca4231deffbcdd97572f155b5df4d0d Mon Sep 17 00:00:00 2001 From: Maruno17 Date: Sun, 9 Apr 2023 22:26:48 +0100 Subject: [PATCH] More work on the AI, refactored stat stage multipliers --- .../002_Battler/001_Battle_Battler.rb | 13 +- .../002_Battler/005_Battler_StatStages.rb | 8 +- .../003_Move/003_Move_UsageCalculations.rb | 26 +- .../003_Move/006_MoveEffects_BattlerStats.rb | 4 +- .../008_MoveEffects_MoveAttributes.rb | 32 +- .../003_Move/010_MoveEffects_Healing.rb | 7 +- .../011_Battle/005_AI/004_AI_ChooseMove.rb | 1 - .../005_AI/020_AI_Move_EffectScoresGeneric.rb | 76 ++- .../070_AI_MoveHandlers_GeneralModifiers.rb | 528 +++++++++--------- .../011_Battle/005_AI/101_AITrainer.rb | 4 +- .../011_Battle/005_AI/102_AIBattler.rb | 242 ++++---- Data/Scripts/011_Battle/005_AI/103_AIMove.rb | 52 +- .../051_AI_MoveHandlers_Misc.rb | 5 +- .../052_AI_MoveHandlers_BattlerStats.rb | 2 - .../053_AI_MoveHandlers_BattlerOther.rb | 167 +++--- .../054_AI_MoveHandlers_MoveAttributes.rb | 65 +-- .../055_AI_MoveHandlers_MultiHit.rb | 20 +- .../056_AI_MoveHandlers_Healing.rb | 8 +- .../057_AI_MoveHandlers_Items.rb | 5 +- .../008_Battle_AbilityEffects.rb | 2 +- .../005_Debug_BattlePkmnCommands.rb | 2 +- 21 files changed, 618 insertions(+), 651 deletions(-) diff --git a/Data/Scripts/011_Battle/002_Battler/001_Battle_Battler.rb b/Data/Scripts/011_Battle/002_Battler/001_Battle_Battler.rb index 9df8aa6a8..da99f60ff 100644 --- a/Data/Scripts/011_Battle/002_Battler/001_Battle_Battler.rb +++ b/Data/Scripts/011_Battle/002_Battler/001_Battle_Battler.rb @@ -45,6 +45,13 @@ class Battle::Battler attr_accessor :canRestoreIceFace # Whether Hail started in the round attr_accessor :damageState + # These arrays should all have the same number of values in them + STAT_STAGE_MULTIPLIERS = [2, 2, 2, 2, 2, 2, 2, 3, 4, 5, 6, 7, 8] + STAT_STAGE_DIVISORS = [8, 7, 6, 5, 4, 3, 2, 2, 2, 2, 2, 2, 2] + ACC_EVA_STAGE_MULTIPLIERS = [3, 3, 3, 3, 3, 3, 3, 4, 5, 6, 7, 8, 9] + ACC_EVA_STAGE_DIVISORS = [9, 8, 7, 6, 5, 4, 3, 3, 3, 3, 3, 3, 3] + STAT_STAGE_MAXIMUM = 6 # Is also the minimum (-6) + #============================================================================= # Complex accessors #============================================================================= @@ -240,10 +247,8 @@ class Battle::Battler #============================================================================= def pbSpeed return 1 if fainted? - stageMul = [2, 2, 2, 2, 2, 2, 2, 3, 4, 5, 6, 7, 8] - stageDiv = [8, 7, 6, 5, 4, 3, 2, 2, 2, 2, 2, 2, 2] - stage = @stages[:SPEED] + 6 - speed = @speed * stageMul[stage] / stageDiv[stage] + stage = @stages[:SPEED] + STAT_STAGE_MAXIMUM + speed = @speed * STAT_STAGE_MULTIPLIERS[stage] / STAT_STAGE_DIVISORS[stage] speedMult = 1.0 # Ability effects that alter calculated Speed if abilityActive? diff --git a/Data/Scripts/011_Battle/002_Battler/005_Battler_StatStages.rb b/Data/Scripts/011_Battle/002_Battler/005_Battler_StatStages.rb index 27afc5b98..134f56f00 100644 --- a/Data/Scripts/011_Battle/002_Battler/005_Battler_StatStages.rb +++ b/Data/Scripts/011_Battle/002_Battler/005_Battler_StatStages.rb @@ -3,7 +3,7 @@ class Battle::Battler # Increase stat stages #============================================================================= def statStageAtMax?(stat) - return @stages[stat] >= 6 + return @stages[stat] >= STAT_STAGE_MAXIMUM end def pbCanRaiseStatStage?(stat, user = nil, move = nil, showFailMsg = false, ignoreContrary = false) @@ -33,7 +33,7 @@ class Battle::Battler increment *= 2 if hasActiveAbility?(:SIMPLE) end # Change the stat stage - increment = [increment, 6 - @stages[stat]].min + increment = [increment, STAT_STAGE_MAXIMUM - @stages[stat]].min if increment > 0 stat_name = GameData::Stat.get(stat).name new = @stages[stat] + increment @@ -117,7 +117,7 @@ class Battle::Battler # Decrease stat stages #============================================================================= def statStageAtMin?(stat) - return @stages[stat] <= -6 + return @stages[stat] <= -STAT_STAGE_MAXIMUM end def pbCanLowerStatStage?(stat, user = nil, move = nil, showFailMsg = false, @@ -183,7 +183,7 @@ class Battle::Battler increment *= 2 if hasActiveAbility?(:SIMPLE) end # Change the stat stage - increment = [increment, 6 + @stages[stat]].min + increment = [increment, STAT_STAGE_MAXIMUM + @stages[stat]].min if increment > 0 stat_name = GameData::Stat.get(stat).name new = @stages[stat] - increment diff --git a/Data/Scripts/011_Battle/003_Move/003_Move_UsageCalculations.rb b/Data/Scripts/011_Battle/003_Move/003_Move_UsageCalculations.rb index 1d8409e02..5ec2a1aca 100644 --- a/Data/Scripts/011_Battle/003_Move/003_Move_UsageCalculations.rb +++ b/Data/Scripts/011_Battle/003_Move/003_Move_UsageCalculations.rb @@ -101,10 +101,11 @@ class Battle::Move # Check if move can't miss return true if modifiers[:base_accuracy] == 0 # Calculation - accStage = [[modifiers[:accuracy_stage], -6].max, 6].min + 6 - evaStage = [[modifiers[:evasion_stage], -6].max, 6].min + 6 - stageMul = [3, 3, 3, 3, 3, 3, 3, 4, 5, 6, 7, 8, 9] - stageDiv = [9, 8, 7, 6, 5, 4, 3, 3, 3, 3, 3, 3, 3] + max_stage = Battle::Battler::STAT_STAGE_MAXIMUM + accStage = [[modifiers[:accuracy_stage], -max_stage].max, max_stage].min + max_stage + evaStage = [[modifiers[:evasion_stage], -max_stage].max, max_stage].min + max_stage + stageMul = Battle::Battler::ACC_EVA_STAGE_MULTIPLIERS + stageDiv = Battle::Battler::ACC_EVA_STAGE_DIVISORS accuracy = 100.0 * stageMul[accStage] / stageDiv[accStage] evasion = 100.0 * stageMul[evaStage] / stageDiv[evaStage] accuracy = (accuracy * modifiers[:accuracy_multiplier]).round @@ -226,13 +227,13 @@ class Battle::Move def pbModifyDamage(damageMult, user, target); return damageMult; end def pbGetAttackStats(user, target) - return user.spatk, user.stages[:SPECIAL_ATTACK] + 6 if specialMove? - return user.attack, user.stages[:ATTACK] + 6 + return user.spatk, user.stages[:SPECIAL_ATTACK] + Battle::Battler::STAT_STAGE_MAXIMUM if specialMove? + return user.attack, user.stages[:ATTACK] + Battle::Battler::STAT_STAGE_MAXIMUM end def pbGetDefenseStats(user, target) - return target.spdef, target.stages[:SPECIAL_DEFENSE] + 6 if specialMove? - return target.defense, target.stages[:DEFENSE] + 6 + return target.spdef, target.stages[:SPECIAL_DEFENSE] + Battle::Battler::STAT_STAGE_MAXIMUM if specialMove? + return target.defense, target.stages[:DEFENSE] + Battle::Battler::STAT_STAGE_MAXIMUM end def pbCalcDamage(user, target, numTargets = 1) @@ -241,8 +242,9 @@ class Battle::Move target.damageState.calcDamage = 1 return end - stageMul = [2, 2, 2, 2, 2, 2, 2, 3, 4, 5, 6, 7, 8] - stageDiv = [8, 7, 6, 5, 4, 3, 2, 2, 2, 2, 2, 2, 2] + max_stage = Battle::Battler::STAT_STAGE_MAXIMUM + stageMul = Battle::Battler::STAT_STAGE_MULTIPLIERS + stageDiv = Battle::Battler::STAT_STAGE_DIVISORS # Get the move's type type = @calcType # nil is treated as physical # Calculate whether this hit deals critical damage @@ -252,13 +254,13 @@ class Battle::Move # Calculate user's attack stat atk, atkStage = pbGetAttackStats(user, target) if !target.hasActiveAbility?(:UNAWARE) || @battle.moldBreaker - atkStage = 6 if target.damageState.critical && atkStage < 6 + atkStage = max_stage if target.damageState.critical && atkStage < max_stage atk = (atk.to_f * stageMul[atkStage] / stageDiv[atkStage]).floor end # Calculate target's defense stat defense, defStage = pbGetDefenseStats(user, target) if !user.hasActiveAbility?(:UNAWARE) - defStage = 6 if target.damageState.critical && defStage > 6 + defStage = max_stage if target.damageState.critical && defStage > max_stage defense = (defense.to_f * stageMul[defStage] / stageDiv[defStage]).floor end # Calculate all multiplier effects 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 35cf01bde..5f4665aab 100644 --- a/Data/Scripts/011_Battle/003_Move/006_MoveEffects_BattlerStats.rb +++ b/Data/Scripts/011_Battle/003_Move/006_MoveEffects_BattlerStats.rb @@ -94,14 +94,14 @@ class Battle::Move::MaxUserAttackLoseHalfOfTotalHP < Battle::Move hpLoss = [user.totalhp / 2, 1].max user.pbReduceHP(hpLoss, false, false) if user.hasActiveAbility?(:CONTRARY) - user.stages[@statUp[0]] = -6 + user.stages[@statUp[0]] = -Battle::Battler::STAT_STAGE_MAXIMUM user.statsLoweredThisRound = true user.statsDropped = true @battle.pbCommonAnimation("StatDown", user) @battle.pbDisplay(_INTL("{1} cut its own HP and minimized its {2}!", user.pbThis, GameData::Stat.get(@statUp[0]).name)) else - user.stages[@statUp[0]] = 6 + user.stages[@statUp[0]] = Battle::Battler::STAT_STAGE_MAXIMUM user.statsRaisedThisRound = true @battle.pbCommonAnimation("StatUp", user) @battle.pbDisplay(_INTL("{1} cut its own HP and maximized its {2}!", diff --git a/Data/Scripts/011_Battle/003_Move/008_MoveEffects_MoveAttributes.rb b/Data/Scripts/011_Battle/003_Move/008_MoveEffects_MoveAttributes.rb index 13de16998..03d12eb07 100644 --- a/Data/Scripts/011_Battle/003_Move/008_MoveEffects_MoveAttributes.rb +++ b/Data/Scripts/011_Battle/003_Move/008_MoveEffects_MoveAttributes.rb @@ -1121,17 +1121,18 @@ class Battle::Move::CategoryDependsOnHigherDamagePoisonTarget < Battle::Move::Po def pbOnStartUse(user, targets) target = targets[0] - stageMul = [2, 2, 2, 2, 2, 2, 2, 3, 4, 5, 6, 7, 8] - stageDiv = [8, 7, 6, 5, 4, 3, 2, 2, 2, 2, 2, 2, 2] + max_stage = Battle::Battler::STAT_STAGE_MAXIMUM + stageMul = Battle::Battler::STAT_STAGE_MULTIPLIERS + stageDiv = Battle::Battler::STAT_STAGE_DIVISORS # Calculate user's effective attacking values - attack_stage = user.stages[:ATTACK] + 6 + attack_stage = user.stages[:ATTACK] + max_stage real_attack = (user.attack.to_f * stageMul[attack_stage] / stageDiv[attack_stage]).floor - special_attack_stage = user.stages[:SPECIAL_ATTACK] + 6 + special_attack_stage = user.stages[:SPECIAL_ATTACK] + max_stage real_special_attack = (user.spatk.to_f * stageMul[special_attack_stage] / stageDiv[special_attack_stage]).floor # Calculate target's effective defending values - defense_stage = target.stages[:DEFENSE] + 6 + defense_stage = target.stages[:DEFENSE] + max_stage real_defense = (target.defense.to_f * stageMul[defense_stage] / stageDiv[defense_stage]).floor - special_defense_stage = target.stages[:SPECIAL_DEFENSE] + 6 + special_defense_stage = target.stages[:SPECIAL_DEFENSE] + max_stage real_special_defense = (target.spdef.to_f * stageMul[special_defense_stage] / stageDiv[special_defense_stage]).floor # Perform simple damage calculation physical_damage = real_attack.to_f / real_defense @@ -1166,13 +1167,14 @@ class Battle::Move::CategoryDependsOnHigherDamageIgnoreTargetAbility < Battle::M def pbOnStartUse(user, targets) # Calculate user's effective attacking value - stageMul = [2, 2, 2, 2, 2, 2, 2, 3, 4, 5, 6, 7, 8] - stageDiv = [8, 7, 6, 5, 4, 3, 2, 2, 2, 2, 2, 2, 2] + max_stage = Battle::Battler::STAT_STAGE_MAXIMUM + stageMul = Battle::Battler::STAT_STAGE_MULTIPLIERS + stageDiv = Battle::Battler::STAT_STAGE_DIVISORS atk = user.attack - atkStage = user.stages[:ATTACK] + 6 + atkStage = user.stages[:ATTACK] + max_stage realAtk = (atk.to_f * stageMul[atkStage] / stageDiv[atkStage]).floor spAtk = user.spatk - spAtkStage = user.stages[:SPECIAL_ATTACK] + 6 + spAtkStage = user.stages[:SPECIAL_ATTACK] + max_stage realSpAtk = (spAtk.to_f * stageMul[spAtkStage] / stageDiv[spAtkStage]).floor # Determine move's category @calcCategory = (realAtk > realSpAtk) ? 0 : 1 @@ -1187,7 +1189,7 @@ end #=============================================================================== class Battle::Move::UseUserDefenseInsteadOfUserAttack < Battle::Move def pbGetAttackStats(user, target) - return user.defense, user.stages[:DEFENSE] + 6 + return user.defense, user.stages[:DEFENSE] + Battle::Battler::STAT_STAGE_MAXIMUM end end @@ -1197,8 +1199,8 @@ end #=============================================================================== class Battle::Move::UseTargetAttackInsteadOfUserAttack < Battle::Move def pbGetAttackStats(user, target) - return target.spatk, target.stages[:SPECIAL_ATTACK] + 6 if specialMove? - return target.attack, target.stages[:ATTACK] + 6 + return target.spatk, target.stages[:SPECIAL_ATTACK] + Battle::Battler::STAT_STAGE_MAXIMUM if specialMove? + return target.attack, target.stages[:ATTACK] + Battle::Battler::STAT_STAGE_MAXIMUM end end @@ -1208,7 +1210,7 @@ end #=============================================================================== class Battle::Move::UseTargetDefenseInsteadOfTargetSpDef < Battle::Move def pbGetDefenseStats(user, target) - return target.defense, target.stages[:DEFENSE] + 6 + return target.defense, target.stages[:DEFENSE] + Battle::Battler::STAT_STAGE_MAXIMUM end end @@ -1264,7 +1266,7 @@ class Battle::Move::IgnoreTargetDefSpDefEvaStatStages < Battle::Move def pbGetDefenseStats(user, target) ret1, _ret2 = super - return ret1, 6 # Def/SpDef stat stage + return ret1, Battle::Battler::STAT_STAGE_MAXIMUM # Def/SpDef stat stage end end diff --git a/Data/Scripts/011_Battle/003_Move/010_MoveEffects_Healing.rb b/Data/Scripts/011_Battle/003_Move/010_MoveEffects_Healing.rb index eae6ba8c2..5e1de0c7a 100644 --- a/Data/Scripts/011_Battle/003_Move/010_MoveEffects_Healing.rb +++ b/Data/Scripts/011_Battle/003_Move/010_MoveEffects_Healing.rb @@ -139,10 +139,11 @@ class Battle::Move::HealUserByTargetAttackLowerTargetAttack1 < Battle::Move def pbEffectAgainstTarget(user, target) # Calculate target's effective attack value - stageMul = [2, 2, 2, 2, 2, 2, 2, 3, 4, 5, 6, 7, 8] - stageDiv = [8, 7, 6, 5, 4, 3, 2, 2, 2, 2, 2, 2, 2] + max_stage = Battle::Battler::STAT_STAGE_MAXIMUM + stageMul = Battle::Battler::STAT_STAGE_MULTIPLIERS + stageDiv = Battle::Battler::STAT_STAGE_DIVISORS atk = target.attack - atkStage = target.stages[@statDown[0]] + 6 + atkStage = target.stages[@statDown[0]] + max_stage healAmt = (atk.to_f * stageMul[atkStage] / stageDiv[atkStage]).floor # Reduce target's Attack stat if target.pbCanLowerStatStage?(@statDown[0], user, self) 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 df248fdca..45c802874 100644 --- a/Data/Scripts/011_Battle/005_AI/004_AI_ChooseMove.rb +++ b/Data/Scripts/011_Battle/005_AI/004_AI_ChooseMove.rb @@ -231,7 +231,6 @@ class Battle::AI if targets # Reset the base score for the move (each target will add its own score) score = 0 - # TODO: Distinguish between affected foes and affected allies? affected_targets = 0 # Get a score for the move against each target in turn orig_move = @move.move # In case move is Mirror Move and changes depending on the target diff --git a/Data/Scripts/011_Battle/005_AI/020_AI_Move_EffectScoresGeneric.rb b/Data/Scripts/011_Battle/005_AI/020_AI_Move_EffectScoresGeneric.rb index eb4f6a43e..5f55c8f34 100644 --- a/Data/Scripts/011_Battle/005_AI/020_AI_Move_EffectScoresGeneric.rb +++ b/Data/Scripts/011_Battle/005_AI/020_AI_Move_EffectScoresGeneric.rb @@ -62,7 +62,7 @@ class Battle::AI # Calculate amount that stat will be raised by increment = stat_changes[idx + 1] increment *= 2 if !fixed_change && !@battle.moldBreaker && target.has_active_ability?(:SIMPLE) - increment = [increment, 6 - target.stages[stat]].min # The actual stages gained + increment = [increment, Battle::Battler::STAT_STAGE_MAXIMUM - target.stages[stat]].min # The actual stages gained # Count this as a valid stat raise real_stat_changes.push([stat, increment]) if increment > 0 end @@ -98,8 +98,10 @@ class Battle::AI if !fixed_change return false if !target.battler.pbCanRaiseStatStage?(stat, @user.battler, @move.move) end + # TODO: Not worth it if target is predicted to switch out (except via Baton Pass). # Check if target won't benefit from the stat being raised - # TODO: Exception if target knows Baton Pass/Stored Power? + return true if target.has_move_with_function?("SwitchOutUserPassOnEffects", + "PowerHigherWithUserPositiveStatStages") case stat when :ATTACK return false if !target.check_for_move { |m| m.physicalMove?(m.type) && @@ -146,24 +148,18 @@ class Battle::AI #============================================================================= def get_target_stat_raise_score_generic(score, target, stat_changes, desire_mult = 1) total_increment = stat_changes.sum { |change| change[1] } - # TODO: Just return if the target's foe is predicted to use a phazing move - # (one that switches the target out). - # TODO: Don't prefer if foe is faster than target and is predicted to deal - # lethal damage. - # TODO: Don't prefer if foe is slower than target but is predicted to be - # able to 2HKO the target. - # TODO: Prefer if foe is semi-invulnerable and target is faster (can't hit - # the foe anyway). # Prefer if move is a status move and it's the user's first/second turn if @user.turnCount < 2 && @move.statusMove? score += total_increment * desire_mult * 5 end - # Prefer if user is at high HP, don't prefer if user is at low HP - if target.index != @user.index - score += total_increment * desire_mult * ((100 * @user.hp / @user.totalhp) - 50) / 8 # +6 to -6 per stage + if @trainer.has_skill_flag?("HPAware") + # Prefer if user is at high HP, don't prefer if user is at low HP + if target.index != @user.index + score += total_increment * desire_mult * ((100 * @user.hp / @user.totalhp) - 50) / 8 # +6 to -6 per stage + end + # Prefer if target is at high HP, don't prefer if target is at low HP + score += total_increment * desire_mult * ((100 * target.hp / target.totalhp) - 50) / 8 # +6 to -6 per stage end - # Prefer if target is at high HP, don't prefer if target is at low HP - score += total_increment * desire_mult * ((100 * target.hp / target.totalhp) - 50) / 8 # +6 to -6 per stage # TODO: Look at abilities that trigger upon stat raise. There are none. return score end @@ -173,15 +169,16 @@ class Battle::AI #============================================================================= def get_target_stat_raise_score_one(score, target, stat, increment, desire_mult = 1) # Figure out how much the stat will actually change by - stage_mul = [2, 2, 2, 2, 2, 2, 2, 3, 4, 5, 6, 7, 8] - stage_div = [8, 7, 6, 5, 4, 3, 2, 2, 2, 2, 2, 2, 2] + max_stage = Battle::Battler::STAT_STAGE_MAXIMUM + stage_mul = Battle::Battler::STAT_STAGE_MULTIPLIERS + stage_div = Battle::Battler::STAT_STAGE_DIVISORS if [:ACCURACY, :EVASION].include?(stat) - stage_mul = [3, 3, 3, 3, 3, 3, 3, 4, 5, 6, 7, 8, 9] - stage_div = [9, 8, 7, 6, 5, 4, 3, 3, 3, 3, 3, 3, 3] + stage_mul = Battle::Battler::ACC_EVA_STAGE_MULTIPLIERS + stage_div = Battle::Battler::ACC_EVA_STAGE_DIVISORS end old_stage = target.stages[stat] new_stage = old_stage + increment - inc_mult = (stage_mul[new_stage + 6].to_f * stage_div[old_stage + 6]) / (stage_div[new_stage + 6] * stage_mul[old_stage + 6]) + inc_mult = (stage_mul[new_stage + max_stage].to_f * stage_div[old_stage + max_stage]) / (stage_div[new_stage + max_stage] * stage_mul[old_stage + max_stage]) inc_mult -= 1 inc_mult *= desire_mult # Stat-based score changes @@ -304,7 +301,6 @@ class Battle::AI # inversion does not happen if the move could target a foe but is targeting an # ally, but only because it is inverted in def pbGetMoveScoreAgainstTarget # instead. - # TODO: Revisit this method as parts may need rewriting. #============================================================================= def get_score_for_target_stat_drop(score, target, stat_changes, whole_effect = true, fixed_change = false, ignore_contrary = false) @@ -358,8 +354,8 @@ class Battle::AI end # Calculate amount that stat will be lowered by decrement = stat_changes[idx + 1] - decrement *= 2 if !fixed_change && !@battle.moldBreaker && @user.has_active_ability?(:SIMPLE) - decrement = [decrement, 6 + target.stages[stat]].min # The actual stages lost + decrement *= 2 if !fixed_change && !@battle.moldBreaker && target.has_active_ability?(:SIMPLE) + decrement = [decrement, Battle::Battler::STAT_STAGE_MAXIMUM + target.stages[stat]].min # The actual stages lost # Count this as a valid stat drop real_stat_changes.push([stat, decrement]) if decrement > 0 end @@ -390,12 +386,12 @@ class Battle::AI # TODO: Make sure the move's actual damage category is taken into account, # i.e. CategoryDependsOnHigherDamagePoisonTarget and # CategoryDependsOnHigherDamageIgnoreTargetAbility. - # TODO: Revisit this method as parts may need rewriting. #============================================================================= def stat_drop_worthwhile?(target, stat, fixed_change = false) if !fixed_change return false if !target.battler.pbCanLowerStatStage?(stat, @user.battler, @move.move) end + # TODO: Not worth it if target is predicted to switch out (except via Baton Pass). # Check if target won't benefit from the stat being lowered case stat when :ATTACK @@ -437,46 +433,40 @@ class Battle::AI #============================================================================= # Make score changes based on the general concept of lowering stats at all. - # TODO: Revisit this method as parts may need rewriting. - # TODO: All comments in this method may be inaccurate. #============================================================================= def get_target_stat_drop_score_generic(score, target, stat_changes, desire_mult = 1) total_decrement = stat_changes.sum { |change| change[1] } - # TODO: Just return if target is predicted to switch out (except via Baton Pass). - # TODO: Don't prefer if target is faster than user and is predicted to deal - # lethal damage. - # TODO: Don't prefer if target is slower than user but is predicted to be able - # to 2HKO user. - # TODO: Don't prefer if target is semi-invulnerable and user is faster. # Prefer if move is a status move and it's the user's first/second turn if @user.turnCount < 2 && @move.statusMove? score += total_decrement * desire_mult * 5 end - # Prefer if user is at high HP, don't prefer if user is at low HP - if target.index != @user.index - score += total_decrement * desire_mult * ((100 * @user.hp / @user.totalhp) - 50) / 8 # +6 to -6 per stage + if @trainer.has_skill_flag?("HPAware") + # Prefer if user is at high HP, don't prefer if user is at low HP + if target.index != @user.index + score += total_decrement * desire_mult * ((100 * @user.hp / @user.totalhp) - 50) / 8 # +6 to -6 per stage + end + # Prefer if target is at high HP, don't prefer if target is at low HP + score += total_decrement * desire_mult * ((100 * target.hp / target.totalhp) - 50) / 8 # +6 to -6 per stage end - # Prefer if target is at high HP, don't prefer if target is at low HP - score += total_decrement * desire_mult * ((100 * target.hp / target.totalhp) - 50) / 8 # +6 to -6 per stage # TODO: Look at abilities that trigger upon stat lowering. return score end #============================================================================= # Make score changes based on the lowering of a specific stat. - # TODO: Revisit this method as parts may need rewriting. #============================================================================= def get_target_stat_drop_score_one(score, target, stat, decrement, desire_mult = 1) # Figure out how much the stat will actually change by - stage_mul = [2, 2, 2, 2, 2, 2, 2, 3, 4, 5, 6, 7, 8] - stage_div = [8, 7, 6, 5, 4, 3, 2, 2, 2, 2, 2, 2, 2] + max_stage = Battle::Battler::STAT_STAGE_MAXIMUM + stage_mul = Battle::Battler::STAT_STAGE_MULTIPLIERS + stage_div = Battle::Battler::STAT_STAGE_DIVISORS if [:ACCURACY, :EVASION].include?(stat) - stage_mul = [3, 3, 3, 3, 3, 3, 3, 4, 5, 6, 7, 8, 9] - stage_div = [9, 8, 7, 6, 5, 4, 3, 3, 3, 3, 3, 3, 3] + stage_mul = Battle::Battler::ACC_EVA_STAGE_MULTIPLIERS + stage_div = Battle::Battler::ACC_EVA_STAGE_DIVISORS end old_stage = target.stages[stat] new_stage = old_stage - decrement - dec_mult = (stage_mul[old_stage + 6].to_f * stage_div[new_stage + 6]) / (stage_div[old_stage + 6] * stage_mul[new_stage + 6]) + dec_mult = (stage_mul[old_stage + max_stage].to_f * stage_div[new_stage + max_stage]) / (stage_div[old_stage + max_stage] * stage_mul[new_stage + max_stage]) dec_mult -= 1 dec_mult *= desire_mult # Stat-based score changes 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 2354d34db..68ce892b9 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 @@ -1,27 +1,3 @@ -# TODO: Check all lingering effects to see if the AI needs to adjust itself -# because of them. - -#=============================================================================== -# -#=============================================================================== -# TODO: -# => Don't prefer damaging move if it won't KO, user has Stance Change and -# is in shield form, and user is slower than the target -# => Check memory for past damage dealt by a target's non-high priority move, -# and prefer move if user is slower than the target and another hit from -# the same amount will KO the user -# => Check memory for past damage dealt by a target's priority move, and don't -# prefer the move if user is slower than the target and can't move faster -# than it because of priority -# => Check memory for whether target has previously used Quick Guard, and -# don't prefer move if so - -#=============================================================================== -#=============================================================================== -#=============================================================================== -#=============================================================================== -#=============================================================================== - #=============================================================================== # Don't prefer hitting a wild shiny Pokémon. #=============================================================================== @@ -37,233 +13,78 @@ Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:shiny_target, ) #=============================================================================== -# Adjust score based on how much damage it can deal. -# Prefer the move even more if it's predicted to do enough damage to KO the -# target. -# TODO: Review score modifier. -# => If target has previously used a move that will hurt the user by 30% of -# its current HP or more, moreso don't prefer a status move. -# => Include EOR damage in this? -# => Prefer move if it will KO the target (moreso if user is slower than target) +# Prefer Shadow moves (for flavour). #=============================================================================== -Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:predicted_damage, +Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:shadow_moves, proc { |score, move, user, target, ai, battle| - if move.damagingMove? - dmg = move.rough_damage + if move.rough_type == :SHADOW old_score = score - score += ([25.0 * dmg / target.hp, 30].min).to_i - PBDebug.log_score_change(score - old_score, "damaging move (predicted damage #{dmg} = #{100 * dmg / target.hp}% of target's HP)") - if dmg > target.hp * 1.1 # Predicted to KO the target - old_score = score - score += 10 - PBDebug.log_score_change(score - old_score, "predicted to KO the target") - end + score += 10 + PBDebug.log_score_change(score - old_score, "prefer using a Shadow move") end next score } ) #=============================================================================== -# Account for accuracy of move. -# TODO: Review score modifier. +# If user is frozen, prefer a move that can thaw the user. #=============================================================================== -Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:move_accuracy, - proc { |score, move, user, target, ai, battle| - acc = move.rough_accuracy.to_i - if acc < 90 +Battle::AI::Handlers::GeneralMoveScore.add(:thawing_move_when_frozen, + proc { |score, move, user, ai, battle| + if ai.trainer.medium_skill? && user.status == :FROZEN old_score = score - score -= (0.2 * (100 - acc)).to_i # -2 (89%) to -19 (1%) - PBDebug.log_score_change(score - old_score, "accuracy (predicted #{acc}%)") + if move.move.thawsUser? + score += 20 + PBDebug.log_score_change(score - old_score, "move will thaw the user") + elsif user.check_for_move { |m| m.thawsUser? } + score -= 20 # Don't prefer this move if user knows another move that thaws + PBDebug.log_score_change(score - old_score, "user knows another move will thaw it") + end end next score } ) #=============================================================================== -# Don't prefer attacking the target if they'd be semi-invulnerable. -# TODO: Review score modifier. +# Prefer using a priority move if the user is slower than the target and... +# - the user is at low HP, or +# - the target is predicted to be knocked out by the move. +# TODO: Less prefer a priority move if any foe knows Quick Guard? #=============================================================================== -Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:target_semi_invulnerable, - proc { |score, move, user, target, ai, battle| - # TODO: Also consider the move's priority compared to that of the move the - # target is using. - if move.rough_accuracy > 0 && user.faster_than?(target) && - (target.battler.semiInvulnerable? || target.effects[PBEffects::SkyDrop] >= 0) - miss = true - miss = false if user.has_active_ability?(:NOGUARD) || target.has_active_ability?(:NOGUARD) - if ai.trainer.high_skill? && miss - # Knows what can get past semi-invulnerability - if target.effects[PBEffects::SkyDrop] >= 0 || - target.battler.inTwoTurnAttack?("TwoTurnAttackInvulnerableInSky", - "TwoTurnAttackInvulnerableInSkyParalyzeTarget", - "TwoTurnAttackInvulnerableInSkyTargetCannotAct") - miss = false if move.move.hitsFlyingTargets? - elsif target.battler.inTwoTurnAttack?("TwoTurnAttackInvulnerableUnderground") - miss = false if move.move.hitsDiggingTargets? - elsif target.battler.inTwoTurnAttack?("TwoTurnAttackInvulnerableUnderwater") - miss = false if move.move.hitsDivingTargets? - end - end - if miss +Battle::AI::Handlers::GeneralMoveScore.add(:priority_move_against_faster_target, + proc { |score, move, user, ai, battle| + if ai.trainer.high_skill? && target.faster_than?(user) && move.rough_priority(user) > 0 + # User is at risk of being knocked out + if ai.trainer.has_skill_flag?("HPAware") && user.hp < user.totalhp / 3 old_score = score - score = Battle::AI::MOVE_USELESS_SCORE - PBDebug.log_score_change(score - old_score, "target is semi-invulnerable") + score += 8 + PBDebug.log_score_change(score - old_score, "user at low HP and move has priority over faster target") end - end - next score - } -) - -#=============================================================================== -# -#=============================================================================== -# TODO: Less prefer two-turn moves, as the foe can see it coming and prepare for -# it. - -#=============================================================================== -# If target is frozen, don't prefer moves that could thaw them. -#=============================================================================== -Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:thawing_move_against_frozen_target, - proc { |score, move, user, target, ai, battle| - if ai.trainer.medium_skill? && target.status == :FROZEN - if move.rough_type == :FIRE || (Settings::MECHANICS_GENERATION >= 6 && move.move.thawsUser?) + # Target is predicted to be knocked out by the move + if move.damaging_move? && move.rough_damage >= target.hp old_score = score - score -= 20 - PBDebug.log_score_change(score - old_score, "thaws the target") + score += 8 + PBDebug.log_score_change(score - old_score, "target at low HP and move has priority over faster target") end end next score } ) -#=============================================================================== -# -#=============================================================================== -# TODO: Prefer move if it has a high critical hit rate, critical hits are -# possible but not certain, and target has raised defences/user has -# lowered offences (Atk/Def or SpAtk/SpDef, whichever is relevant). - -#=============================================================================== -# Prefer flinching external effects (note that move effects which cause -# flinching are dealt with in the function code part of score calculation). -# TODO: Review score modifier. -#=============================================================================== -Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:external_flinching_effects, - proc { |score, move, user, target, ai, battle| - if ai.trainer.medium_skill? && move.damagingMove? && !move.move.flinchingMove? - if (battle.moldBreaker || !target.has_active_ability?([:INNERFOCUS, :SHIELDDUST])) && - target.effects[PBEffects::Substitute] == 0 - if user.has_active_item?([:KINGSROCK, :RAZORFANG]) || - user.has_active_ability?(:STENCH) - old_score = score - score += 8 - PBDebug.log_score_change(score - old_score, "flinching") - end - end - end - next score - } -) - -#=============================================================================== -# -#=============================================================================== -# TODO: Don't prefer contact move if making contact with the target could -# trigger an effect that's bad for the user (Static, etc.). -# => Also check if target has previously used Spiky Shield/King's Shield/ -# Baneful Bunker, and don't prefer move if so - -#=============================================================================== -# -#=============================================================================== -# TODO: Prefer a contact move if making contact with the target could trigger -# an effect that's good for the user (Poison Touch/Pickpocket). - -#=============================================================================== -# -#=============================================================================== -# TODO: Prefer a higher priority move if the user is slower than the foe(s) and -# the user is at risk of being knocked out. Consider whether the foe(s) -# have priority moves of their own? Limit this to prefer priority damaging -# moves? - -#=============================================================================== -# Don't prefer damaging moves if the target is Biding, unless the move will deal -# enough damage to KO the target before it retaliates (assuming the move is used -# repeatedly until the target retaliates). Doesn't do a score change if the user -# will be immune to Bide's damage. -#=============================================================================== -Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:damaging_a_biding_target, - proc { |score, move, user, target, ai, battle| - if ai.trainer.medium_skill? && target.effects[PBEffects::Bide] > 0 && move.damagingMove? - eff = user.effectiveness_of_type_against_battler(:NORMAL, target) # Bide is Normal type - if !Effectiveness.ineffective?(eff) - dmg = move.rough_damage - eor_dmg = target.rough_end_of_round_damage - hits_possible = target.effects[PBEffects::Bide] - 1 - eor_dmg *= hits_possible - hits_possible += 1 if user.faster_than?(target) - if dmg * hits_possible + eor_dmg < target.hp * 1.05 - old_score = score - score -= 20 - PBDebug.log_score_change(score - old_score, "don't want to damage the Biding target") - end - end - end - next score - } -) - -#=============================================================================== -# Don't prefer damaging moves that will knock out the target if they are using -# Destiny Bond. -# TODO: Review score modifier. -# => Also don't prefer damaging moves if user is slower than the target, move -# is likely to be lethal, and target has previously used Destiny Bond -#=============================================================================== -Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:knocking_out_a_destiny_bonder, - proc { |score, move, user, target, ai, battle| - if ai.trainer.medium_skill? && move.damagingMove? && target.effects[PBEffects::DestinyBond] - dmg = move.rough_damage - if dmg > target.hp * 1.05 # Predicted to KO the target - old_score = score - score -= 20 - score -= 10 if battle.pbAbleNonActiveCount(user.idxOwnSide) == 0 - PBDebug.log_score_change(score - old_score, "don't want to KO the Destiny Bonding target") - end - end - next score - } -) - -#=============================================================================== -# -#=============================================================================== -# TODO: Don't prefer Fire-type moves if target has previously used Powder and is -# faster than the user. - -#=============================================================================== -# -#=============================================================================== -# TODO: Check memory for whether target has previously used Ion Deluge, and -# don't prefer move if it's Normal-type and target is immune because -# of its ability (Lightning Rod, etc.). - #=============================================================================== # Don't prefer a move that can be Magic Coated if the target (or any foe if the # move doesn't have a target) knows Magic Coat/has Magic Bounce. #=============================================================================== Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:target_can_Magic_Coat_or_Bounce_move, proc { |score, move, user, target, ai, battle| - # TODO: Modify the semiInvulnerable? check to only apply if the target will - # still be invulnerable when the user acts, i.e. compare speeds? - if move.statusMove? && move.move.canMagicCoat? && - target.opposes?(user) && !target.battler.semiInvulnerable? + if move.statusMove? && move.move.canMagicCoat? && target.opposes?(user) && + (target.faster_than?(user) || !target.battler.semiInvulnerable?) old_score = score if !battle.moldBreaker && target.has_active_ability?(:MAGICBOUNCE) score = Battle::AI::MOVE_USELESS_SCORE PBDebug.log_score_change(score - old_score, "useless because target will Magic Bounce it") - elsif target.has_move_with_function?("BounceBackProblemCausingStatusMoves") + elsif target.has_move_with_function?("BounceBackProblemCausingStatusMoves") && + target.can_attack? && !target.battler.semiInvulnerable? score -= 7 PBDebug.log_score_change(score - old_score, "target knows Magic Coat and could bounce it") end @@ -277,15 +98,13 @@ Battle::AI::Handlers::GeneralMoveScore.add(:any_foe_can_Magic_Coat_or_Bounce_mov if move.statusMove? && move.move.canMagicCoat? && move.pbTarget(user.battler).num_targets == 0 old_score = score ai.each_foe_battler(user.side) do |b, i| - # TODO: Modify the semiInvulnerable? check to only apply if the target - # will still be invulnerable when the user acts, i.e. compare - # speeds? - next if b.battler.semiInvulnerable? + next if user.faster_than?(b) && b.battler.semiInvulnerable? if b.has_active_ability?(:MAGICBOUNCE) && !battle.moldBreaker score = Battle::AI::MOVE_USELESS_SCORE PBDebug.log_score_change(score - old_score, "useless because a foe will Magic Bounce it") break - elsif b.has_move_with_function?("BounceBackProblemCausingStatusMoves") + elsif b.has_move_with_function?("BounceBackProblemCausingStatusMoves") && + b.can_attack? && !b.battler.semiInvulnerable? score -= 7 PBDebug.log_score_change(score - old_score, "a foe knows Magic Coat and could bounce it") break @@ -316,52 +135,6 @@ Battle::AI::Handlers::GeneralMoveScore.add(:any_battler_can_Snatch_move, } ) -#=============================================================================== -#=============================================================================== -#=============================================================================== -#=============================================================================== -#=============================================================================== - -#=============================================================================== -# -#=============================================================================== -# TODO: Prefer Shadow moves (for flavour). - -#=============================================================================== -# If user is frozen, prefer a move that can thaw the user. -#=============================================================================== -Battle::AI::Handlers::GeneralMoveScore.add(:thawing_move_when_frozen, - proc { |score, move, user, ai, battle| - if ai.trainer.medium_skill? && user.status == :FROZEN - old_score = score - if move.move.thawsUser? - score += 20 - PBDebug.log_score_change(score - old_score, "move will thaw the user") - elsif user.check_for_move { |m| m.thawsUser? } - score -= 20 # Don't prefer this move if user knows another move that thaws - PBDebug.log_score_change(score - old_score, "user knows another move will thaw it") - end - end - next score - } -) - -#=============================================================================== -# Don't prefer a dancing move if the target has the Dancer ability. -#=============================================================================== -Battle::AI::Handlers::GeneralMoveScore.add(:dance_move_against_dancer, - proc { |score, move, user, ai, battle| - if move.move.danceMove? - old_score = score - ai.each_foe_battler(user.side) do |b, i| - score -= 10 if b.has_active_ability?(:DANCER) - end - PBDebug.log_score_change(score - old_score, "don't want to use a dance move because a foe has Dancer") - end - next score - } -) - #=============================================================================== # Pick a good move for the Choice items. # TODO: Review score modifier. @@ -371,9 +144,9 @@ Battle::AI::Handlers::GeneralMoveScore.add(:good_move_for_choice_item, if ai.trainer.medium_skill? if user.has_active_item?([:CHOICEBAND, :CHOICESPECS, :CHOICESCARF]) || user.has_active_ability?(:GORILLATACTICS) + old_score = score # Really don't prefer status moves (except Trick) if move.statusMove? && move.function != "UserTargetSwapItems" - old_score = score score -= 25 PBDebug.log_score_change(score - old_score, "don't want to be Choiced into a status move") next score @@ -400,7 +173,6 @@ Battle::AI::Handlers::GeneralMoveScore.add(:good_move_for_choice_item, # Prefer damaging moves if the foe is down to their last Pokémon (opportunistic). # Prefer damaging moves if the AI is down to its last Pokémon but the foe has # more (desperate). -# TODO: Review score modifier. #=============================================================================== Battle::AI::Handlers::GeneralMoveScore.add(:damaging_move_and_either_side_no_reserves, proc { |score, move, user, ai, battle| @@ -426,11 +198,225 @@ Battle::AI::Handlers::GeneralMoveScore.add(:damaging_move_and_either_side_no_res #=============================================================================== # #=============================================================================== -# TODO: Don't prefer a move that is stopped by Wide Guard if any foe has -# previously used Wide Guard. +# TODO: Don't prefer Fire-type moves if target has previously used Powder and is +# faster than the user. #=============================================================================== # #=============================================================================== -# TODO: Don't prefer sound move if user hasn't been Throat Chopped but a foe has -# previously used Throat Chop. +# TODO: Don't prefer Normal-type moves if target has previously used Ion Deluge +# and is immune to Electric moves. + +#=============================================================================== +# +#=============================================================================== +# TODO: Don't prefer a move that is stopped by Wide Guard if any foe has +# previously used Wide Guard. + +#=============================================================================== +# Don't prefer attacking the target if they'd be semi-invulnerable. +#=============================================================================== +Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:target_semi_invulnerable, + proc { |score, move, user, target, ai, battle| + if ai.trainer.medium_skill? && move.rough_accuracy > 0 && + (target.battler.semiInvulnerable? || target.effects[PBEffects::SkyDrop] >= 0) + next score if user.has_active_ability?(:NOGUARD) || target.has_active_ability?(:NOGUARD) + priority = move.rough_priority + if priority > 0 || (priority == 0 && user.faster_than?(target)) # User goes first + miss = true + if ai.trainer.high_skill? + # Knows what can get past semi-invulnerability + if target.effects[PBEffects::SkyDrop] >= 0 || + target.battler.inTwoTurnAttack?("TwoTurnAttackInvulnerableInSky", + "TwoTurnAttackInvulnerableInSkyParalyzeTarget", + "TwoTurnAttackInvulnerableInSkyTargetCannotAct") + miss = false if move.move.hitsFlyingTargets? + elsif target.battler.inTwoTurnAttack?("TwoTurnAttackInvulnerableUnderground") + miss = false if move.move.hitsDiggingTargets? + elsif target.battler.inTwoTurnAttack?("TwoTurnAttackInvulnerableUnderwater") + miss = false if move.move.hitsDivingTargets? + end + end + if miss + old_score = score + score = Battle::AI::MOVE_USELESS_SCORE + PBDebug.log_score_change(score - old_score, "target is semi-invulnerable") + end + end + end + next score + } +) + +#=============================================================================== +# Account for accuracy of move. +#=============================================================================== +Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:predicted_accuracy, + proc { |score, move, user, target, ai, battle| + acc = move.rough_accuracy.to_i + if acc < 90 + old_score = score + score -= (0.25 * (100 - acc)).to_i # -2 (89%) to -24 (1%) + PBDebug.log_score_change(score - old_score, "accuracy (predicted #{acc}%)") + end + next score + } +) + +#=============================================================================== +# Adjust score based on how much damage it can deal. +# Prefer the move even more if it's predicted to do enough damage to KO the +# target. +#=============================================================================== +Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:predicted_damage, + proc { |score, move, user, target, ai, battle| + if move.damagingMove? + dmg = move.rough_damage + old_score = score + if target.effects[PBEffects::Substitute] > 0 + target_hp = target.effects[PBEffects::Substitute] + score += ([15.0 * dmg / target.effects[PBEffects::Substitute], 20].min).to_i + PBDebug.log_score_change(score - old_score, "damaging move (predicted damage #{dmg} = #{100 * dmg / target.hp}% of target's Substitute)") + else + score += ([25.0 * dmg / target.hp, 30].min).to_i + PBDebug.log_score_change(score - old_score, "damaging move (predicted damage #{dmg} = #{100 * dmg / target.hp}% of target's HP)") + if ai.trainer.has_skill_flag?("HPAware") && dmg > target.hp * 1.1 # Predicted to KO the target + old_score = score + score += 10 + PBDebug.log_score_change(score - old_score, "predicted to KO the target") + end + end + end + next score + } +) + +#=============================================================================== +# Prefer flinching external effects (note that move effects which cause +# flinching are dealt with in the function code part of score calculation). +#=============================================================================== +Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:external_flinching_effects, + proc { |score, move, user, target, ai, battle| + if ai.trainer.medium_skill? && move.damagingMove? && !move.move.flinchingMove? && + user.faster_than?(target) && target.effects[PBEffects::Substitute] == 0 + if user.has_active_item?([:KINGSROCK, :RAZORFANG]) || + user.has_active_ability?(:STENCH) + if battle.moldBreaker || !target.has_active_ability?([:INNERFOCUS, :SHIELDDUST]) + old_score = score + score += 8 + PBDebug.log_score_change(score - old_score, "added chance to cause flinching") + end + end + end + next score + } +) + +#=============================================================================== +# If target is frozen, don't prefer moves that could thaw them. +#=============================================================================== +Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:thawing_move_against_frozen_target, + proc { |score, move, user, target, ai, battle| + if ai.trainer.medium_skill? && target.status == :FROZEN + if move.rough_type == :FIRE || (Settings::MECHANICS_GENERATION >= 6 && move.move.thawsUser?) + old_score = score + score -= 20 + PBDebug.log_score_change(score - old_score, "thaws the target") + end + end + next score + } +) + +#=============================================================================== +# +#=============================================================================== +# TODO: Prefer a contact move if making contact with the target could trigger +# an effect that's good for the user (Poison Touch/Pickpocket). + +#=============================================================================== +# +#=============================================================================== +# TODO: Don't prefer contact move if making contact with the target could +# trigger an effect that's bad for the user (Static, etc.). +# => Also check if target has previously used Spiky Shield/King's Shield/ +# Baneful Bunker, and don't prefer move if so + +#=============================================================================== +# Don't prefer damaging moves that will knock out the target if they are using +# Destiny Bond or Grudge. +#=============================================================================== +Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:knocking_out_a_destiny_bonder_or_grudger, + proc { |score, move, user, target, ai, battle| + if (ai.trainer.has_skill_flag?("HPAware") || ai.trainer.high_skill?) && move.damagingMove? && + (target.effects[PBEffects::DestinyBond] || target.effects[PBEffects::Grudge]) + priority = move.rough_priority + if priority > 0 || (priority == 0 && user.faster_than?(target)) # User goes first + if move.rough_damage > target.hp * 1.1 # Predicted to KO the target + old_score = score + if target.effects[PBEffects::DestinyBond] + score -= 20 + score -= 10 if battle.pbAbleNonActiveCount(user.idxOwnSide) == 0 + PBDebug.log_score_change(score - old_score, "don't want to KO the Destiny Bonding target") + elsif target.effects[PBEffects::Grudge] + score -= 15 + score -= 7 if battle.pbAbleNonActiveCount(user.idxOwnSide) == 0 + PBDebug.log_score_change(score - old_score, "don't want to KO the Grudge-using target") + end + end + end + end + next score + } +) + +#=============================================================================== +# +#=============================================================================== +# TODO: Don't prefer damaging moves if the target is using Rage and they benefit +# from the raised Attack. + +#=============================================================================== +# Don't prefer damaging moves if the target is Biding, unless the move will deal +# enough damage to KO the target before it retaliates (assuming the move is used +# repeatedly until the target retaliates). Doesn't do a score change if the user +# will be immune to Bide's damage. +#=============================================================================== +Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:damaging_a_biding_target, + proc { |score, move, user, target, ai, battle| + if ai.trainer.medium_skill? && target.effects[PBEffects::Bide] > 0 && move.damagingMove? + eff = user.effectiveness_of_type_against_battler(:NORMAL, target) # Bide is Normal type + if !Effectiveness.ineffective?(eff) + # Worth damaging the target if it can be knocked out before Bide ends + if ai.trainer.has_skill_flag?("HPAware") + dmg = move.rough_damage + eor_dmg = target.rough_end_of_round_damage + hits_possible = target.effects[PBEffects::Bide] - 1 + eor_dmg *= hits_possible + hits_possible += 1 if user.faster_than?(target) + next score if dmg * hits_possible + eor_dmg > target.hp * 1.1 + end + old_score = score + score -= 20 + PBDebug.log_score_change(score - old_score, "don't want to damage the Biding target") + end + end + next score + } +) + +#=============================================================================== +# Don't prefer a dancing move if the target has the Dancer ability. +#=============================================================================== +Battle::AI::Handlers::GeneralMoveScore.add(:dance_move_against_dancer, + proc { |score, move, user, ai, battle| + if move.move.danceMove? + old_score = score + ai.each_foe_battler(user.side) do |b, i| + score -= 10 if b.has_active_ability?(:DANCER) + end + PBDebug.log_score_change(score - old_score, "don't want to use a dance move because a foe has Dancer") + end + next score + } +) diff --git a/Data/Scripts/011_Battle/005_AI/101_AITrainer.rb b/Data/Scripts/011_Battle/005_AI/101_AITrainer.rb index fc0487385..115b415c2 100644 --- a/Data/Scripts/011_Battle/005_AI/101_AITrainer.rb +++ b/Data/Scripts/011_Battle/005_AI/101_AITrainer.rb @@ -17,6 +17,8 @@ # ConsiderSwitching (can choose to switch out Pokémon) # ReserveLastPokemon (don't switch it in if possible) # UsePokemonInOrder (uses earliest-listed Pokémon possible) +# +# TODO: Add more skill flags. #=============================================================================== class Battle::AI::AITrainer attr_reader :side, :trainer_index @@ -53,7 +55,6 @@ class Battle::AI::AITrainer if @trainer @trainer.flags.each { |flag| @skill_flags.push(flag) } end - # TODO: Add skill flags depending on @skill. if @skill > 0 @skill_flags.push("PredictMoveFailure") @skill_flags.push("ScoreMoves") @@ -74,7 +75,6 @@ class Battle::AI::AITrainer # NOTE: Any skill flag which is shorthand for multiple other skill flags # should be "unpacked" here. # TODO: Have a bunch of "AntiX" flags that negate the corresponding "X" flags. - # TODO: Have flag "DontReserveLastPokemon" which negates "ReserveLastPokemon". end def has_skill_flag?(flag) diff --git a/Data/Scripts/011_Battle/005_AI/102_AIBattler.rb b/Data/Scripts/011_Battle/005_AI/102_AIBattler.rb index 646994959..3afaa6a2b 100644 --- a/Data/Scripts/011_Battle/005_AI/102_AIBattler.rb +++ b/Data/Scripts/011_Battle/005_AI/102_AIBattler.rb @@ -15,34 +15,34 @@ class Battle::AI::AIBattler def refresh_battler old_party_index = @party_index @battler = @ai.battle.battlers[@index] - @party_index = @battler.pokemonIndex + @party_index = battler.pokemonIndex if @party_index != old_party_index # TODO: Start of battle or Pokémon switched/shifted; recalculate roles, # etc. end end - def pokemon; return @battler.pokemon; end - def level; return @battler.level; end - def hp; return @battler.hp; end - def totalhp; return @battler.totalhp; end - def fainted?; return @battler.fainted?; end - def status; return @battler.status; end - def statusCount; return @battler.statusCount; end - def gender; return @battler.gender; end - def turnCount; return @battler.turnCount; end - def effects; return @battler.effects; end - def stages; return @battler.stages; end - def statStageAtMax?(stat); return @battler.statStageAtMax?(stat); end - def statStageAtMin?(stat); return @battler.statStageAtMin?(stat); end - def moves; return @battler.moves; end + def pokemon; return battler.pokemon; end + def level; return battler.level; end + def hp; return battler.hp; end + def totalhp; return battler.totalhp; end + def fainted?; return battler.fainted?; end + def status; return battler.status; end + def statusCount; return battler.statusCount; end + def gender; return battler.gender; end + def turnCount; return battler.turnCount; end + def effects; return battler.effects; end + def stages; return battler.stages; end + def statStageAtMax?(stat); return battler.statStageAtMax?(stat); end + def statStageAtMin?(stat); return battler.statStageAtMin?(stat); end + def moves; return battler.moves; end def wild? return @ai.battle.wildBattle? && opposes? end def name - return sprintf("%s (%d)", @battler.name, @index) + return sprintf("%s (%d)", battler.name, @index) end def opposes?(other = nil) @@ -50,10 +50,10 @@ class Battle::AI::AIBattler return other.side != @side end - def idxOwnSide; return @battler.idxOwnSide; end - def pbOwnSide; return @battler.pbOwnSide; end - def idxOpposingSide; return @battler.idxOpposingSide; end - def pbOpposingSide; return @battler.pbOpposingSide; end + def idxOwnSide; return battler.idxOwnSide; end + def pbOwnSide; return battler.pbOwnSide; end + def idxOpposingSide; return battler.idxOpposingSide; end + def pbOpposingSide; return battler.pbOpposingSide; end #============================================================================= @@ -63,44 +63,44 @@ class Battle::AI::AIBattler # Future Sight/Doom Desire # TODO # Wish - if @ai.battle.positions[@index].effects[PBEffects::Wish] == 1 && @battler.canHeal? + if @ai.battle.positions[@index].effects[PBEffects::Wish] == 1 && battler.canHeal? ret -= @ai.battle.positions[@index].effects[PBEffects::WishAmount] end # Sea of Fire if @ai.battle.sides[@side].effects[PBEffects::SeaOfFire] > 1 && - @battler.takesIndirectDamage? && !has_type?(:FIRE) + battler.takesIndirectDamage? && !has_type?(:FIRE) ret += self.totalhp / 8 end # Grassy Terrain (healing) - if @ai.battle.field.terrain == :Grassy && @battler.affectedByTerrain? && @battler.canHeal? + if @ai.battle.field.terrain == :Grassy && battler.affectedByTerrain? && battler.canHeal? ret -= [battler.totalhp / 16, 1].max end # Leftovers/Black Sludge if has_active_item?(:BLACKSLUDGE) if has_type?(:POISON) - ret -= [battler.totalhp / 16, 1].max if @battler.canHeal? + ret -= [battler.totalhp / 16, 1].max if battler.canHeal? else - ret += [battler.totalhp / 8, 1].max if @battler.takesIndirectDamage? + ret += [battler.totalhp / 8, 1].max if battler.takesIndirectDamage? end elsif has_active_item?(:LEFTOVERS) - ret -= [battler.totalhp / 16, 1].max if @battler.canHeal? + ret -= [battler.totalhp / 16, 1].max if battler.canHeal? end # Aqua Ring - if self.effects[PBEffects::AquaRing] && @battler.canHeal? + if self.effects[PBEffects::AquaRing] && battler.canHeal? amt = battler.totalhp / 16 amt = (amt * 1.3).floor if has_active_item?(:BIGROOT) ret -= [amt, 1].max end # Ingrain - if self.effects[PBEffects::Ingrain] && @battler.canHeal? + if self.effects[PBEffects::Ingrain] && battler.canHeal? amt = battler.totalhp / 16 amt = (amt * 1.3).floor if has_active_item?(:BIGROOT) ret -= [amt, 1].max end # Leech Seed if self.effects[PBEffects::LeechSeed] >= 0 - if @battler.takesIndirectDamage? - ret += [battler.totalhp / 8, 1].max if @battler.takesIndirectDamage? + if battler.takesIndirectDamage? + ret += [battler.totalhp / 8, 1].max if battler.takesIndirectDamage? end else @ai.each_battler do |b, i| @@ -111,31 +111,33 @@ class Battle::AI::AIBattler end end # Hyper Mode (Shadow Pokémon) - # TODO + if battler.inHyperMode? + ret += [battler.totalhp / 24, 1].max + end # Poison/burn/Nightmare if self.status == :POISON if has_active_ability?(:POISONHEAL) - ret -= [battler.totalhp / 8, 1].max if @battler.canHeal? - elsif @battler.takesIndirectDamage? + ret -= [battler.totalhp / 8, 1].max if battler.canHeal? + elsif battler.takesIndirectDamage? mult = 2 mult = [self.effects[PBEffects::Toxic] + 1, 16].min if self.statusCount > 0 # Toxic ret += [mult * battler.totalhp / 16, 1].max end elsif self.status == :BURN - if @battler.takesIndirectDamage? + if battler.takesIndirectDamage? amt = (Settings::MECHANICS_GENERATION >= 7) ? self.totalhp / 16 : self.totalhp / 8 amt = (amt / 2.0).round if has_active_ability?(:HEATPROOF) ret += [amt, 1].max end - elsif @battler.asleep? && self.statusCount > 1 && self.effects[PBEffects::Nightmare] - ret += [battler.totalhp / 4, 1].max if @battler.takesIndirectDamage? + elsif battler.asleep? && self.statusCount > 1 && self.effects[PBEffects::Nightmare] + ret += [battler.totalhp / 4, 1].max if battler.takesIndirectDamage? end # Curse if self.effects[PBEffects::Curse] - ret += [battler.totalhp / 4, 1].max if @battler.takesIndirectDamage? + ret += [battler.totalhp / 4, 1].max if battler.takesIndirectDamage? end # Trapping damage - if self.effects[PBEffects::Trapping] > 1 && @battler.takesIndirectDamage? + if self.effects[PBEffects::Trapping] > 1 && battler.takesIndirectDamage? amt = (Settings::MECHANICS_GENERATION >= 6) ? self.totalhp / 8 : self.totalhp / 16 if @ai.battlers[self.effects[PBEffects::TrappingUser]].has_active_item?(:BINDINGBAND) amt = (Settings::MECHANICS_GENERATION >= 6) ? self.totalhp / 6 : self.totalhp / 8 @@ -143,16 +145,16 @@ class Battle::AI::AIBattler ret += [amt, 1].max end # Perish Song - # TODO + return 999_999 if self.effects[PBEffects::PerishSong] == 1 # Bad Dreams - if @battler.asleep? && self.statusCount > 1 && @battler.takesIndirectDamage? + if battler.asleep? && self.statusCount > 1 && battler.takesIndirectDamage? @ai.each_battler do |b, i| - next if i == @index || !b.battler.near?(@battler) || !b.has_active_ability?(:BADDREAMS) + next if i == @index || !b.battler.near?(battler) || !b.has_active_ability?(:BADDREAMS) ret += [battler.totalhp / 8, 1].max end end # Sticky Barb - if has_active_item?(:STICKYBARB) && @battler.takesIndirectDamage? + if has_active_item?(:STICKYBARB) && battler.takesIndirectDamage? ret += [battler.totalhp / 8, 1].max end return ret @@ -163,22 +165,26 @@ class Battle::AI::AIBattler def base_stat(stat) ret = 0 case stat - when :ATTACK then ret = @battler.attack - when :DEFENSE then ret = @battler.defense - when :SPECIAL_ATTACK then ret = @battler.spatk - when :SPECIAL_DEFENSE then ret = @battler.spdef - when :SPEED then ret = @battler.speed + when :ATTACK then ret = battler.attack + when :DEFENSE then ret = battler.defense + when :SPECIAL_ATTACK then ret = battler.spatk + when :SPECIAL_DEFENSE then ret = battler.spdef + when :SPEED then ret = battler.speed end return ret end def rough_stat(stat) - return @battler.pbSpeed if stat == :SPEED && @ai.trainer.high_skill? - stageMul = [2, 2, 2, 2, 2, 2, 2, 3, 4, 5, 6, 7, 8] - stageDiv = [8, 7, 6, 5, 4, 3, 2, 2, 2, 2, 2, 2, 2] - stage = @battler.stages[stat] + 6 + return battler.pbSpeed if stat == :SPEED && @ai.trainer.high_skill? + stage_mul = Battle::Battler::STAT_STAGE_MULTIPLIERS + stage_div = Battle::Battler::STAT_STAGE_DIVISORS + if [:ACCURACY, :EVASION].include?(stat) + stage_mul = Battle::Battler::ACC_EVA_STAGE_MULTIPLIERS + stage_div = Battle::Battler::ACC_EVA_STAGE_DIVISORS + end + stage = battler.stages[stat] + Battle::Battler::STAT_STAGE_MAXIMUM value = base_stat(stat) - return (value.to_f * stageMul[stage] / stageDiv[stage]).floor + return (value.to_f * stage_mul[stage] / stage_div[stage]).floor end def faster_than?(other) @@ -190,8 +196,8 @@ class Battle::AI::AIBattler #============================================================================= - def types; return @battler.types; end - def pbTypes(withExtraType = false); return @battler.pbTypes(withExtraType); end + def types; return battler.types; end + def pbTypes(withExtraType = false); return battler.pbTypes(withExtraType); end def has_type?(type) return false if !type @@ -201,19 +207,20 @@ class Battle::AI::AIBattler # TODO: Also make a def effectiveness_of_move_against_battler which calls # pbCalcTypeModSingle instead of effectiveness_of_type_against_single_battler_type. + # Why? def effectiveness_of_type_against_battler(type, user = nil) ret = Effectiveness::NORMAL_EFFECTIVE_MULTIPLIER return ret if !type return ret if type == :GROUND && has_type?(:FLYING) && has_active_item?(:IRONBALL) # Get effectivenesses if type == :SHADOW - if @battler.shadowPokemon? + if battler.shadowPokemon? ret = Effectiveness::NOT_VERY_EFFECTIVE_MULTIPLIER else ret = Effectiveness::SUPER_EFFECTIVE_MULTIPLIER end else - @battler.pbTypes(true).each do |defend_type| + battler.pbTypes(true).each do |defend_type| ret *= effectiveness_of_type_against_single_battler_type(type, defend_type, user) end ret *= 2 if self.effects[PBEffects::TarShot] && type == :FIRE @@ -223,39 +230,39 @@ class Battle::AI::AIBattler #============================================================================= - def ability_id; return @battler.ability_id; end - def ability; return @battler.ability; end + def ability_id; return battler.ability_id; end + def ability; return battler.ability; end def ability_active? - return @battler.abilityActive? + return battler.abilityActive? end def has_active_ability?(ability, ignore_fainted = false) - return @battler.hasActiveAbility?(ability, ignore_fainted) + return battler.hasActiveAbility?(ability, ignore_fainted) end def has_mold_breaker? - return @ai.move.function == "IgnoreTargetAbility" || @battler.hasMoldBreaker? + return @ai.move.function == "IgnoreTargetAbility" || battler.hasMoldBreaker? end #============================================================================= - def item_id; return @battler.item_id; end - def item; return @battler.item; end + def item_id; return battler.item_id; end + def item; return battler.item; end def item_active? - return @battler.itemActive? + return battler.itemActive? end def has_active_item?(item) - return @battler.hasActiveItem?(item) + return battler.hasActiveItem?(item) end #============================================================================= def check_for_move ret = false - @battler.eachMove do |move| + battler.eachMove do |move| next if move.pp == 0 && move.total_pp > 0 next unless yield move ret = true @@ -266,7 +273,7 @@ class Battle::AI::AIBattler def has_damaging_move_of_type?(*types) check_for_move do |m| - return true if m.damagingMove? && types.include?(m.pbCalcType(@battler)) + return true if m.damagingMove? && types.include?(m.pbCalcType(battler)) end return false end @@ -303,24 +310,24 @@ class Battle::AI::AIBattler def can_become_trapped? return false if fainted? # Ability/item effects that allow switching no matter what - if ability_active? && Battle::AbilityEffects.triggerCertainSwitching(ability, @battler, @ai.battle) + if ability_active? && Battle::AbilityEffects.triggerCertainSwitching(ability, battler, @ai.battle) return false end - if item_active? && Battle::ItemEffects.triggerCertainSwitching(item, @battler, @ai.battle) + if item_active? && Battle::ItemEffects.triggerCertainSwitching(item, battler, @ai.battle) return false end # Other certain switching effects return false if Settings::MORE_TYPE_EFFECTS && has_type?(:GHOST) # Other certain trapping effects - return false if @battler.trappedInBattle? + return false if battler.trappedInBattle? # Trapping abilities/items ai.each_foe_battler(side) do |b, i| if b.ability_active? && - Battle::AbilityEffects.triggerTrappingByTarget(b.ability, @battler, b.battler, @ai.battle) + Battle::AbilityEffects.triggerTrappingByTarget(b.ability, battler, b.battler, @ai.battle) return false end if b.item_active? && - Battle::ItemEffects.triggerTrappingByTarget(b.item, @battler, b.battler, @ai.battle) + Battle::ItemEffects.triggerTrappingByTarget(b.item, battler, b.battler, @ai.battle) return false end end @@ -432,9 +439,11 @@ class Battle::AI::AIBattler return 0 if has_active_ability?(:KLUTZ) # TODO: Unnerve, other item-negating effects. ret = BASE_ITEM_RATINGS[item] || 0 + # TODO: Add more context-sensitive modifications to the ratings from above. + # Should they be moved into a handler? case item when :ADAMANTORB - ret = 0 if !@battler.isSpecies?(:DIALGA) || !has_damaging_move_of_type?(:DRAGON, :STEEL) + ret = 0 if !battler.isSpecies?(:DIALGA) || !has_damaging_move_of_type?(:DRAGON, :STEEL) when :BLACKBELT, :BLACKGLASSES, :CHARCOAL, :DRAGONFANG, :HARDSTONE, :MAGNET, :METALCOAT, :MIRACLESEED, :MYSTICWATER, :NEVERMELTICE, :POISONBARB, :SHARPBEAK, :SILKSCARF, :SILVERPOWDER, :SOFTSAND, :SPELLTAG, @@ -493,17 +502,17 @@ class Battle::AI::AIBattler when :CHOICESPECS, :WISEGLASSES ret = 0 if !check_for_move { |m| m.specialMove?(m.type) } when :DEEPSEATOOTH - ret = 0 if !@battler.isSpecies?(:CLAMPERL) || !check_for_move { |m| m.specialMove?(m.type) } + ret = 0 if !battler.isSpecies?(:CLAMPERL) || !check_for_move { |m| m.specialMove?(m.type) } when :GRISEOUSORB - ret = 0 if !@battler.isSpecies?(:GIRATINA) || !has_damaging_move_of_type?(:DRAGON, :GHOST) + ret = 0 if !battler.isSpecies?(:GIRATINA) || !has_damaging_move_of_type?(:DRAGON, :GHOST) when :IRONBALL ret = 0 if has_move_with_function?("ThrowUserItemAtTarget") when :LIGHTBALL - ret = 0 if !@battler.isSpecies?(:PIKACHU) || !check_for_move { |m| m.damagingMove? } + ret = 0 if !battler.isSpecies?(:PIKACHU) || !check_for_move { |m| m.damagingMove? } when :LUSTROUSORB - ret = 0 if !@battler.isSpecies?(:PALKIA) || !has_damaging_move_of_type?(:DRAGON, :WATER) + ret = 0 if !battler.isSpecies?(:PALKIA) || !has_damaging_move_of_type?(:DRAGON, :WATER) when :SOULDEW - if !@battler.isSpecies?(:LATIAS) && !@battler.isSpecies?(:LATIOS) + if !battler.isSpecies?(:LATIAS) && !battler.isSpecies?(:LATIOS) ret = 0 elsif Settings::SOUL_DEW_POWERS_UP_TYPES ret = 0 if !has_damaging_move_of_type?(:PSYCHIC, :DRAGON) @@ -511,7 +520,7 @@ class Battle::AI::AIBattler ret -= 2 if !check_for_move { |m| m.specialMove?(m.type) } # Also boosts SpDef end when :THICKCLUB - ret = 0 if (!@battler.isSpecies?(:CUBONE) && !@battler.isSpecies?(:MAROWAK)) || + ret = 0 if (!battler.isSpecies?(:CUBONE) && !battler.isSpecies?(:MAROWAK)) || !check_for_move { |m| m.physicalMove?(m.type) } end # Prefer if this battler knows Fling and it will do a lot of damage/have an @@ -568,18 +577,18 @@ class Battle::AI::AIBattler ret += (cured_status && status == cured_status) ? 6 : -6 when :PERSIMBERRY # Confusion cure - ret += (effects[PBEffects::Confusion] > 1) ? 6 : -6 + ret += (self.effects[PBEffects::Confusion] > 1) ? 6 : -6 when :LUMBERRY # Any status/confusion cure - ret += (status != :NONE || effects[PBEffects::Confusion] > 1) ? 6 : -6 + ret += (status != :NONE || self.effects[PBEffects::Confusion] > 1) ? 6 : -6 when :MENTALHERB # Cure mental effects - if effects[PBEffects::Attract] >= 0 || - effects[PBEffects::Taunt] > 1 || - effects[PBEffects::Encore] > 1 || - effects[PBEffects::Torment] || - effects[PBEffects::Disable] > 1 || - effects[PBEffects::HealBlock] > 1 + if self.effects[PBEffects::Attract] >= 0 || + self.effects[PBEffects::Taunt] > 1 || + self.effects[PBEffects::Encore] > 1 || + self.effects[PBEffects::Torment] || + self.effects[PBEffects::Disable] > 1 || + self.effects[PBEffects::HealBlock] > 1 ret += 6 else ret -= 6 @@ -604,13 +613,13 @@ class Battle::AI::AIBattler ret = ret * 3 / 2 if GameData::Item.get(item).is_berry? && has_active_ability?(:RIPEN) when :WHITEHERB # Resets lowered stats - ret += (@battler.hasLoweredStatStages?) ? 8 : -8 + ret += (battler.hasLoweredStatStages?) ? 8 : -8 when :MICLEBERRY # Raises accuracy of next move ret += (@ai.stat_raise_worthwhile?(self, :ACCURACY, true)) ? 6 : -6 when :LANSATBERRY # Focus energy - ret += (effects[PBEffects::FocusEnergy] < 2) ? 6 : -6 + ret += (self.effects[PBEffects::FocusEnergy] < 2) ? 6 : -6 when :LEPPABERRY # Restore PP ret += 6 @@ -903,54 +912,53 @@ class Battle::AI::AIBattler # they need to do something special in that case. def wants_ability?(ability = :NONE) ability = ability.id if !ability.is_a?(Symbol) && ability.respond_to?("id") - # TODO: Ideally replace the above list of ratings with context-sensitive - # calculations. Should they all go in this method, or should there be - # more handlers for each ability? + ret = BASE_ABILITY_RATINGS[ability] || 0 + # TODO: Add more context-sensitive modifications to the ratings from above. + # Should they be moved into a handler? case ability when :BLAZE - return 0 if !has_damaging_move_of_type?(:FIRE) + ret = 0 if !has_damaging_move_of_type?(:FIRE) when :CUTECHARM, :RIVALRY - return 0 if gender == 2 + ret = 0 if gender == 2 when :FRIENDGUARD, :HEALER, :SYMBOISIS, :TELEPATHY has_ally = false each_ally(@side) { |b, i| has_ally = true } - return 0 if !has_ally + ret = 0 if !has_ally when :GALEWINGS - return 0 if !check_for_move { |m| m.type == :FLYING } + ret = 0 if !check_for_move { |m| m.type == :FLYING } when :HUGEPOWER, :PUREPOWER - return 0 if !ai.stat_raise_worthwhile?(self, :ATTACK, true) + ret = 0 if !ai.stat_raise_worthwhile?(self, :ATTACK, true) when :IRONFIST - return 0 if !check_for_move { |m| m.punchingMove? } + ret = 0 if !check_for_move { |m| m.punchingMove? } when :LIQUIDVOICE - return 0 if !check_for_move { |m| m.soundMove? } + ret = 0 if !check_for_move { |m| m.soundMove? } when :MEGALAUNCHER - return 0 if !check_for_move { |m| m.pulseMove? } + ret = 0 if !check_for_move { |m| m.pulseMove? } when :OVERGROW - return 0 if !has_damaging_move_of_type?(:GRASS) + ret = 0 if !has_damaging_move_of_type?(:GRASS) when :PRANKSTER - return 0 if !check_for_move { |m| m.statusMove? } + ret = 0 if !check_for_move { |m| m.statusMove? } when :PUNKROCK - return 1 if !check_for_move { |m| m.damagingMove? && m.soundMove? } + ret = 1 if !check_for_move { |m| m.damagingMove? && m.soundMove? } when :RECKLESS - return 0 if !check_for_move { |m| m.recoilMove? } + ret = 0 if !check_for_move { |m| m.recoilMove? } when :ROCKHEAD - return 0 if !check_for_move { |m| m.recoilMove? && !m.is_a?(Battle::Move::CrashDamageIfFailsUnusableInGravity) } + ret = 0 if !check_for_move { |m| m.recoilMove? && !m.is_a?(Battle::Move::CrashDamageIfFailsUnusableInGravity) } when :RUNAWAY - return 0 if wild? + ret = 0 if wild? when :SANDFORCE - return 2 if !has_damaging_move_of_type?(:GROUND, :ROCK, :STEEL) + ret = 2 if !has_damaging_move_of_type?(:GROUND, :ROCK, :STEEL) when :SKILLLINK - return 0 if !check_for_move { |m| m.is_a?(Battle::Move::HitTwoToFiveTimes) } + ret = 0 if !check_for_move { |m| m.is_a?(Battle::Move::HitTwoToFiveTimes) } when :STEELWORKER - return 0 if !has_damaging_move_of_type?(:GRASS) + ret = 0 if !has_damaging_move_of_type?(:GRASS) when :SWARM - return 0 if !has_damaging_move_of_type?(:BUG) + ret = 0 if !has_damaging_move_of_type?(:BUG) when :TORRENT - return 0 if !has_damaging_move_of_type?(:WATER) + ret = 0 if !has_damaging_move_of_type?(:WATER) when :TRIAGE - return 0 if !check_for_move { |m| m.healingMove? } + ret = 0 if !check_for_move { |m| m.healingMove? } end - ret = BASE_ABILITY_RATINGS[ability] || 0 return ret end @@ -966,22 +974,22 @@ class Battle::AI::AIBattler ret = Effectiveness::NORMAL_EFFECTIVE_MULTIPLIER end # Foresight - if (user&.has_active_ability?(:SCRAPPY) || @battler.effects[PBEffects::Foresight]) && + if (user&.has_active_ability?(:SCRAPPY) || self.effects[PBEffects::Foresight]) && defend_type == :GHOST ret = Effectiveness::NORMAL_EFFECTIVE_MULTIPLIER end # Miracle Eye - if @battler.effects[PBEffects::MiracleEye] && defend_type == :DARK + if self.effects[PBEffects::MiracleEye] && defend_type == :DARK ret = Effectiveness::NORMAL_EFFECTIVE_MULTIPLIER end elsif Effectiveness.super_effective_type?(type, defend_type) # Delta Stream's weather - if @battler.effectiveWeather == :StrongWinds && defend_type == :FLYING + if battler.effectiveWeather == :StrongWinds && defend_type == :FLYING ret = Effectiveness::NORMAL_EFFECTIVE_MULTIPLIER end end # Grounded Flying-type Pokémon become susceptible to Ground moves - if !@battler.airborne? && defend_type == :FLYING && type == :GROUND + if !battler.airborne? && defend_type == :FLYING && type == :GROUND ret = Effectiveness::NORMAL_EFFECTIVE_MULTIPLIER end return ret diff --git a/Data/Scripts/011_Battle/005_AI/103_AIMove.rb b/Data/Scripts/011_Battle/005_AI/103_AIMove.rb index 6e50d341b..9313a3e9b 100644 --- a/Data/Scripts/011_Battle/005_AI/103_AIMove.rb +++ b/Data/Scripts/011_Battle/005_AI/103_AIMove.rb @@ -105,20 +105,21 @@ class Battle::AI::AIMove def rough_damage base_dmg = base_power return base_dmg if @move.is_a?(Battle::Move::FixedDamageMove) - stage_mul = [2, 2, 2, 2, 2, 2, 2, 3, 4, 5, 6, 7, 8] - stage_div = [8, 7, 6, 5, 4, 3, 2, 2, 2, 2, 2, 2, 2] + max_stage = Battle::Battler::STAT_STAGE_MAXIMUM + stage_mul = Battle::Battler::STAT_STAGE_MULTIPLIERS + stage_div = Battle::Battler::STAT_STAGE_DIVISORS # Get the user and target of this move user = @ai.user user_battler = user.battler target = @ai.target target_battler = target.battler - # Get the move's type calc_type = rough_type - - # Decide whether the move will definitely be a critical hit - is_critical = rough_critical_hit_stage >= Battle::Move::CRITICAL_HIT_RATIOS.length - + # Decide whether the move has 50% chance of higher of being a critical hit + # TODO: Make this a gradient/probability rather than all-or-nothing? + crit_stage = rough_critical_hit_stage + is_critical = crit_stage >= Battle::Move::CRITICAL_HIT_RATIOS.length || + Battle::Move::CRITICAL_HIT_RATIOS[crit_stage] <= 2 ##### Calculate user's attack stat ##### if ["CategoryDependsOnHigherDamagePoisonTarget", "CategoryDependsOnHigherDamageIgnoreTargetAbility"].include?(function) @@ -126,17 +127,15 @@ class Battle::AI::AIMove end atk, atk_stage = @move.pbGetAttackStats(user.battler, target.battler) if !target.has_active_ability?(:UNAWARE) || @ai.battle.moldBreaker - atk_stage = 6 if is_critical && atk_stage < 6 + atk_stage = max_stage if is_critical && atk_stage < max_stage atk = (atk.to_f * stage_mul[atk_stage] / stage_div[atk_stage]).floor end - ##### Calculate target's defense stat ##### defense, def_stage = @move.pbGetDefenseStats(user.battler, target.battler) if !user.has_active_ability?(:UNAWARE) || @ai.battle.moldBreaker - def_stage = 6 if is_critical && def_stage > 6 + def_stage = max_stage if is_critical && def_stage > max_stage defense = (defense.to_f * stage_mul[def_stage] / stage_div[def_stage]).floor end - ##### Calculate all multiplier effects ##### multipliers = { :power_multiplier => 1.0, @@ -154,7 +153,6 @@ class Battle::AI::AIMove multipliers[:power_multiplier] *= 4 / 3.0 end end - # Ability effects that alter damage if user.ability_active? # NOTE: These abilities aren't suitable for checking at the start of the @@ -166,7 +164,6 @@ class Battle::AI::AIMove ) end end - if !@ai.battle.moldBreaker user_battler.allAllies.each do |b| next if !b.abilityActive? @@ -198,7 +195,6 @@ class Battle::AI::AIMove ) end end - # Item effects that alter damage # NOTE: Type-boosting gems aren't suitable for checking at the start of the # round. @@ -218,23 +214,18 @@ class Battle::AI::AIMove target.item, user_battler, target_battler, @move, multipliers, base_dmg, calc_type ) end - # Parental Bond if user.has_active_ability?(:PARENTALBOND) multipliers[:power_multiplier] *= (Settings::MECHANICS_GENERATION >= 7) ? 1.25 : 1.5 end - # Me First # TODO - # Helping Hand - n/a - # Charge if @ai.trainer.medium_skill? && user.effects[PBEffects::Charge] > 0 && calc_type == :ELECTRIC multipliers[:power_multiplier] *= 2 end - # Mud Sport and Water Sport if @ai.trainer.medium_skill? if calc_type == :ELECTRIC @@ -253,7 +244,6 @@ class Battle::AI::AIMove end end end - # Terrain moves if @ai.trainer.medium_skill? terrain_multiplier = (Settings::MECHANICS_GENERATION >= 8) ? 1.3 : 1.5 @@ -268,7 +258,6 @@ class Battle::AI::AIMove multipliers[:power_multiplier] /= 2 if calc_type == :DRAGON && target_battler.affectedByTerrain? end end - # Badge multipliers if @ai.trainer.high_skill? && @ai.battle.internalBattle && target_battler.pbOwnedByPlayer? # Don't need to check the Atk/Sp Atk-boosting badges because the AI @@ -279,12 +268,10 @@ class Battle::AI::AIMove multipliers[:defense_multiplier] *= 1.1 end end - # Multi-targeting attacks if @ai.trainer.high_skill? && targets_multiple_battlers? multipliers[:final_damage_multiplier] *= 0.75 end - # Weather if @ai.trainer.medium_skill? case user_battler.effectiveWeather @@ -309,7 +296,6 @@ class Battle::AI::AIMove end end end - # Critical hits if is_critical if Settings::NEW_CRITICAL_HIT_RATE_MECHANICS @@ -318,9 +304,7 @@ class Battle::AI::AIMove multipliers[:final_damage_multiplier] *= 2 end end - # Random variance - n/a - # STAB if calc_type && user.has_type?(calc_type) if user.has_active_ability?(:ADAPTABILITY) @@ -329,17 +313,14 @@ class Battle::AI::AIMove multipliers[:final_damage_multiplier] *= 1.5 end end - # Type effectiveness typemod = target.effectiveness_of_type_against_battler(calc_type, user) multipliers[:final_damage_multiplier] *= typemod - # Burn if @ai.trainer.high_skill? && user.status == :BURN && physicalMove?(calc_type) && @move.damageReducedByBurn? && !user.has_active_ability?(:GUTS) multipliers[:final_damage_multiplier] /= 2 end - # Aurora Veil, Reflect, Light Screen if @ai.trainer.medium_skill? && !@move.ignoresReflect? && !is_critical && !user.has_active_ability?(:INFILTRATOR) @@ -363,18 +344,14 @@ class Battle::AI::AIMove end end end - # Minimize if @ai.trainer.medium_skill? && target.effects[PBEffects::Minimize] && @move.tramplesMinimize? multipliers[:final_damage_multiplier] *= 2 end - # Move-specific base damage modifiers # TODO - # Move-specific final damage modifiers # TODO - ##### Main damage calculation ##### base_dmg = [(base_dmg * multipliers[:power_multiplier]).round, 1].max atk = [(atk * multipliers[:attack_multiplier]).round, 1].max @@ -428,10 +405,11 @@ class Battle::AI::AIMove return 0 if modifiers[:base_accuracy] < 0 return 100 if modifiers[:base_accuracy] == 0 # Calculation - accStage = [[modifiers[:accuracy_stage], -6].max, 6].min + 6 - evaStage = [[modifiers[:evasion_stage], -6].max, 6].min + 6 - stageMul = [3, 3, 3, 3, 3, 3, 3, 4, 5, 6, 7, 8, 9] - stageDiv = [9, 8, 7, 6, 5, 4, 3, 3, 3, 3, 3, 3, 3] + max_stage = Battle::Battler::STAT_STAGE_MAXIMUM + accStage = [[modifiers[:accuracy_stage], -max_stage].max, max_stage].min + max_stage + evaStage = [[modifiers[:evasion_stage], -max_stage].max, max_stage].min + max_stage + stageMul = Battle::Battler::ACC_EVA_STAGE_MULTIPLIERS + stageDiv = Battle::Battler::ACC_EVA_STAGE_DIVISORS accuracy = 100.0 * stageMul[accStage] / stageDiv[accStage] evasion = 100.0 * stageMul[evaStage] / stageDiv[evaStage] accuracy = (accuracy * modifiers[:accuracy_multiplier]).round diff --git a/Data/Scripts/011_Battle/005b_AI move function codes/051_AI_MoveHandlers_Misc.rb b/Data/Scripts/011_Battle/005b_AI move function codes/051_AI_MoveHandlers_Misc.rb index fbe07f5c9..a2521b756 100644 --- a/Data/Scripts/011_Battle/005b_AI move function codes/051_AI_MoveHandlers_Misc.rb +++ b/Data/Scripts/011_Battle/005b_AI move function codes/051_AI_MoveHandlers_Misc.rb @@ -562,8 +562,9 @@ Battle::AI::Handlers::MoveEffectScore.add("UserMakeSubstitute", ai.each_foe_battler(user.side) do |b, i| score += 5 if !b.check_for_move { |m| m.ignoresSubstitute?(b.battler) } end - # TODO: Predict incoming damage, and prefer if it's greater than - # user.totalhp / 4? + # Prefer if the user lost more than a Substitute's worth of HP from the last + # attack against it + score += 7 if user.battler.lastHPLost >= user.totalhp / 4 next score } ) diff --git a/Data/Scripts/011_Battle/005b_AI move function codes/052_AI_MoveHandlers_BattlerStats.rb b/Data/Scripts/011_Battle/005b_AI move function codes/052_AI_MoveHandlers_BattlerStats.rb index f9438b62c..adcb16044 100644 --- a/Data/Scripts/011_Battle/005b_AI move function codes/052_AI_MoveHandlers_BattlerStats.rb +++ b/Data/Scripts/011_Battle/005b_AI move function codes/052_AI_MoveHandlers_BattlerStats.rb @@ -518,8 +518,6 @@ Battle::AI::Handlers::MoveEffectScore.add("StartRaiseUserAtk1WhenDamaged", if ai.trainer.has_skill_flag?("HPAware") next score if user.hp <= user.totalhp / 3 end - # TODO: Check whether any foe has damaging moves that will trigger the stat - # raise? # Prefer if user benefits from a raised Attack stat score += 10 if ai.stat_raise_worthwhile?(user, :ATTACK) score += 7 if user.has_move_with_function?("PowerHigherWithUserPositiveStatStages") diff --git a/Data/Scripts/011_Battle/005b_AI move function codes/053_AI_MoveHandlers_BattlerOther.rb b/Data/Scripts/011_Battle/005b_AI move function codes/053_AI_MoveHandlers_BattlerOther.rb index d8e5532eb..7a26b6929 100644 --- a/Data/Scripts/011_Battle/005b_AI move function codes/053_AI_MoveHandlers_BattlerOther.rb +++ b/Data/Scripts/011_Battle/005b_AI move function codes/053_AI_MoveHandlers_BattlerOther.rb @@ -1,6 +1,5 @@ #=============================================================================== -# TODO: Should there be all the "next score" for status moves? Remember that -# other function codes can call this code as part of their scoring. +# #=============================================================================== Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("SleepTarget", proc { |move, user, target, ai, battle| @@ -9,15 +8,16 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("SleepTarget", ) Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("SleepTarget", proc { |score, move, user, target, ai, battle| - next score if target.effects[PBEffects::Yawn] > 0 # Target is going to fall asleep anyway + useless_score = (move.statusMove?) ? Battle::AI::MOVE_USELESS_SCORE : score + next useless_score if target.effects[PBEffects::Yawn] > 0 # Target is going to fall asleep anyway # No score modifier if the sleep will be removed immediately - next score if target.has_active_item?([:CHESTOBERRY, :LUMBERRY]) - next score if target.faster_than?(user) && - target.has_active_ability?(:HYDRATION) && - [:Rain, :HeavyRain].include?(target.battler.effectiveWeather) + next useless_score if target.has_active_item?([:CHESTOBERRY, :LUMBERRY]) + next useless_score if target.faster_than?(user) && + target.has_active_ability?(:HYDRATION) && + [:Rain, :HeavyRain].include?(target.battler.effectiveWeather) if target.battler.pbCanSleep?(user.battler, false, move.move) add_effect = move.get_score_change_for_additional_effect(user, target) - next score if add_effect == -999 # Additional effect will be negated + next useless_score if add_effect == -999 # Additional effect will be negated score += add_effect # Inherent preference score += 15 @@ -88,8 +88,7 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.copy("SleepTarget", "SleepTargetNextTurn") #=============================================================================== -# TODO: Should there be all the "next score" for status moves? Remember that -# other function codes can call this code as part of their scoring. +# #=============================================================================== Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("PoisonTarget", proc { |move, user, target, ai, battle| @@ -98,16 +97,16 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("PoisonTarget", ) Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("PoisonTarget", proc { |score, move, user, target, ai, battle| - next score if target.effects[PBEffects::Yawn] > 0 # Target is going to fall asleep - next Battle::AI::MOVE_USELESS_SCORE if move.statusMove? && target.has_active_ability?(:POISONHEAL) + useless_score = (move.statusMove?) ? Battle::AI::MOVE_USELESS_SCORE : score + next useless_score if target.has_active_ability?(:POISONHEAL) # No score modifier if the poisoning will be removed immediately - next score if target.has_active_item?([:PECHABERRY, :LUMBERRY]) - next score if target.faster_than?(user) && - target.has_active_ability?(:HYDRATION) && - [:Rain, :HeavyRain].include?(target.battler.effectiveWeather) + next useless_score if target.has_active_item?([:PECHABERRY, :LUMBERRY]) + next useless_score if target.faster_than?(user) && + target.has_active_ability?(:HYDRATION) && + [:Rain, :HeavyRain].include?(target.battler.effectiveWeather) if target.battler.pbCanPoison?(user.battler, false, move.move) add_effect = move.get_score_change_for_additional_effect(user, target) - next score if add_effect == -999 # Additional effect will be negated + next useless_score if add_effect == -999 # Additional effect will be negated score += add_effect # Inherent preference score += 15 @@ -159,10 +158,10 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("PoisonTargetLowerTarget ) Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("PoisonTargetLowerTargetSpeed1", proc { |score, move, user, target, ai, battle| - score = Battle::AI::Handlers.apply_move_effect_against_target_score("PoisonTarget", - score, move, user, target, ai, battle) - score = Battle::AI::Handlers.apply_move_effect_against_target_score("LowerTargetSpeed1", - score, move, user, target, ai, battle) + poison_score = Battle::AI::Handlers.apply_move_effect_against_target_score("PoisonTarget", + 0, move, user, target, ai, battle) + score += poison_score if poison_score != Battle::AI::MOVE_USELESS_SCORE + score = ai.get_score_for_target_stat_drop(score, target, move.move.statDown, false) next score } ) @@ -176,8 +175,7 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.copy("PoisonTarget", "BadPoisonTarget") #=============================================================================== -# TODO: Should there be all the "next score" for status moves? Remember that -# other function codes can call this code as part of their scoring. +# #=============================================================================== Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("ParalyzeTarget", proc { |move, user, target, ai, battle| @@ -186,15 +184,15 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("ParalyzeTarget", ) Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("ParalyzeTarget", proc { |score, move, user, target, ai, battle| - next score if target.effects[PBEffects::Yawn] > 0 # Target is going to fall asleep + useless_score = (move.statusMove?) ? Battle::AI::MOVE_USELESS_SCORE : score # No score modifier if the paralysis will be removed immediately - next score if target.has_active_item?([:CHERIBERRY, :LUMBERRY]) - next score if target.faster_than?(user) && - target.has_active_ability?(:HYDRATION) && - [:Rain, :HeavyRain].include?(target.battler.effectiveWeather) + next useless_score if target.has_active_item?([:CHERIBERRY, :LUMBERRY]) + next useless_score if target.faster_than?(user) && + target.has_active_ability?(:HYDRATION) && + [:Rain, :HeavyRain].include?(target.battler.effectiveWeather) if target.battler.pbCanParalyze?(user.battler, false, move.move) add_effect = move.get_score_change_for_additional_effect(user, target) - next score if add_effect == -999 # Additional effect will be negated + next useless_score if add_effect == -999 # Additional effect will be negated score += add_effect # Inherent preference (because of the chance of full paralysis) score += 10 @@ -262,17 +260,22 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.copy("ParalyzeTarget", #=============================================================================== Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("ParalyzeFlinchTarget", proc { |score, move, user, target, ai, battle| - score = Battle::AI::Handlers.apply_move_effect_against_target_score("ParalyzeTarget", - score, move, user, target, ai, battle) - score = Battle::AI::Handlers.apply_move_effect_against_target_score("FlinchTarget", - score, move, user, target, ai, battle) + paralyze_score = Battle::AI::Handlers.apply_move_effect_against_target_score("ParalyzeTarget", + 0, move, user, target, ai, battle) + flinch_score = Battle::AI::Handlers.apply_move_effect_against_target_score("FlinchTarget", + 0, move, user, target, ai, battle) + if paralyze_score == Battle::AI::MOVE_USELESS_SCORE && + flinch_score == Battle::AI::MOVE_USELESS_SCORE + next Battle::AI::MOVE_USELESS_SCORE + end + score += paralyze_score if paralyze_score != Battle::AI::MOVE_USELESS_SCORE + score += flinch_score if flinch_score != Battle::AI::MOVE_USELESS_SCORE next score } ) #=============================================================================== -# TODO: Should there be all the "next score" for status moves? Remember that -# other function codes can call this code as part of their scoring. +# #=============================================================================== Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("BurnTarget", proc { |move, user, target, ai, battle| @@ -281,15 +284,15 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("BurnTarget", ) Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("BurnTarget", proc { |score, move, user, target, ai, battle| - next score if target.effects[PBEffects::Yawn] > 0 # Target is going to fall asleep + useless_score = (move.statusMove?) ? Battle::AI::MOVE_USELESS_SCORE : score # No score modifier if the burn will be removed immediately - next score if target.has_active_item?([:RAWSTBERRY, :LUMBERRY]) - next score if target.faster_than?(user) && - target.has_active_ability?(:HYDRATION) && - [:Rain, :HeavyRain].include?(target.battler.effectiveWeather) + next useless_score if target.has_active_item?([:RAWSTBERRY, :LUMBERRY]) + next useless_score if target.faster_than?(user) && + target.has_active_ability?(:HYDRATION) && + [:Rain, :HeavyRain].include?(target.battler.effectiveWeather) if target.battler.pbCanBurn?(user.battler, false, move.move) add_effect = move.get_score_change_for_additional_effect(user, target) - next score if add_effect == -999 # Additional effect will be negated + next useless_score if add_effect == -999 # Additional effect will be negated score += add_effect # Inherent preference score += 15 @@ -339,17 +342,22 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("BurnTarget", #=============================================================================== Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("BurnFlinchTarget", proc { |score, move, user, target, ai, battle| - score = Battle::AI::Handlers.apply_move_effect_against_target_score("BurnTarget", - score, move, user, target, ai, battle) - score = Battle::AI::Handlers.apply_move_effect_against_target_score("FlinchTarget", - score, move, user, target, ai, battle) + burn_score = Battle::AI::Handlers.apply_move_effect_against_target_score("BurnTarget", + 0, move, user, target, ai, battle) + flinch_score = Battle::AI::Handlers.apply_move_effect_against_target_score("FlinchTarget", + 0, move, user, target, ai, battle) + if burn_score == Battle::AI::MOVE_USELESS_SCORE && + flinch_score == Battle::AI::MOVE_USELESS_SCORE + next Battle::AI::MOVE_USELESS_SCORE + end + score += burn_score if burn_score != Battle::AI::MOVE_USELESS_SCORE + score += flinch_score if flinch_score != Battle::AI::MOVE_USELESS_SCORE next score } ) #=============================================================================== -# TODO: Should there be all the "next score" for status moves? Remember that -# other function codes can call this code as part of their scoring. +# #=============================================================================== Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("FreezeTarget", proc { |move, user, target, ai, battle| @@ -358,15 +366,15 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("FreezeTarget", ) Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("FreezeTarget", proc { |score, move, user, target, ai, battle| - next score if target.effects[PBEffects::Yawn] > 0 # Target is going to fall asleep + useless_score = (move.statusMove?) ? Battle::AI::MOVE_USELESS_SCORE : score # No score modifier if the freeze will be removed immediately - next score if target.has_active_item?([:ASPEARBERRY, :LUMBERRY]) - next score if target.faster_than?(user) && - target.has_active_ability?(:HYDRATION) && - [:Rain, :HeavyRain].include?(target.battler.effectiveWeather) + next useless_score if target.has_active_item?([:ASPEARBERRY, :LUMBERRY]) + next useless_score if target.faster_than?(user) && + target.has_active_ability?(:HYDRATION) && + [:Rain, :HeavyRain].include?(target.battler.effectiveWeather) if target.battler.pbCanFreeze?(user.battler, false, move.move) add_effect = move.get_score_change_for_additional_effect(user, target) - next score if add_effect == -999 # Additional effect will be negated + next useless_score if add_effect == -999 # Additional effect will be negated score += add_effect # Inherent preference score += 15 @@ -413,10 +421,16 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.copy("FreezeTarget", #=============================================================================== Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("FreezeFlinchTarget", proc { |score, move, user, target, ai, battle| - score = Battle::AI::Handlers.apply_move_effect_against_target_score("FreezeTarget", - score, move, user, target, ai, battle) - score = Battle::AI::Handlers.apply_move_effect_against_target_score("FlinchTarget", - score, move, user, target, ai, battle) + freeze_score = Battle::AI::Handlers.apply_move_effect_against_target_score("FreezeTarget", + 0, move, user, target, ai, battle) + flinch_score = Battle::AI::Handlers.apply_move_effect_against_target_score("FlinchTarget", + 0, move, user, target, ai, battle) + if freeze_score == Battle::AI::MOVE_USELESS_SCORE && + flinch_score == Battle::AI::MOVE_USELESS_SCORE + next Battle::AI::MOVE_USELESS_SCORE + end + score += freeze_score if freeze_score != Battle::AI::MOVE_USELESS_SCORE + score += flinch_score if flinch_score != Battle::AI::MOVE_USELESS_SCORE next score } ) @@ -426,19 +440,17 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("FreezeFlinchTarget", #=============================================================================== Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("ParalyzeBurnOrFreezeTarget", proc { |score, move, user, target, ai, battle| - next score if target.effects[PBEffects::Yawn] > 0 # Target is going to fall asleep # No score modifier if the status problem will be removed immediately next score if target.has_active_item?(:LUMBERRY) next score if target.faster_than?(user) && target.has_active_ability?(:HYDRATION) && [:Rain, :HeavyRain].include?(target.battler.effectiveWeather) # Scores for the possible effects - score += (Battle::AI::Handlers.apply_move_effect_against_target_score("ParalyzeTarget", - Battle::AI::MOVE_BASE_SCORE, move, user, target, ai, battle) - Battle::AI::MOVE_BASE_SCORE) / 3 - score += (Battle::AI::Handlers.apply_move_effect_against_target_score("BurnTarget", - Battle::AI::MOVE_BASE_SCORE, move, user, target, ai, battle) - Battle::AI::MOVE_BASE_SCORE) / 3 - score += (Battle::AI::Handlers.apply_move_effect_against_target_score("FreezeTarget", - Battle::AI::MOVE_BASE_SCORE, move, user, target, ai, battle) - Battle::AI::MOVE_BASE_SCORE) / 3 + ["ParalyzeTarget", "BurnTarget", "FreezeTarget"].each do |function_code| + effect_score = Battle::AI::Handlers.apply_move_effect_against_target_score(function_code, + 0, move, user, target, ai, battle) + score += effect_score / 3 if effect_score != Battle::AI::MOVE_USELESS_SCORE + end next score } ) @@ -461,22 +473,17 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("GiveUserStatusToTarget", # Curing the user's status problem score += 15 if !user.wants_status_problem?(user.status) # Giving the target a status problem - case user.status - when :SLEEP - next Battle::AI::Handlers.apply_move_effect_against_target_score("SleepTarget", - score, move, user, target, ai, battle) - when :PARALYSIS - next Battle::AI::Handlers.apply_move_effect_against_target_score("ParalyzeTarget", - score, move, user, target, ai, battle) - when :POISON - next Battle::AI::Handlers.apply_move_effect_against_target_score("PoisonTarget", - score, move, user, target, ai, battle) - when :BURN - next Battle::AI::Handlers.apply_move_effect_against_target_score("BurnTarget", - score, move, user, target, ai, battle) - when :FROZEN - next Battle::AI::Handlers.apply_move_effect_against_target_score("FreezeTarget", + function_code = { + :SLEEP => "SleepTarget", + :PARALYSIS => "ParalyzeTarget", + :POISON => "PoisonTarget", + :BURN => "BurnTarget", + :FROZEN => "FreezeTarget" + }[user.status] + if function_code + new_score = Battle::AI::Handlers.apply_move_effect_against_target_score(function_code, score, move, user, target, ai, battle) + next new_score if new_score != Battle::AI::MOVE_USELESS_SCORE end next score } @@ -633,7 +640,6 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("ConfuseTarget", score += 20 * target.hp / target.totalhp end # Prefer if the target is paralysed or infatuated, to compound the turn skipping - # TODO: Also prefer if the target is trapped in battle or can't switch out? score += 8 if target.status == :PARALYSIS || target.effects[PBEffects::Attract] >= 0 # Don't prefer if target benefits from being confused score -= 15 if target.has_active_ability?(:TANGLEDFEET) @@ -665,7 +671,6 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("AttractTarget", # Inherent preference score += 15 # Prefer if the target is paralysed or confused, to compound the turn skipping - # TODO: Also prefer if the target is trapped in battle or can't switch out? score += 8 if target.status == :PARALYSIS || target.effects[PBEffects::Confusion] > 1 # Don't prefer if the target can infatuate the user because of this move score -= 15 if target.has_active_item?(:DESTINYKNOT) && diff --git a/Data/Scripts/011_Battle/005b_AI move function codes/054_AI_MoveHandlers_MoveAttributes.rb b/Data/Scripts/011_Battle/005b_AI move function codes/054_AI_MoveHandlers_MoveAttributes.rb index d13642aab..c4a40a708 100644 --- a/Data/Scripts/011_Battle/005b_AI move function codes/054_AI_MoveHandlers_MoveAttributes.rb +++ b/Data/Scripts/011_Battle/005b_AI move function codes/054_AI_MoveHandlers_MoveAttributes.rb @@ -464,9 +464,6 @@ Battle::AI::Handlers::MoveEffectScore.add("EnsureNextCriticalHit", end # Prefer if user knows a damaging move which won't definitely critical hit if user.check_for_move { |m| m.damagingMove? && m.function != "AlwaysCriticalHit"} - # TODO: Change the score depending on how much of an effect a critical hit - # will have? Critical hits ignore the user's offensive stat drops - # and the target's defensive stat raises, and multiply the damage. score += 15 end next score @@ -521,9 +518,6 @@ Battle::AI::Handlers::MoveEffectScore.add("StartPreventCriticalHitsAgainstUserSi crit_stage = 99 if b.check_for_move { |m| m.pbCritialOverride(b.battler, user.battler) > 0 } crit_stage = [crit_stage, Battle::Move::CRITICAL_HIT_RATIOS.length - 1].min end - # TODO: Change the score depending on how much of an effect a critical hit - # will have? Critical hits ignore the user's offensive stat drops - # and the target's defensive stat raises, and multiply the damage. score += 8 * crit_stage if crit_stage > 0 score += 10 if b.effects[PBEffects::LaserFocus] > 0 end @@ -655,10 +649,7 @@ Battle::AI::Handlers::MoveEffectScore.add("StartWeakenPhysicalDamageAgainstUserS # Doesn't stack with Aurora Veil next Battle::AI::MOVE_USELESS_SCORE if user.pbOwnSide.effects[PBEffects::AuroraVeil] > 0 # Don't prefer the lower the user's HP is - # TODO: Should this HP check exist? The effect can still be set up for - # allies. Maybe just don't prefer if there are no replacement mons - # left. - if ai.trainer.has_skill_flag?("HPAware") + if ai.trainer.has_skill_flag?("HPAware") && battle.pbAbleNonActiveCount(user.idxOwnSide) == 0 if user.hp <= user.totalhp / 2 score -= (20 * (0.75 - (user.hp.to_f / user.totalhp))).to_i # -5 to -15 end @@ -686,10 +677,7 @@ Battle::AI::Handlers::MoveEffectScore.add("StartWeakenSpecialDamageAgainstUserSi # Doesn't stack with Aurora Veil next Battle::AI::MOVE_USELESS_SCORE if user.pbOwnSide.effects[PBEffects::AuroraVeil] > 0 # Don't prefer the lower the user's HP is - # TODO: Should this HP check exist? The effect can still be set up for - # allies. Maybe just don't prefer if there are no replacement mons - # left. - if ai.trainer.has_skill_flag?("HPAware") + if ai.trainer.has_skill_flag?("HPAware") && battle.pbAbleNonActiveCount(user.idxOwnSide) == 0 if user.hp <= user.totalhp / 2 score -= (20 * (0.75 - (user.hp.to_f / user.totalhp))).to_i # -5 to -15 end @@ -720,10 +708,7 @@ Battle::AI::Handlers::MoveEffectScore.add("StartWeakenDamageAgainstUserSideIfHai next Battle::AI::MOVE_USELESS_SCORE if user.pbOwnSide.effects[PBEffects::Reflect] > 0 && user.pbOwnSide.effects[PBEffects::LightScreen] > 0 # Don't prefer the lower the user's HP is - # TODO: Should this HP check exist? The effect can still be set up for - # allies. Maybe just don't prefer if there are no replacement mons - # left. - if ai.trainer.has_skill_flag?("HPAware") + if ai.trainer.has_skill_flag?("HPAware") && battle.pbAbleNonActiveCount(user.idxOwnSide) == 0 if user.hp <= user.totalhp / 2 score -= (20 * (0.75 - (user.hp.to_f / user.totalhp))).to_i # -5 to -15 end @@ -767,8 +752,7 @@ Battle::AI::Handlers::MoveEffectScore.add("ProtectUser", ai.each_foe_battler(user.side) do |b, i| next if !b.can_attack? next if !b.check_for_move { |m| m.canProtectAgainst? } - # TODO: Include b's Unseen Fist somehow? We don't know which move b will - # be using, so we don't know if Unseen Fist will apply. + next if b.has_active_ability?(:UNSEENFIST) && b.check_for_move { |m| m.contactMove? } useless = false # General preference score += 7 @@ -812,8 +796,7 @@ Battle::AI::Handlers::MoveEffectScore.add("ProtectUserBanefulBunker", ai.each_foe_battler(user.side) do |b, i| next if !b.can_attack? next if !b.check_for_move { |m| m.canProtectAgainst? } - # TODO: Include b's Unseen Fist somehow? We don't know which move b will - # be using, so we don't know if Unseen Fist will apply. + next if b.has_active_ability?(:UNSEENFIST) && b.check_for_move { |m| m.contactMove? } useless = false # General preference score += 7 @@ -821,7 +804,9 @@ Battle::AI::Handlers::MoveEffectScore.add("ProtectUserBanefulBunker", if b.check_for_move { |m| m.contactMove? } poison_score = Battle::AI::Handlers.apply_move_effect_against_target_score("PoisonTarget", 0, move, user, b, ai, battle) - score += poison_score / 2 # Halved because we don't know what move b will use + if poison_score != Battle::AI::MOVE_USELESS_SCORE + score += poison_score / 2 # Halved because we don't know what move b will use + end end # Prefer if the foe is in the middle of using a two turn attack score += 15 if b.effects[PBEffects::TwoTurnAttack] && @@ -852,7 +837,7 @@ Battle::AI::Handlers::MoveEffectScore.add("ProtectUserBanefulBunker", ) #=============================================================================== -# TODO: Special scoring for Aegislash. +# #=============================================================================== Battle::AI::Handlers::MoveEffectScore.add("ProtectUserFromDamagingMovesKingsShield", proc { |score, move, user, ai, battle| @@ -863,8 +848,7 @@ Battle::AI::Handlers::MoveEffectScore.add("ProtectUserFromDamagingMovesKingsShie ai.each_foe_battler(user.side) do |b, i| next if !b.can_attack? next if !b.check_for_move { |m| m.damagingMove? && m.canProtectAgainst? } - # TODO: Include b's Unseen Fist somehow? We don't know which move b will - # be using, so we don't know if Unseen Fist will apply. + next if b.has_active_ability?(:UNSEENFIST) && b.check_for_move { |m| m.contactMove? } useless = false # General preference score += 7 @@ -898,6 +882,9 @@ Battle::AI::Handlers::MoveEffectScore.add("ProtectUserFromDamagingMovesKingsShie # Don't prefer if the user used a protection move last turn, making this one # less likely to work score -= (user.effects[PBEffects::ProtectRate] - 1) * ((Settings::MECHANICS_GENERATION >= 6) ? 15 : 10) + # Aegislash + score += 10 if user.battler.isSpecies?(:AEGISLASH) && user.form == 1 && + user.ability == :STANCECHANGE next score } ) @@ -914,8 +901,7 @@ Battle::AI::Handlers::MoveEffectScore.add("ProtectUserFromDamagingMovesObstruct" ai.each_foe_battler(user.side) do |b, i| next if !b.can_attack? next if !b.check_for_move { |m| m.damagingMove? && m.canProtectAgainst? } - # TODO: Include b's Unseen Fist somehow? We don't know which move b will - # be using, so we don't know if Unseen Fist will apply. + next if b.has_active_ability?(:UNSEENFIST) && b.check_for_move { |m| m.contactMove? } useless = false # General preference score += 7 @@ -964,8 +950,7 @@ Battle::AI::Handlers::MoveEffectScore.add("ProtectUserFromTargetingMovesSpikyShi ai.each_foe_battler(user.side) do |b, i| next if !b.can_attack? next if !b.check_for_move { |m| m.canProtectAgainst? } - # TODO: Include b's Unseen Fist somehow? We don't know which move b will - # be using, so we don't know if Unseen Fist will apply. + next if b.has_active_ability?(:UNSEENFIST) && b.check_for_move { |m| m.contactMove? } useless = false # General preference score += 7 @@ -1016,8 +1001,7 @@ Battle::AI::Handlers::MoveEffectScore.add("ProtectUserSideFromDamagingMovesIfUse ai.each_foe_battler(user.side) do |b, i| next if !b.can_attack? next if !b.check_for_move { |m| m.damagingMove? && m.canProtectAgainst? } - # TODO: Include b's Unseen Fist somehow? We don't know which move b will - # be using, so we don't know if Unseen Fist will apply. + next if b.has_active_ability?(:UNSEENFIST) && b.check_for_move { |m| m.contactMove? } useless = false # General preference score += 7 @@ -1252,8 +1236,9 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("RecoilThirdOfDamageDealt score -= 25 * [dmg, user.hp].min / user.hp end # Score for paralysing - score = Battle::AI::Handlers.apply_move_effect_against_target_score("ParalyzeTarget", - score, move, user, target, ai, battle) + paralyze_score = Battle::AI::Handlers.apply_move_effect_against_target_score("ParalyzeTarget", + 0, move, user, target, ai, battle) + score += paralyze_score if paralyze_score != Battle::AI::MOVE_USELESS_SCORE next score } ) @@ -1274,8 +1259,9 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("RecoilThirdOfDamageDealt score -= 25 * [dmg, user.hp].min / user.hp end # Score for burning - score = Battle::AI::Handlers.apply_move_effect_against_target_score("BurnTarget", - score, move, user, target, ai, battle) + burn_score = Battle::AI::Handlers.apply_move_effect_against_target_score("BurnTarget", + 0, move, user, target, ai, battle) + score += burn_score if burn_score != Battle::AI::MOVE_USELESS_SCORE next score } ) @@ -1359,7 +1345,7 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("EnsureNextMoveAlwaysHits acc = m.pbBaseAccuracy(user.battler, target.battler) if ai.trainer.medium_skill? score += 5 if acc < 90 && acc != 0 score += 8 if acc <= 50 && acc != 0 - # TODO: Prefer more if m is a OHKO move. + score += 8 if m.is_a?(Battle::Move::OHKO) end # TODO: Prefer if target has increased evasion. # Not worth it if the user or the target is at low HP @@ -1535,10 +1521,7 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TargetMovesBecomeElectri ) #=============================================================================== -# TODO: This could check all other battlers, not just foes. It could check the -# effectivenesses of their Normal and Electric moves on all their foes, -# not just on the user. I think this is overkill, particularly as the -# effect only lasts for one round. +# #=============================================================================== Battle::AI::Handlers::MoveEffectScore.add("NormalMovesBecomeElectric", proc { |score, move, user, ai, battle| diff --git a/Data/Scripts/011_Battle/005b_AI move function codes/055_AI_MoveHandlers_MultiHit.rb b/Data/Scripts/011_Battle/005b_AI move function codes/055_AI_MoveHandlers_MultiHit.rb index 2692b3545..02bbed7c9 100644 --- a/Data/Scripts/011_Battle/005b_AI move function codes/055_AI_MoveHandlers_MultiHit.rb +++ b/Data/Scripts/011_Battle/005b_AI move function codes/055_AI_MoveHandlers_MultiHit.rb @@ -31,8 +31,9 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("HitTwoTimesPoisonTarget" score = Battle::AI::Handlers.apply_move_effect_against_target_score("HitTwoTimes", score, move, user, target, ai, battle) # Score for poisoning - score = Battle::AI::Handlers.apply_move_effect_against_target_score("PoisonTarget", - score, move, user, target, ai, battle) + poison_score = Battle::AI::Handlers.apply_move_effect_against_target_score("PoisonTarget", + 0, move, user, target, ai, battle) + score += poison_score if poison_score != Battle::AI::MOVE_USELESS_SCORE next score } ) @@ -279,6 +280,7 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TwoTurnAttackParalyzeTar # Score for being a two turn attack score = Battle::AI::Handlers.apply_move_effect_against_target_score("TwoTurnAttack", score, move, user, target, ai, battle) + next score if score == Battle::AI::MOVE_USELESS_SCORE # Score for paralysing score = Battle::AI::Handlers.apply_move_effect_against_target_score("ParalyzeTarget", score, move, user, target, ai, battle) @@ -294,6 +296,7 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TwoTurnAttackBurnTarget" # Score for being a two turn attack score = Battle::AI::Handlers.apply_move_effect_against_target_score("TwoTurnAttack", score, move, user, target, ai, battle) + next score if score == Battle::AI::MOVE_USELESS_SCORE # Score for burning score = Battle::AI::Handlers.apply_move_effect_against_target_score("BurnTarget", score, move, user, target, ai, battle) @@ -309,6 +312,7 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TwoTurnAttackFlinchTarge # Score for being a two turn attack score = Battle::AI::Handlers.apply_move_effect_against_target_score("TwoTurnAttack", score, move, user, target, ai, battle) + next score if score == Battle::AI::MOVE_USELESS_SCORE # Score for flinching score = Battle::AI::Handlers.apply_move_effect_against_target_score("FlinchTarget", score, move, user, target, ai, battle) @@ -323,11 +327,12 @@ Battle::AI::Handlers::MoveFailureCheck.copy("RaiseUserAtkDef1", "TwoTurnAttackRaiseUserSpAtkSpDefSpd2") Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TwoTurnAttackRaiseUserSpAtkSpDefSpd2", proc { |score, move, user, target, ai, battle| - # Score for raising user's stats - score = ai.get_score_for_target_stat_raise(score, user, move.move.statUp) # Score for being a two turn attack score = Battle::AI::Handlers.apply_move_effect_against_target_score("TwoTurnAttack", score, move, user, target, ai, battle) + next score if score == Battle::AI::MOVE_USELESS_SCORE + # Score for raising user's stats + score = ai.get_score_for_target_stat_raise(score, user, move.move.statUp) next score } ) @@ -340,6 +345,7 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TwoTurnAttackChargeRaise # Score for being a two turn attack score = Battle::AI::Handlers.apply_move_effect_against_target_score("TwoTurnAttack", score, move, user, target, ai, battle) + next score if score == Battle::AI::MOVE_USELESS_SCORE # Score for raising the user's stat score = Battle::AI::Handlers.apply_move_effect_score("RaiseUserDefense1", score, move, user, ai, battle) @@ -355,6 +361,7 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TwoTurnAttackChargeRaise # Score for being a two turn attack score = Battle::AI::Handlers.apply_move_effect_against_target_score("TwoTurnAttack", score, move, user, target, ai, battle) + next score if score == Battle::AI::MOVE_USELESS_SCORE # Score for raising the user's stat score = Battle::AI::Handlers.apply_move_effect_score("RaiseUserSpAtk1", score, move, user, ai, battle) @@ -370,6 +377,7 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TwoTurnAttackInvulnerabl # Score for being a two turn attack score = Battle::AI::Handlers.apply_move_effect_against_target_score("TwoTurnAttack", score, move, user, target, ai, battle) + next score if score == Battle::AI::MOVE_USELESS_SCORE # Score for being semi-invulnerable underground ai.each_foe_battler(user.side) do |b, i| if b.check_for_move { |m| m.hitsDiggingTargets? } @@ -390,6 +398,7 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TwoTurnAttackInvulnerabl # Score for being a two turn attack score = Battle::AI::Handlers.apply_move_effect_against_target_score("TwoTurnAttack", score, move, user, target, ai, battle) + next score if score == Battle::AI::MOVE_USELESS_SCORE # Score for being semi-invulnerable underwater ai.each_foe_battler(user.side) do |b, i| if b.check_for_move { |m| m.hitsDivingTargets? } @@ -410,6 +419,7 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TwoTurnAttackInvulnerabl # Score for being a two turn attack score = Battle::AI::Handlers.apply_move_effect_against_target_score("TwoTurnAttack", score, move, user, target, ai, battle) + next score if score == Battle::AI::MOVE_USELESS_SCORE # Score for being semi-invulnerable in the sky ai.each_foe_battler(user.side) do |b, i| if b.check_for_move { |m| m.hitsFlyingTargets? } @@ -430,6 +440,7 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TwoTurnAttackInvulnerabl # Score for being a two turn attack and semi-invulnerable in the sky score = Battle::AI::Handlers.apply_move_effect_against_target_score("TwoTurnAttackInvulnerableInSky", score, move, user, target, ai, battle) + next score if score == Battle::AI::MOVE_USELESS_SCORE # Score for paralyzing the target score = Battle::AI::Handlers.apply_move_effect_against_target_score("ParalyzeTarget", score, move, user, target, ai, battle) @@ -460,6 +471,7 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TwoTurnAttackInvulnerabl # Score for being a two turn attack score = Battle::AI::Handlers.apply_move_effect_against_target_score("TwoTurnAttack", score, move, user, target, ai, battle) + next score if score == Battle::AI::MOVE_USELESS_SCORE # Score for being invulnerable score += 8 # Score for removing protections diff --git a/Data/Scripts/011_Battle/005b_AI move function codes/056_AI_MoveHandlers_Healing.rb b/Data/Scripts/011_Battle/005b_AI move function codes/056_AI_MoveHandlers_Healing.rb index 7d2dc8a45..768fdf025 100644 --- a/Data/Scripts/011_Battle/005b_AI move function codes/056_AI_MoveHandlers_Healing.rb +++ b/Data/Scripts/011_Battle/005b_AI move function codes/056_AI_MoveHandlers_Healing.rb @@ -97,11 +97,7 @@ Battle::AI::Handlers::MoveEffectScore.add("HealUserHalfOfTotalHPLoseFlyingTypeTh score = Battle::AI::Handlers.apply_move_effect_score("HealUserHalfOfTotalHP", score, move, user, ai, battle) # User loses the Flying type this round - if user.has_type?(:FLYING) - # TODO: Decide whether losing the Flying type is good or bad. Look at - # type effectiveness changes against the user, and for foes' Ground - # moves (foe foes slower than the user). Anything else? - end + # NOTE: Not worth considering and scoring for. next score } ) @@ -118,7 +114,7 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("CureTargetStatusHealUser proc { |score, move, user, target, ai, battle| # Consider how much HP will be restored score = Battle::AI::Handlers.apply_move_effect_score("HealUserHalfOfTotalHP", - score, move, user, ai, battle) + score, move, user, ai, battle) # Will cure target's status score += (target.wants_status_problem?(target.status)) ? 10 : -8 next score diff --git a/Data/Scripts/011_Battle/005b_AI move function codes/057_AI_MoveHandlers_Items.rb b/Data/Scripts/011_Battle/005b_AI move function codes/057_AI_MoveHandlers_Items.rb index 54cfae5fa..413d87fed 100644 --- a/Data/Scripts/011_Battle/005b_AI move function codes/057_AI_MoveHandlers_Items.rb +++ b/Data/Scripts/011_Battle/005b_AI move function codes/057_AI_MoveHandlers_Items.rb @@ -217,8 +217,9 @@ Battle::AI::Handlers::MoveFailureCheck.add("UserConsumeBerryRaiseDefense2", Battle::AI::Handlers::MoveEffectScore.add("UserConsumeBerryRaiseDefense2", proc { |score, move, user, ai, battle| # Score for raising the user's stat - score = Battle::AI::Handlers.apply_move_effect_score("RaiseUserDefense2", - score, move, user, ai, battle) + stat_raise_score = Battle::AI::Handlers.apply_move_effect_score("RaiseUserDefense2", + 0, move, user, ai, battle) + score += stat_raise_score if stat_raise_score != Battle::AI::MOVE_USELESS_SCORE # Score for the consumed berry's effect score += user.get_score_change_for_consuming_item(user.item_id, true) # Score for other results of consuming the berry diff --git a/Data/Scripts/011_Battle/006_Other battle code/008_Battle_AbilityEffects.rb b/Data/Scripts/011_Battle/006_Other battle code/008_Battle_AbilityEffects.rb index 53c7abc6d..569b3692c 100644 --- a/Data/Scripts/011_Battle/006_Other battle code/008_Battle_AbilityEffects.rb +++ b/Data/Scripts/011_Battle/006_Other battle code/008_Battle_AbilityEffects.rb @@ -1693,7 +1693,7 @@ Battle::AbilityEffects::OnBeingHit.add(:ANGERPOINT, next if !target.damageState.critical next if !target.pbCanRaiseStatStage?(:ATTACK, target) battle.pbShowAbilitySplash(target) - target.stages[:ATTACK] = 6 + target.stages[:ATTACK] = Battle::Battler::STAT_STAGE_MAXIMUM target.statsRaisedThisRound = true battle.pbCommonAnimation("StatUp", target) if Battle::Scene::USE_ABILITY_SPLASH diff --git a/Data/Scripts/020_Debug/003_Debug menus/005_Debug_BattlePkmnCommands.rb b/Data/Scripts/020_Debug/003_Debug menus/005_Debug_BattlePkmnCommands.rb index bf00bff07..3a179831f 100644 --- a/Data/Scripts/020_Debug/003_Debug menus/005_Debug_BattlePkmnCommands.rb +++ b/Data/Scripts/020_Debug/003_Debug menus/005_Debug_BattlePkmnCommands.rb @@ -162,7 +162,7 @@ MenuHandlers.add(:battle_pokemon_debug_menu, :set_stat_stages, { break if cmd < 0 if cmd < stat_ids.length # Set a stat params = ChooseNumberParams.new - params.setRange(-6, 6) + params.setRange(-Battle::Battler::STAT_STAGE_MAXIMUM, Battle::Battler::STAT_STAGE_MAXIMUM) params.setNegativesAllowed(true) params.setDefaultValue(battler.stages[stat_ids[cmd]]) value = pbMessageChooseNumber(