Resolved all remaining TODO comments for AI (except testing), fixed effects of moves that can end the battle

This commit is contained in:
Maruno17
2023-05-13 22:49:09 +01:00
parent 7a8754c425
commit a4d74a9663
16 changed files with 866 additions and 440 deletions

View File

@@ -387,7 +387,7 @@ class Battle::Battler
#=============================================================================
# Item effects
#=============================================================================
def pbConfusionBerry(item_to_use, forced, flavor, confuse_msg)
def pbConfusionBerry(item_to_use, forced, confuse_stat, confuse_msg)
return false if !forced && !canHeal?
return false if !forced && !canConsumePinchBerry?(Settings::MECHANICS_GENERATION >= 7)
used_item_name = GameData::Item.get(item_to_use).name
@@ -415,12 +415,9 @@ class Battle::Battler
@battle.pbDisplay(_INTL("{1} restored its health using its {2}!", pbThis, used_item_name))
end
end
flavor_stat = [:ATTACK, :DEFENSE, :SPEED, :SPECIAL_ATTACK, :SPECIAL_DEFENSE][flavor]
self.nature.stat_changes.each do |change|
next if change[1] > 0 || change[0] != flavor_stat
if self.nature.stat_changes.any? { |val| val[0] == confuse_stat && val[1] < 0 }
@battle.pbDisplay(confuse_msg)
pbConfuse if pbCanConfuseSelf?(false)
break
end
return true
end

View File

@@ -3,7 +3,7 @@
#===============================================================================
class Battle::Move::FleeFromBattle < Battle::Move
def pbMoveFailed?(user, targets)
if !@battle.pbCanRun?(user.index)
if !@battle.pbCanRun?(user.index) || (user.wild? && user.allAllies.length > 0)
@battle.pbDisplay(_INTL("But it failed!"))
return true
end
@@ -23,7 +23,7 @@ end
class Battle::Move::SwitchOutUserStatusMove < Battle::Move
def pbMoveFailed?(user, targets)
if user.wild?
if !@battle.pbCanRun?(user.index)
if !@battle.pbCanRun?(user.index) || user.allAllies.length > 0
@battle.pbDisplay(_INTL("But it failed!"))
return true
end
@@ -145,9 +145,9 @@ class Battle::Move::SwitchOutUserPassOnEffects < Battle::Move
end
#===============================================================================
# In wild battles, makes target flee. Fails if target is a higher level than the
# user.
# In trainer battles, target switches out.
# When used against a sole wild Pokémon, makes target flee and ends the battle;
# fails if target is a higher level than the user.
# When used against a trainer's Pokémon, target switches out.
# For status moves. (Roar, Whirlwind)
#===============================================================================
class Battle::Move::SwitchOutTargetStatusMove < Battle::Move
@@ -171,38 +171,40 @@ class Battle::Move::SwitchOutTargetStatusMove < Battle::Move
@battle.pbDisplay(_INTL("{1} anchored itself with its roots!", target.pbThis)) if show_message
return true
end
if !@battle.canRun
@battle.pbDisplay(_INTL("But it failed!")) if show_message
return true
end
if @battle.wildBattle? && target.level > user.level
@battle.pbDisplay(_INTL("But it failed!")) if show_message
return true
end
if @battle.trainerBattle?
if target.wild? && target.allAllies.length == 0 && @battle.canRun
# End the battle
if target.level > user.level
@battle.pbDisplay(_INTL("But it failed!")) if show_message
return true
end
elsif !target.wild?
# Switch target out
canSwitch = false
@battle.eachInTeamFromBattlerIndex(target.index) do |_pkmn, i|
next if !@battle.pbCanSwitchIn?(target.index, i)
canSwitch = true
break
canSwitch = @battle.pbCanSwitchIn?(target.index, i)
break if canSwitch
end
if !canSwitch
@battle.pbDisplay(_INTL("But it failed!")) if show_message
return true
end
else
@battle.pbDisplay(_INTL("But it failed!")) if show_message
return true
end
return false
end
def pbEffectGeneral(user)
@battle.decision = 3 if @battle.wildBattle? # Escaped from battle
def pbEffectAgainstTarget(user, target)
@battle.decision = 3 if target.wild? # Escaped from battle
end
def pbSwitchOutTargetEffect(user, targets, numHits, switched_battlers)
return if @battle.wildBattle? || !switched_battlers.empty?
return if !switched_battlers.empty?
return if user.fainted? || numHits == 0
targets.each do |b|
next if b.fainted? || b.damageState.unaffected
next if b.wild?
next if b.effects[PBEffects::Ingrain]
next if b.hasActiveAbility?(:SUCTIONCUPS) && !@battle.moldBreaker
newPkmn = @battle.pbGetReplacementPokemonIndex(b.index, true) # Random
@@ -218,24 +220,26 @@ class Battle::Move::SwitchOutTargetStatusMove < Battle::Move
end
#===============================================================================
# In wild battles, makes target flee. Fails if target is a higher level than the
# user.
# In trainer battles, target switches out.
# When used against a sole wild Pokémon, makes target flee and ends the battle;
# fails if target is a higher level than the user.
# When used against a trainer's Pokémon, target switches out.
# For damaging moves. (Circle Throw, Dragon Tail)
#===============================================================================
class Battle::Move::SwitchOutTargetDamagingMove < Battle::Move
def pbEffectAgainstTarget(user, target)
if @battle.wildBattle? && target.level <= user.level && @battle.canRun &&
if target.wild? && target.allAllies.length == 0 && @battle.canRun &&
target.level <= user.level &&
(target.effects[PBEffects::Substitute] == 0 || ignoresSubstitute?(user))
@battle.decision = 3
@battle.decision = 3 # Escaped from battle
end
end
def pbSwitchOutTargetEffect(user, targets, numHits, switched_battlers)
return if @battle.wildBattle? || !switched_battlers.empty?
return if !switched_battlers.empty?
return if user.fainted? || numHits == 0
targets.each do |b|
next if b.fainted? || b.damageState.unaffected || b.damageState.substitute
next if b.wild?
next if b.effects[PBEffects::Ingrain]
next if b.hasActiveAbility?(:SUCTIONCUPS) && !@battle.moldBreaker
newPkmn = @battle.pbGetReplacementPokemonIndex(b.index, true) # Random

View File

@@ -5,13 +5,10 @@ class Battle::AI
attr_reader :battle
attr_reader :trainer
attr_reader :battlers
attr_reader :roles
attr_reader :user, :target, :move
def initialize(battle)
@battle = battle
@roles = [Array.new(@battle.pbParty(0).length) { |i| determine_roles(0, i) },
Array.new(@battle.pbParty(1).length) { |i| determine_roles(1, i) }]
end
def create_ai_objects

View File

@@ -97,9 +97,7 @@ class Battle::AI
reserves.sort! { |a, b| b[1] <=> a[1] } # Sort from highest to lowest rated
# Don't bother choosing to switch if all replacements are poorly rated
if @trainer.high_skill? && !mandatory
# TODO: Should the current battler be rated as well, to provide a
# threshold instead of using a threshold of 100?
return -1 if reserves[0][1] < 100 # Best replacement rated at <100, don't switch
return -1 if reserves[0][1] < 100 # If best replacement rated at <100, don't switch
end
# Return the party index of the best rated replacement Pokémon
return reserves[0][0]
@@ -136,9 +134,8 @@ class Battle::AI
pkmn.moves.each do |m|
next if m.power == 0 || (m.pp == 0 && m.total_pp > 0)
@battle.battlers[idxBattler].allOpposing.each do |b|
next if pokemon_can_absorb_move?(b.pokemon, m, m.type)
bTypes = b.pbTypes(true)
# TODO: Consider Wonder Guard, Volt Absorb et al. Consider pkmn's
# ability if it changes the user's types or powers up their moves?
score += m.power * Effectiveness.calculate(m.type, *bTypes) / 10
end
end
@@ -175,7 +172,6 @@ end
#===============================================================================
# Pokémon is about to faint because of Perish Song.
# TODO: Also switch to remove other negative effects like Disable, Yawn.
#===============================================================================
Battle::AI::Handlers::ShouldSwitch.add(:perish_song,
proc { |battler, reserves, ai, battle|
@@ -443,65 +439,60 @@ Battle::AI::Handlers::ShouldSwitch.add(:battler_is_useless,
)
#===============================================================================
# Pokémon can't do anything to a Wonder Guard foe.
# TODO: Check other abilities that provide immunities?
# Pokémon can't do anything to any foe because its ability absorbs all damage
# the Pokémon can deal out.
#===============================================================================
Battle::AI::Handlers::ShouldSwitch.add(:foe_has_wonder_guard,
Battle::AI::Handlers::ShouldSwitch.add(:foe_absorbs_all_moves_with_its_ability,
proc { |battler, reserves, ai, battle|
next false if battler.battler.turnCount < 2 # Don't switch out too quickly
next false if battler.battler.hasMoldBreaker?
non_wonder_guard_foe_exists = false
has_super_effective_move = false
foe_types = battler.pbTypes(true)
next false if foe_types.length == 0
# Check if battler can damage any of its foes
can_damage_foe = false
ai.each_foe_battler(battler.side) do |b, i|
if !b.has_active_ability?(:WONDERGUARD)
non_wonder_guard_foe_exists = true
break
end
if ai.trainer.high_skill? && b.rough_end_of_round_damage > 0
non_wonder_guard_foe_exists = true # Wonder Guard is being overcome already
can_damage_foe = true # Foe is being damaged already
break
end
# Check for super-effective damaging moves
# Check for battler's moves that can damage the foe (b)
battler.battler.eachMove do |move|
next if move.statusMove?
if ["IgnoreTargetAbility",
"CategoryDependsOnHigherDamageIgnoreTargetAbility"].include?(move.function)
has_super_effective_move = true
can_damage_foe = true
break
end
eff = Effectiveness.calculate(move.pbCalcType(battler.battler), *foe_types)
if Effectiveness.super_effective?(eff)
has_super_effective_move = true
if !ai.pokemon_can_absorb_move?(b, move, move.pbCalcType(battler.battler))
can_damage_foe = true
break
end
end
break if has_super_effective_move
break if can_damage_foe
end
if !non_wonder_guard_foe_exists && !has_super_effective_move
# Check reserves for super-effective moves; only switch if there are any
reserve_has_super_effective_move = false
reserves.each do |pkmn|
next false if can_damage_foe
# Check if a reserve could damage any foe; only switch if one could
reserve_can_damage_foe = false
reserves.each do |pkmn|
ai.each_foe_battler(battler.side) do |b, i|
# Check for reserve's moves that can damage the foe (b)
pkmn.moves.each do |move|
next if move.status_move?
if ["IgnoreTargetAbility",
"CategoryDependsOnHigherDamageIgnoreTargetAbility"].include?(move.function_code)
reserve_has_super_effective_move = true
reserve_can_damage_foe = true
break
end
eff = Effectiveness.calculate(move.type, *foe_types)
if Effectiveness.super_effective?(eff)
reserve_has_super_effective_move = true
if !ai.pokemon_can_absorb_move?(b, move, move.type)
reserve_can_damage_foe = true
break
end
end
break if reserve_has_super_effective_move
break if reserve_can_damage_foe
end
next false if !reserve_has_super_effective_move
PBDebug.log_ai("#{battler.name} wants to switch because it can't do anything against Wonder Guard")
next true
break if reserve_can_damage_foe
end
next false
next false if !reserve_can_damage_foe
PBDebug.log_ai("#{battler.name} wants to switch because it can't damage the foe(s)")
next true
}
)
@@ -576,7 +567,6 @@ Battle::AI::Handlers::ShouldSwitch.add(:sudden_death,
#===============================================================================
# Pokémon is within 5 levels of the foe, and foe's last move was super-effective
# and powerful.
# TODO: Review switch deciding.
#===============================================================================
Battle::AI::Handlers::ShouldSwitch.add(:high_damage_from_foe,
proc { |battler, reserves, ai, battle|
@@ -653,7 +643,7 @@ Battle::AI::Handlers::ShouldNotSwitch.add(:battler_has_super_effective_move,
# NOTE: Ideally this would ignore foes that move cannot target, but that
# is complicated enough to implement that I'm not bothering. It's
# also rare that it would matter.
eff = b.effectiveness_of_type_against_battler(move_type, battler)
eff = b.effectiveness_of_type_against_battler(move_type, battler, move)
has_super_effective_move = Effectiveness.super_effective?(eff)
break if has_super_effective_move
end
@@ -670,8 +660,6 @@ Battle::AI::Handlers::ShouldNotSwitch.add(:battler_has_super_effective_move,
#===============================================================================
# Don't bother switching if the battler has 4 or more positive stat stages.
# Negative stat stages are ignored.
# TODO: Ignore this if deciding whether to use Baton Pass (assuming move-scoring
# uses this code).
#===============================================================================
Battle::AI::Handlers::ShouldNotSwitch.add(:battler_has_very_raised_stats,
proc { |battler, reserves, ai, battle|

View File

@@ -146,34 +146,31 @@ Battle::AI::Handlers::GeneralMoveScore.add(:any_battler_can_Snatch_move,
#===============================================================================
# Pick a good move for the Choice items.
# TODO: Review score modifier.
#===============================================================================
Battle::AI::Handlers::GeneralMoveScore.add(:good_move_for_choice_item,
proc { |score, move, user, ai, battle|
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"
score -= 25
PBDebug.log_score_change(score - old_score, "don't want to be Choiced into a status move")
next score
end
# Don't prefer moves which are 0x against at least one type
move_type = move.rough_type
GameData::Type.each do |type_data|
score -= 8 if type_data.immunities.include?(move_type)
end
# Don't prefer moves with lower accuracy
if move.accuracy > 0
score -= (0.4 * (100 - move.accuracy)).to_i # -0 (100%) to -39 (1%)
end
# Don't prefer moves with low PP
score -= 10 if move.move.pp <= 5
PBDebug.log_score_change(score - old_score, "move is less suitable to be Choiced into")
end
next score if !ai.trainer.medium_skill?
next score 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"
score -= 25
PBDebug.log_score_change(score - old_score, "don't want to be Choiced into a status move")
next score
end
# Don't prefer moves which are 0x against at least one type
move_type = move.rough_type
GameData::Type.each do |type_data|
score -= 8 if type_data.immunities.include?(move_type)
end
# Don't prefer moves with lower accuracy
if move.accuracy > 0
score -= (0.4 * (100 - move.accuracy)).to_i # -0 (100%) to -39 (1%)
end
# Don't prefer moves with low PP
score -= 10 if move.move.pp <= 5
PBDebug.log_score_change(score - old_score, "move is less suitable to be Choiced into")
next score
}
)
@@ -205,22 +202,42 @@ Battle::AI::Handlers::GeneralMoveScore.add(:damaging_move_and_either_side_no_res
)
#===============================================================================
#
# Don't prefer Fire-type moves if target knows Powder and is faster than the
# user.
#===============================================================================
# TODO: Don't prefer Fire-type moves if target has previously used Powder and is
# faster than the user.
Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:target_can_powder_fire_moves,
proc { |score, move, user, target, ai, battle|
if ai.trainer.high_skill? && move.rough_type == :FIRE &&
target.has_move_with_function?("TargetNextFireMoveDamagesTarget") &&
target.faster_than?(user)
old_score = score
score -= 5 # Only 5 because we're not sure target will use Powder
PBDebug.log_score_change(score - old_score, "target knows Powder and could negate Fire moves")
end
next score
}
)
#===============================================================================
#
# Don't prefer moves if target knows a move that can make them Electric-type,
# and if target is unaffected by Electric moves.
#===============================================================================
# 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.
Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:target_can_make_moves_Electric_and_be_immune,
proc { |score, move, user, target, ai, battle|
next score if !ai.trainer.high_skill?
next score if !target.has_move_with_function?("TargetMovesBecomeElectric") &&
!(move.rough_type == :NORMAL && target.has_move_with_function?("NormalMovesBecomeElectric"))
next score if !ai.pokemon_can_absorb_move?(target, move, :ELECTRIC) &&
!Effectiveness.ineffective?(target.effectiveness_of_type_against_battler(:ELECTRIC, user))
priority = move.rough_priority(user)
if priority > 0 || (priority == 0 && target.faster_than?(user)) # Target goes first
old_score = score
score -= 5 # Only 5 because we're not sure target will use Electrify/Ion Deluge
PBDebug.log_score_change(score - old_score, "target knows Electrify/Ion Deluge and is immune to Electric moves")
end
next score
}
)
#===============================================================================
# Don't prefer attacking the target if they'd be semi-invulnerable.
@@ -293,6 +310,12 @@ Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:predicted_damage,
old_score = score
score += 10
PBDebug.log_score_change(score - old_score, "predicted to KO the target")
if move.move.multiHitMove? && target.hp == target.totalhp &&
(target.has_active_ability?(:STURDY) || target.has_active_item?(:FOCUSSASH))
old_score = score
score += 8
PBDebug.log_score_change(score - old_score, "predicted to overcome the target's Sturdy/Focus Sash")
end
end
end
end
@@ -313,6 +336,7 @@ Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:external_flinching_effe
if battle.moldBreaker || !target.has_active_ability?([:INNERFOCUS, :SHIELDDUST])
old_score = score
score += 8
score += 5 if move.move.multiHitMove?
PBDebug.log_score_change(score - old_score, "added chance to cause flinching")
end
end
@@ -338,25 +362,67 @@ Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:thawing_move_against_fr
)
#===============================================================================
#
# Don't prefer a damaging move if it will trigger the target's ability or held
# item when used, e.g. Effect Spore/Rough Skin, Pickpocket, Rocky Helmet, Red
# Card.
# NOTE: These abilities/items may not be triggerable after all (e.g. they
# require the move to make contact but it doesn't), or may have a negative
# effect for the target (e.g. Air Balloon popping), but it's too much
# effort to go into detail deciding all this.
#===============================================================================
# TODO: Check all effects that trigger upon using a move, including per-hit
# stuff in def pbEffectsOnMakingHit (worse if the move is a multi-hit one)
# and end-of-move stuff in def pbEffectsAfterMove.
Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:trigger_target_ability_or_item_upon_hit,
proc { |score, move, user, target, ai, battle|
if ai.trainer.high_skill? && move.damagingMove? && target.effects[PBEffects::Substitute] == 0
if target.ability_active?
if Battle::AbilityEffects::OnBeingHit[target.ability] ||
(Battle::AbilityEffects::AfterMoveUseFromTarget[target.ability] &&
(!user.has_active_ability?(:SHEERFORCE) || move.move.addlEffect == 0))
old_score = score
score += 8
PBDebug.log_score_change(score - old_score, "can trigger the target's ability")
end
end
if target.battler.isSpecies?(:CRAMORANT) && target.ability == :GULPMISSILE &&
target.battler.form > 0 && !target.effects[PBEffects::Transform]
old_score = score
score += 8
PBDebug.log_score_change(score - old_score, "can trigger the target's ability")
end
if target.item_active?
if Battle::ItemEffects::OnBeingHit[target.item] ||
(Battle::ItemEffects::AfterMoveUseFromTarget[target.item] &&
(!user.has_active_ability?(:SHEERFORCE) || move.move.addlEffect == 0))
old_score = score
score += 8
PBDebug.log_score_change(score - old_score, "can trigger the target's item")
end
end
end
next score
}
)
#===============================================================================
#
# Prefer a damaging move if it will trigger the user's ability when used, e.g.
# Poison Touch, Magician.
#===============================================================================
# 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
Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:trigger_user_ability_upon_hit,
proc { |score, move, user, target, ai, battle|
if ai.trainer.high_skill? && user.ability_active? && move.damagingMove? &&
target.effects[PBEffects::Substitute] == 0
# NOTE: The only ability with an OnDealingHit effect also requires the
# move to make contact. The only abilities with an OnEndOfUsingMove
# effect revolve around damaging moves.
if (Battle::AbilityEffects::OnDealingHit[user.ability] && move.move.contactMove?) ||
Battle::AbilityEffects::OnEndOfUsingMove[user.ability]
old_score = score
score += 8
PBDebug.log_score_change(score - old_score, "can trigger the user's ability")
end
end
next score
}
)
#===============================================================================
# Don't prefer damaging moves that will knock out the target if they are using
@@ -387,10 +453,23 @@ Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:knocking_out_a_destiny_
)
#===============================================================================
#
# Don't prefer damaging moves if the target is using Rage, unless the move will
# deal enough damage to KO the target within two rounds.
#===============================================================================
# TODO: Don't prefer damaging moves if the target is using Rage and they benefit
# from the raised Attack.
Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:damaging_a_raging_target,
proc { |score, move, user, target, ai, battle|
if ai.trainer.medium_skill? && target.effects[PBEffects::Rage] && move.damagingMove?
# Worth damaging the target if it can be knocked out within two rounds
if ai.trainer.has_skill_flag?("HPAware")
next score if (move.rough_damage + target.rough_end_of_round_damage) * 2 > target.hp * 1.1
end
old_score = score
score -= 10
PBDebug.log_score_change(score - old_score, "don't want to damage a Raging target")
end
next score
}
)
#===============================================================================
# Don't prefer damaging moves if the target is Biding, unless the move will deal

View File

@@ -38,14 +38,16 @@ class Battle::AI
# Assumes that pkmn's ability is not negated by a global effect (e.g.
# Neutralizing Gas).
# pkmn is either a Battle::AI::AIBattler or a Pokemon. move is a Battle::Move.
# pkmn is either a Battle::AI::AIBattler or a Pokemon.
# move is a Battle::Move or a Pokemon::Move.
def pokemon_can_absorb_move?(pkmn, move, move_type)
return false if pkmn.is_a?(Battle::AI::AIBattler) && !pkmn.ability_active?
# Check pkmn's ability
# Anything with a Battle::AbilityEffects::MoveImmunity handler
case pkmn.ability_id
when :BULLETPROOF
return move.bombMove?
move_data = GameData::Move.get(move.id)
return move_data.has_flag?("Bomb")
when :FLASHFIRE
return move_type == :FIRE
when :LIGHTNINGROD, :MOTORDRIVE, :VOLTABSORB
@@ -53,7 +55,8 @@ class Battle::AI
when :SAPSIPPER
return move_type == :GRASS
when :SOUNDPROOF
return move.soundMove?
move_data = GameData::Move.get(move.id)
return move_data.has_flag?("Sound")
when :STORMDRAIN, :WATERABSORB, :DRYSKIN
return move_type == :WATER
when :TELEPATHY
@@ -62,7 +65,7 @@ class Battle::AI
when :WONDERGUARD
types = pkmn.types
types = pkmn.pbTypes(true) if pkmn.is_a?(Battle::AI::AIBattler)
return Effectiveness.super_effective_type?(move_type, *types)
return !Effectiveness.super_effective_type?(move_type, *types)
end
return false
end
@@ -157,24 +160,72 @@ class Battle::AI
#-----------------------------------------------------------------------------
# TODO: Add more items.
BASE_ITEM_RATINGS = {
4 => [:CHOICEBAND, :CHOICESCARF, :CHOICESPECS, :DEEPSEATOOTH, :LEFTOVERS,
:LIGHTBALL, :THICKCLUB],
3 => [:ADAMANTORB, :GRISEOUSORB, :LIFEORB, :LUSTROUSORB, :SOULDEW],
2 => [:BLACKBELT, :BLACKGLASSES, :CHARCOAL, :DRAGONFANG, :HARDSTONE,
10 => [:EVIOLITE, :FOCUSSASH, :LIFEORB, :THICKCLUB],
9 => [:ASSAULTVEST, :BLACKSLUDGE, :CHOICEBAND, :CHOICESCARF, :CHOICESPECS,
:DEEPSEATOOTH, :LEFTOVERS],
8 => [:LEEK, :STICK, :THROATSPRAY, :WEAKNESSPOLICY],
7 => [:EXPERTBELT, :LIGHTBALL, :LUMBERRY, :POWERHERB, :ROCKYHELMET,
:SITRUSBERRY],
6 => [:KINGSROCK, :LIECHIBERRY, :LIGHTCLAY, :PETAYABERRY, :RAZORFANG,
:REDCARD, :SALACBERRY, :SHELLBELL, :WHITEHERB,
# Type-resisting berries
:BABIRIBERRY, :CHARTIBERRY, :CHILANBERRY, :CHOPLEBERRY, :COBABERRY,
:COLBURBERRY, :HABANBERRY, :KASIBBERRY, :KEBIABERRY, :OCCABERRY,
:PASSHOBERRY, :PAYAPABERRY, :RINDOBERRY, :ROSELIBERRY, :SHUCABERRY,
:TANGABERRY, :WACANBERRY, :YACHEBERRY,
# Gems
:BUGGEM, :DARKGEM, :DRAGONGEM, :ELECTRICGEM, :FAIRYGEM, :FIGHTINGGEM,
:FIREGEM, :FLYINGGEM, :GHOSTGEM, :GRASSGEM, :GROUNDGEM, :ICEGEM,
:NORMALGEM, :POISONGEM, :PSYCHICGEM, :ROCKGEM, :STEELGEM, :WATERGEM,
# Legendary Orbs
:ADAMANTORB, :GRISEOUSORB, :LUSTROUSORB, :SOULDEW,
# Berries that heal HP and may confuse
:AGUAVBERRY, :FIGYBERRY, :IAPAPABERRY, :MAGOBERRY, :WIKIBERRY],
5 => [:CUSTAPBERRY, :DEEPSEASCALE, :EJECTBUTTON, :FOCUSBAND, :JABOCABERRY,
:KEEBERRY, :LANSATBERRY, :MARANGABERRY, :MENTALHERB, :METRONOME,
:MUSCLEBAND, :QUICKCLAW, :RAZORCLAW, :ROWAPBERRY, :SCOPELENS,
:WISEGLASSES,
# Type power boosters
:BLACKBELT, :BLACKGLASSES, :CHARCOAL, :DRAGONFANG, :HARDSTONE,
:MAGNET, :METALCOAT, :MIRACLESEED, :MYSTICWATER, :NEVERMELTICE,
:POISONBARB, :SHARPBEAK, :SILKSCARF, :SILVERPOWDER, :SOFTSAND,
:POISONBARB, :SHARPBEAK, :SILKSCARF,:SILVERPOWDER, :SOFTSAND,
:SPELLTAG, :TWISTEDSPOON,
:ODDINCENSE, :ROCKINCENSE, :ROSEINCENSE, :SEAINCENSE, :WAVEINCENSE,
# Plates
:DRACOPLATE, :DREADPLATE, :EARTHPLATE, :FISTPLATE, :FLAMEPLATE,
:ICICLEPLATE, :INSECTPLATE, :IRONPLATE, :MEADOWPLATE, :MINDPLATE,
:PIXIEPLATE, :SKYPLATE, :SPLASHPLATE, :SPOOKYPLATE, :STONEPLATE,
:TOXICPLATE, :ZAPPLATE,
:ODDINCENSE, :ROCKINCENSE, :ROSEINCENSE, :SEAINCENSE, :WAVEINCENSE,
:MUSCLEBAND, :WISEGLASSES],
1 => [:METRONOME],
-2 => [:LAGGINGTAIL, :STICKYBARB],
-4 => [:BLACKSLUDGE, :FLAMEORB, :IRONBALL, :TOXICORB]
# Weather/terrain extenders
:DAMPROCK, :HEATROCK, :ICYROCK, :SMOOTHROCK, :TERRAINEXTENDER],
4 => [:ADRENALINEORB, :APICOTBERRY, :BLUNDERPOLICY, :CHESTOBERRY,
:EJECTPACK, :ENIGMABERRY, :GANLONBERRY, :HEAVYDUTYBOOTS,
:ROOMSERVICE, :SAFETYGOGGLES, :SHEDSHELL, :STARFBERRY],
3 => [:BIGROOT, :BRIGHTPOWDER, :LAXINCENSE, :LEPPABERRY, :PERSIMBERRY,
:PROTECTIVEPADS, :UTILITYUMBRELLA,
# Status problem-curing berries (except Chesto which is in 4)
:ASPEARBERRY, :CHERIBERRY, :PECHABERRY, :RAWSTBERRY],
2 => [:ABSORBBULB, :BERRYJUICE, :CELLBATTERY, :GRIPCLAW, :LUMINOUSMOSS,
:MICLEBERRY, :ORANBERRY, :SNOWBALL, :WIDELENS, :ZOOMLENS,
# Terrain seeds
:ELECTRICSEED, :GRASSYSEED, :MISTYSEED, :PSYCHICSEED],
1 => [:AIRBALLOON, :BINDINGBAND, :DESTINYKNOT, :FLOATSTONE, :LUCKYPUNCH,
:METALPOWDER, :QUICKPOWDER,
# Drives
:BURNDRIVE, :CHILLDRIVE, :DOUSEDRIVE, :SHOCKDRIVE,
# Memories
:BUGMEMORY, :DARKMEMORY, :DRAGONMEMORY, :ELECTRICMEMORY,
:FAIRYMEMORY, :FIGHTINGMEMORY, :FIREMEMORY, :FLYINGMEMORY,
:GHOSTMEMORY, :GRASSMEMORY, :GROUNDMEMORY, :ICEMEMORY, :POISONMEMORY,
:PSYCHICMEMORY, :ROCKMEMORY, :STEELMEMORY, :WATERMEMORY
],
0 => [:SMOKEBALL],
-5 => [:FULLINCENSE, :LAGGINGTAIL, :RINGTARGET],
-6 => [:MACHOBRACE, :POWERANKLET, :POWERBAND, :POWERBELT, :POWERBRACER,
:POWERLENS, :POWERWEIGHT],
-7 => [:FLAMEORB, :IRONBALL, :TOXICORB],
-9 => [:STICKYBARB]
}
end
@@ -342,9 +393,71 @@ Battle::AI::Handlers::ItemRanking.add(:ADAMANTORB,
}
)
Battle::AI::Handlers::ItemRanking.add(:AGUAVBERRY,
proc { |item, score, battler, ai|
if Settings::MECHANICS_GENERATION == 7 # Heals 50%
score += 2
elsif Settings::MECHANICS_GENERATION <= 6 # Heals 12.5%
score -= 3
end
if ai.trainer.high_skill?
if battler.battler.nature.stat_changes.any? { |val| val[0] == :SPECIAL_DEFENSE && val[1] < 0 }
score -= 2 # Will confuse
end
end
next score
}
)
Battle::AI::Handlers::ItemRanking.add(:ASSAULTVEST,
proc { |item, score, battler, ai|
if ai.trainer.high_skill?
score += 1 if !battler.check_for_move { |m| m.statusMove? && !m.is_a?(Battle::Move::UseMoveTargetIsAboutToUse) }
end
next score
}
)
Battle::AI::Handlers::ItemRanking.add(:BERRYJUICE,
proc { |item, score, battler, ai|
next [10 - (battler.totalhp / 15), 1].max
}
)
Battle::AI::Handlers::ItemRanking.add(:BIGROOT,
proc { |item, score, battler, ai|
next score if battler.check_for_move do |m|
m.is_a?(Battle::Move::HealUserByHalfOfDamageDone) ||
m.is_a?(Battle::Move::HealUserByHalfOfDamageDoneIfTargetAsleep) ||
m.is_a?(Battle::Move::HealUserByThreeQuartersOfDamageDone) ||
m.is_a?(Battle::Move::HealUserByTargetAttackLowerTargetAttack1) ||
m.is_a?(Battle::Move::StartLeechSeedTarget)
end
next 0
}
)
Battle::AI::Handlers::ItemRanking.add(:BINDINGBAND,
proc { |item, score, battler, ai|
next score if battler.check_for_move { |m| m.is_a?(Battle::Move::BindTarget) }
next 0
}
)
Battle::AI::Handlers::ItemRanking.copy(:BINDINGBAND, :GRIPCLAW)
Battle::AI::Handlers::ItemRanking.add(:BLACKSLUDGE,
proc { |item, score, battler, ai|
next 4 if battler.has_type?(:POISON)
next score if battler.has_type?(:POISON)
next -9
}
)
Battle::AI::Handlers::ItemRanking.add(:CHESTOBERRY,
proc { |item, score, battler, ai|
if ai.trainer.high_skill?
score += 1 if battler.has_move_with_function("HealUserFullyAndFallAsleep")
end
next score
}
)
@@ -367,6 +480,20 @@ Battle::AI::Handlers::ItemRanking.add(:CHOICESPECS,
Battle::AI::Handlers::ItemRanking.copy(:CHOICESPECS, :WISEGLASSES)
Battle::AI::Handlers::ItemRanking.add(:DEEPSEASCALE,
proc { |item, score, battler, ai|
next score if battler.battler.isSpecies?(:CLAMPERL)
next 0
}
)
Battle::AI::Handlers::ItemRanking.add(:DAMPROCK,
proc { |item, score, battler, ai|
next score if battler.check_for_move { |m| m.is_a?(Battle::Move::StartRainWeather) }
next 0
}
)
Battle::AI::Handlers::ItemRanking.add(:DEEPSEATOOTH,
proc { |item, score, battler, ai|
next score if battler.battler.isSpecies?(:CLAMPERL) &&
@@ -375,6 +502,62 @@ Battle::AI::Handlers::ItemRanking.add(:DEEPSEATOOTH,
}
)
Battle::AI::Handlers::ItemRanking.add(:ELECTRICSEED,
proc { |item, score, battler, ai|
next score if battler.check_for_move { |m| m.is_a?(Battle::Move::StartElectricTerrain) }
next 0
}
)
Battle::AI::Handlers::ItemRanking.add(:EVIOLITE,
proc { |item, score, battler, ai|
next score if battler.battler.pokemon.species_data.get_evolutions(true).length > 0
next 0
}
)
Battle::AI::Handlers::ItemRanking.add(:FIGYBERRY,
proc { |item, score, battler, ai|
if Settings::MECHANICS_GENERATION == 7 # Heals 50%
score += 2
elsif Settings::MECHANICS_GENERATION <= 6 # Heals 12.5%
score -= 3
end
if ai.trainer.high_skill?
if battler.battler.nature.stat_changes.any? { |val| val[0] == :ATTACK && val[1] < 0 }
score -= 2 # Will confuse
end
end
next score
}
)
Battle::AI::Handlers::ItemRanking.add(:FLAMEORB,
proc { |item, score, battler, ai|
next 0 if battler.status != :NONE
next 7 if battler.wants_status_problem?(:BURN)
next score
}
)
Battle::AI::Handlers::ItemRanking.add(:FULLINCENSE,
proc { |item, score, battler, ai|
if ai.trainer.high_skill?
score = 7 if battler.has_active_ability?(:ANALYTIC)
end
next score
}
)
Battle::AI::Handlers::ItemRanking.copy(:FULLINCENSE, :LAGGINGTAIL)
Battle::AI::Handlers::ItemRanking.add(:GRASSYSEED,
proc { |item, score, battler, ai|
next score if battler.check_for_move { |m| m.is_a?(Battle::Move::StartGrassyTerrain) }
next 0
}
)
Battle::AI::Handlers::ItemRanking.add(:GRISEOUSORB,
proc { |item, score, battler, ai|
next score if battler.battler.isSpecies?(:GIRATINA) &&
@@ -383,6 +566,36 @@ Battle::AI::Handlers::ItemRanking.add(:GRISEOUSORB,
}
)
Battle::AI::Handlers::ItemRanking.add(:HEATROCK,
proc { |item, score, battler, ai|
next score if battler.check_for_move { |m| m.is_a?(Battle::Move::StartSunWeather) }
next 0
}
)
Battle::AI::Handlers::ItemRanking.add(:IAPAPABERRY,
proc { |item, score, battler, ai|
if Settings::MECHANICS_GENERATION == 7 # Heals 50%
score += 2
elsif Settings::MECHANICS_GENERATION <= 6 # Heals 12.5%
score -= 3
end
if ai.trainer.high_skill?
if battler.battler.nature.stat_changes.any? { |val| val[0] == :DEFENSE && val[1] < 0 }
score -= 2 # Will confuse
end
end
next score
}
)
Battle::AI::Handlers::ItemRanking.add(:ICYROCK,
proc { |item, score, battler, ai|
next score if battler.check_for_move { |m| m.is_a?(Battle::Move::StartHailWeather) }
next 0
}
)
Battle::AI::Handlers::ItemRanking.add(:IRONBALL,
proc { |item, score, battler, ai|
next 0 if battler.has_move_with_function?("ThrowUserItemAtTarget")
@@ -390,6 +603,27 @@ Battle::AI::Handlers::ItemRanking.add(:IRONBALL,
}
)
Battle::AI::Handlers::ItemRanking.add(:KINGSROCK,
proc { |item, score, battler, ai|
if ai.trainer.high_skill?
score += 1 if battler.check_for_move { |m| m.multiHitMove? }
end
next score
}
)
Battle::AI::Handlers::ItemRanking.copy(:KINGSROCK, :RAZORFANG)
Battle::AI::Handlers::ItemRanking.add(:LEEK,
proc { |item, score, battler, ai|
next score if (battler.battler.isSpecies?(:FARFETCHD) || battler.battler.isSpecies?(:SIRFETCHD)) &&
battler.check_for_move { |m| m.damagingMove? }
next 0
}
)
Battle::AI::Handlers::ItemRanking.copy(:LEEK, :STICK)
Battle::AI::Handlers::ItemRanking.add(:LIGHTBALL,
proc { |item, score, battler, ai|
next score if battler.battler.isSpecies?(:PIKACHU) &&
@@ -398,6 +632,24 @@ Battle::AI::Handlers::ItemRanking.add(:LIGHTBALL,
}
)
Battle::AI::Handlers::ItemRanking.add(:LIGHTCLAY,
proc { |item, score, battler, ai|
next score if battler.check_for_move do |m|
m.is_a?(Battle::Move::StartWeakenPhysicalDamageAgainstUserSide) ||
m.is_a?(Battle::Move::StartWeakenSpecialDamageAgainstUserSide) ||
m.is_a?(Battle::Move::StartWeakenDamageAgainstUserSideIfHail)
end
next 0
}
)
Battle::AI::Handlers::ItemRanking.add(:LUCKYPUNCH,
proc { |item, score, battler, ai|
next score if battler.battler.isSpecies?(:CHANSEY)
next 0
}
)
Battle::AI::Handlers::ItemRanking.add(:LUSTROUSORB,
proc { |item, score, battler, ai|
next score if battler.battler.isSpecies?(:PALKIA) &&
@@ -406,18 +658,106 @@ Battle::AI::Handlers::ItemRanking.add(:LUSTROUSORB,
}
)
Battle::AI::Handlers::ItemRanking.add(:MAGOBERRY,
proc { |item, score, battler, ai|
if Settings::MECHANICS_GENERATION == 7 # Heals 50%
score += 2
elsif Settings::MECHANICS_GENERATION <= 6 # Heals 12.5%
score -= 3
end
if ai.trainer.high_skill?
if battler.battler.nature.stat_changes.any? { |val| val[0] == :SPEED && val[1] < 0 }
score -= 2 # Will confuse
end
end
next score
}
)
Battle::AI::Handlers::ItemRanking.add(:METALPOWDER,
proc { |item, score, battler, ai|
next score if battler.battler.isSpecies?(:DITTO) && !battler.effects[PBEffects::Transform]
next 0
}
)
Battle::AI::Handlers::ItemRanking.copy(:METALPOWDER, :QUICKPOWDER)
Battle::AI::Handlers::ItemRanking.add(:MISTYSEED,
proc { |item, score, battler, ai|
next score if battler.check_for_move { |m| m.is_a?(Battle::Move::StartMistyTerrain) }
next 0
}
)
Battle::AI::Handlers::ItemRanking.add(:ORANBERRY,
proc { |item, score, battler, ai|
next [10 - (battler.totalhp / 8), 1].max
}
)
Battle::AI::Handlers::ItemRanking.add(:POWERHERB,
proc { |item, score, battler, ai|
next score if battler.check_for_move do |m|
m.is_a?(Battle::Move::TwoTurnMove) &&
!m.is_a?(Battle::Move::TwoTurnAttackInvulnerableInSkyTargetCannotAct)
end
next 0
}
)
Battle::AI::Handlers::ItemRanking.add(:PSYCHICSEED,
proc { |item, score, battler, ai|
next score if battler.check_for_move { |m| m.is_a?(Battle::Move::StartPsychicTerrain) }
next 0
}
)
Battle::AI::Handlers::ItemRanking.add(:RINGTARGET,
proc { |item, score, battler, ai|
has_immunity = false
battler.pbTypes(true).each do |type|
has_immunity = GameData::Type.get(type).immunities.length > 0
break if has_immunity
end
next score if has_immunity
next 0
}
)
Battle::AI::Handlers::ItemRanking.add(:SMOOTHROCK,
proc { |item, score, battler, ai|
next score if battler.check_for_move { |m| m.is_a?(Battle::Move::StartSandstormWeather) }
next 0
}
)
Battle::AI::Handlers::ItemRanking.add(:SOULDEW,
proc { |item, score, battler, ai|
next 0 if !battler.battler.isSpecies?(:LATIAS) && !battler.battler.isSpecies?(:LATIOS)
if Settings::SOUL_DEW_POWERS_UP_TYPES
next 0 if !battler.has_damaging_move_of_type?(:PSYCHIC, :DRAGON)
elsif !battler.check_for_move { |m| m.specialMove?(m.type) }
next 1 # Also boosts SpDef
elsif battler.check_for_move { |m| m.specialMove?(m.type) }
next 10
else
next 6 # Boosts SpDef
end
next score
}
)
Battle::AI::Handlers::ItemRanking.add(:TERRAINEXTENDER,
proc { |item, score, battler, ai|
next score if battler.check_for_move do |m|
m.is_a?(Battle::Move::StartElectricTerrain) ||
m.is_a?(Battle::Move::StartGrassyTerrain) ||
m.is_a?(Battle::Move::StartMistyTerrain) ||
m.is_a?(Battle::Move::StartPsychicTerrain)
end
next 0
}
)
Battle::AI::Handlers::ItemRanking.add(:THICKCLUB,
proc { |item, score, battler, ai|
next score if (battler.battler.isSpecies?(:CUBONE) || battler.battler.isSpecies?(:MAROWAK)) &&
@@ -426,6 +766,61 @@ Battle::AI::Handlers::ItemRanking.add(:THICKCLUB,
}
)
Battle::AI::Handlers::ItemRanking.add(:THROATSPRAY,
proc { |item, score, battler, ai|
next score if battler.check_for_move { |m| m.soundMove? }
next 0
}
)
Battle::AI::Handlers::ItemRanking.add(:TOXICORB,
proc { |item, score, battler, ai|
next 0 if battler.status != :NONE
next 7 if battler.wants_status_problem?(:POISON)
next score
}
)
Battle::AI::Handlers::ItemRanking.add(:WHITEHERB,
proc { |item, score, battler, ai|
if ai.trainer.high_skill?
score += 1 if battler.has_move_with_function("LowerUserDefSpDef1RaiseUserAtkSpAtkSpd2")
end
next score
}
)
Battle::AI::Handlers::ItemRanking.add(:WIKIBERRY,
proc { |item, score, battler, ai|
if Settings::MECHANICS_GENERATION == 7 # Heals 50%
score += 2
elsif Settings::MECHANICS_GENERATION <= 6 # Heals 12.5%
score -= 3
end
if ai.trainer.high_skill?
if battler.battler.nature.stat_changes.any? { |val| val[0] == :SPECIAL_ATTACK && val[1] < 0 }
score -= 2 # Will confuse
end
end
next score
}
)
Battle::AI::Handlers::ItemRanking.add(:ZOOMLENS,
proc { |item, score, battler, ai|
if ai.trainer.high_skill?
score += 1 if battler.stages[:ACCURACY] < 0
battler.battler.eachMove do |m|
next if m.accuracy == 0 || m.is_a?(Battle::Move::OHKO)
next if m.accuracy > 70
score += 1
break
end
end
next score
}
)
Battle::AI::Handlers::ItemRanking.addIf(:type_boosting_items,
proc { |item|
next [:BLACKBELT, :BLACKGLASSES, :CHARCOAL, :DRAGONFANG, :HARDSTONE,
@@ -469,3 +864,36 @@ Battle::AI::Handlers::ItemRanking.addIf(:type_boosting_items,
next 0
}
)
Battle::AI::Handlers::ItemRanking.addIf(:gems,
proc { |item|
next [:FIREGEM, :WATERGEM, :ELECTRICGEM, :GRASSGEM, :ICEGEM, :FIGHTINGGEM,
:POISONGEM, :GROUNDGEM, :FLYINGGEM, :PSYCHICGEM, :BUGGEM, :ROCKGEM,
:GHOSTGEM, :DRAGONGEM, :DARKGEM, :STEELGEM, :FAIRYGEM, :NORMALGEM].include?(item)
},
proc { |item, score, battler, ai|
score += 2 if Settings::MECHANICS_GENERATION <= 5 # 1.5x boost rather than 1.3x
boosted_type = {
:BUGGEM => :BUG,
:DARKGEM => :DARK,
:DRAGONGEM => :DRAGON,
:ELECTRICGEM => :ELECTRIC,
:FAIRYGEM => :FAIRY,
:FIGHTINGGEM => :FIGHTING,
:FIREGEM => :FIRE,
:FLYINGGEM => :FLYING,
:GHOSTGEM => :GHOST,
:GRASSGEM => :GRASS,
:GROUNDGEM => :GROUND,
:ICEGEM => :ICE,
:NORMALGEM => :NORMAL,
:POISONGEM => :POISON,
:PSYCHICGEM => :PSYCHIC,
:ROCKGEM => :ROCK,
:STEELGEM => :STEEL,
:WATERGEM => :WATER,
}[item]
next score if boosted_type && battler.has_damaging_move_of_type?(boosted_type)
next 0
}
)

View File

@@ -1,157 +0,0 @@
#===============================================================================
#
#===============================================================================
class Battle::AI
# Determine the roles filled by a Pokémon on a given side at a given party
# index.
# Roles are:
# :ace
# :baton_passer
# :cleric
# :field_setter
# :lead
# :phazer
# :physical_wall
# :pivot
# :screener
# :second
# :special_wall
# :spinner
# :stall_breaker
# :status_absorber
# :sweeper
# :tank
# :trapper
# :weather_setter
# NOTE: Reborn has the REVENGEKILLER role which compares mon's speed with
# opponent (only when deciding whether to switch mon in) - this
# comparison should be calculated when needed instead of being a role.
def determine_roles(side, index)
pkmn = @battle.pbParty(side)[index]
ret = []
return ret if !pkmn || pkmn.egg?
# Check for moves indicative of particular roles
hasHealMove = false
pkmn.moves.each do |m|
next if !m
move = Battle::Move.from_pokemon_move(@battle, m)
hasHealMove = true if !hasHealMove && move.healingMove?
case move.function
when "SleepTargetNextTurn", # Yawn
"StartPerishCountsForAllBattlers", # Perish Song
"SwitchOutTargetStatusMove", # Roar
"SwitchOutTargetDamagingMove" # Circle Throw
ret.push(:phazer)
when "CureUserPartyStatus" # Aromatherapy/Heal Bell
ret.push(:cleric)
when "DisableTargetStatusMoves" # Taunt
ret.push(:stall_breaker)
when "HealUserPositionNextTurn" # Wish
ret.push(:cleric) if pkmn.ev[:HP] == Pokemon::EV_STAT_LIMIT
when "HealUserFullyAndFallAsleep" # Rest
ret.push(:status_absorber)
when "SwitchOutUserPassOnEffects" # Baton Pass
ret.push(:baton_passer)
when "SwitchOutUserStatusMove", "SwitchOutUserDamagingMove" # Teleport (Gen 8+), U-turn
ret.push(:pivot) if hasHealMove
when "RemoveUserBindingAndEntryHazards" # Rapid Spin
ret.push(:spinner)
when "StartElectricTerrain", "StartGrassyTerrain",
"StartMistyTerrain", "StartPsychicTerrain" # Terrain moves
ret.push(:field_setter)
else
ret.push(:weather_setter) if move.is_a?(Battle::Move::WeatherMove)
end
end
# Check EVs, nature and moves for combinations indicative of particular roles
if pkmn.ev[:SPEED] == Pokemon::EV_STAT_LIMIT
if [:MODEST, :ADAMANT, # SpAtk+ Atk-, Atk+ SpAtk-
:TIMID, :JOLLY].include?(pkmn.nature) # Spd+ Atk-, Spd+ SpAtk-
ret.push(:sweeper)
end
end
if hasHealMove
if pkmn.nature.stat_changes.any? { |change| change[0] == :DEFENSE && change[1] > 0 } &&
!pkmn.nature.stat_changes.any? { |change| change[0] == :DEFENSE && change[1] < 0 }
ret.push(:physical_wall) if pkmn.ev[:DEFENSE] == Pokemon::EV_STAT_LIMIT
elsif pkmn.nature.stat_changes.any? { |change| change[0] == :SPECIAL_DEFENSE && change[1] > 0 } &&
!pkmn.nature.stat_changes.any? { |change| change[0] == :SPECIAL_DEFENSE && change[1] < 0 }
ret.push(:special_wall) if pkmn.ev[:SPECIAL_DEFENSE] == Pokemon::EV_STAT_LIMIT
end
else
ret.push(:tank) if pkmn.ev[:HP] == Pokemon::EV_STAT_LIMIT
end
# Check for abilities indicative of particular roles
case pkmn.ability_id
when :REGENERATOR
ret.push(:pivot)
when :GUTS, :QUICKFEET, :FLAREBOOST, :TOXICBOOST, :NATURALCURE, :MAGICGUARD,
:MAGICBOUNCE, :HYDRATION
ret.push(:status_absorber)
when :SHADOWTAG, :ARENATRAP, :MAGNETPULL
ret.push(:trapper)
when :DROUGHT, :DRIZZLE, :SANDSTREAM, :SNOWWARNING, :PRIMORDIALSEA,
:DESOLATELAND, :DELTASTREAM
ret.push(:weather_setter)
when :GRASSYSURGE, :ELECTRICSURGE, :MISTYSURGE, :PSYCHICSURGE
ret.push(:field_setter)
end
# Check for items indicative of particular roles
case pkmn.item_id
when :LIGHTCLAY
ret.push(:screener)
when :ASSAULTVEST
ret.push(:tank)
when :CHOICEBAND, :CHOICESPECS
ret.push(:stall_breaker)
ret.push(:sweeper) if pkmn.ev[:SPEED] == Pokemon::EV_STAT_LIMIT
when :CHOICESCARF
ret.push(:sweeper) if pkmn.ev[:SPEED] == Pokemon::EV_STAT_LIMIT
when :TOXICORB, :FLAMEORB
ret.push(:status_absorber)
when :TERRAINEXTENDER
ret.push(:field_setter)
end
# Check for position in team, level relative to other levels in team
partyStarts = @battle.pbPartyStarts(side)
if partyStarts.include?(index + 1) || index == @battle.pbParty(side).length - 1
ret.push(:ace) # Last in party, assumed to be the best Pokémon
else
ret.push(:lead) if partyStarts.include?(index) # First in party
idxTrainer = @battle.pbGetOwnerIndexFromPartyIndex(side, index)
maxLevel = @battle.pbMaxLevelInTeam(side, idxTrainer)
if pkmn.level >= maxLevel
ret.push(:second)
else
secondHighest = true
seenHigherLevel = false
@battle.eachInTeam(side, @battle.pbGetOwnerIndexFromPartyIndex(side, index)) do |p|
next if p.level < pkmn.level
if seenHigherLevel
secondHighest = false
break
end
seenHigherLevel = true
end
# NOTE: There can be multiple "second"s if all their levels are equal
# and the highest in the team (and none are the ace).
ret.push(:second) if secondHighest
end
end
return ret
end
def check_role(side, idxBattler, *roles)
role_array = @roles[side][idxBattler]
roles.each do |r|
return true if role_array.include?(r)
end
return false
end
def check_battler_role(battler, *roles)
side = idxParty.idxOwnSide
idxParty = idxParty.pokemonIndex
return check_role(side, idxParty, *roles)
end
end

View File

@@ -16,10 +16,6 @@ class Battle::AI::AIBattler
old_party_index = @party_index
@battler = @ai.battle.battlers[@index]
@party_index = battler.pokemonIndex
if @party_index != old_party_index
# TODO: Start of battle or Pokémon switched/shifted; recalculate roles,
# etc. What is etc.?
end
end
def pokemon; return battler.pokemon; end
@@ -232,12 +228,7 @@ class Battle::AI::AIBattler
end
alias pbHasType? has_type?
# TODO: Also make a def effectiveness_of_move_against_battler which calls
# pbCalcTypeModSingle instead of effectiveness_of_type_against_single_battler_type,
# for moves with custom def pbCalcTypeMod, e.g. Freeze-Dry. When would
# that be used instead? Or rather, when would THIS method be used if
# that existed?
def effectiveness_of_type_against_battler(type, user = nil)
def effectiveness_of_type_against_battler(type, user = nil, move = nil)
ret = Effectiveness::NORMAL_EFFECTIVE_MULTIPLIER
return ret if !type
return ret if type == :GROUND && has_type?(:FLYING) && has_active_item?(:IRONBALL)
@@ -250,7 +241,16 @@ class Battle::AI::AIBattler
end
else
battler.pbTypes(true).each do |defend_type|
ret *= effectiveness_of_type_against_single_battler_type(type, defend_type, user)
mult = effectiveness_of_type_against_single_battler_type(type, defend_type, user)
if move
case move.function
when "HitsTargetInSkyGroundsTarget"
mult = Effectiveness::NORMAL_EFFECTIVE_MULTIPLIER if type == :GROUND && defend_type == :FLYING
when "FreezeTargetSuperEffectiveAgainstWater"
mult = Effectiveness::SUPER_EFFECTIVE_MULTIPLIER if defend_type == :WATER
end
end
ret *= mult
end
ret *= 2 if self.effects[PBEffects::TarShot] && type == :FIRE
end
@@ -414,7 +414,9 @@ class Battle::AI::AIBattler
break
end
# Modify the rating based on ability-specific contexts
ret = Battle::AI::Handlers.modify_ability_ranking(ability, ret, self, @ai)
if @ai.trainer.medium_skill?
ret = Battle::AI::Handlers.modify_ability_ranking(ability, ret, self, @ai)
end
return ret
end
@@ -424,13 +426,12 @@ class Battle::AI::AIBattler
# battler if it is holding it.
# Return values are typically between -10 and +10. 0 is indifferent, positive
# values mean this battler benefits, negative values mean this battler suffers.
# TODO: This method shouldn't check for item effect negation, to work the same
# as def wants_ability?.
# NOTE: This method assumes the item isn't being negated. The calculations
# that call this method separately check for it being negated, because
# they need to do something special in that case.
def wants_item?(item)
item = :NONE if !item
item = item.id if !item.is_a?(Symbol) && item.respond_to?("id")
return 0 if has_active_ability?(:KLUTZ)
# TODO: Unnerve, other item-negating effects.
# Get the base item rating
ret = 0
Battle::AI::BASE_ITEM_RATINGS.each_pair do |val, items|
@@ -439,7 +440,9 @@ class Battle::AI::AIBattler
break
end
# Modify the rating based on item-specific contexts
ret = Battle::AI::Handlers.modify_item_ranking(item, ret, self, @ai)
if @ai.trainer.medium_skill?
ret = Battle::AI::Handlers.modify_item_ranking(item, ret, self, @ai)
end
# Prefer if this battler knows Fling and it will do a lot of damage/have an
# additional (negative) effect when flung
if item != :NONE && has_move_with_function?("ThrowUserItemAtTarget")
@@ -481,7 +484,18 @@ class Battle::AI::AIBattler
end
ret += (hp > totalhp * (1 - (1.0 / fraction_to_heal))) ? -6 : 6
ret = ret * 3 / 2 if GameData::Item.get(item).is_berry? && has_active_ability?(:RIPEN)
# TODO: Check whether the item will cause confusion?
if @ai.trainer.high_skill?
flavor_stat = {
:AGUAVBERRY => :SPECIAL_DEFENSE,
:FIGYBERRY => :ATTACK,
:IAPAPABERRY => :DEFENSE,
:MAGOBERRY => :SPEED,
:WIKIBERRY => :SPECIAL_ATTACK
}[item]
if @battler.nature.stat_changes.any? { |val| val[0] == flavor_stat && val[1] < 0 }
ret -= 3 if @battler.pbCanConfuseSelf?(false)
end
end
when :ASPEARBERRY, :CHERIBERRY, :CHESTOBERRY, :PECHABERRY, :RAWSTBERRY
# Status cure
cured_status = {

View File

@@ -101,7 +101,6 @@ class Battle::AI::AIMove
# Get the move's type
calc_type = rough_type
# 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
@@ -325,7 +324,7 @@ class Battle::AI::AIMove
end
end
# Type effectiveness
typemod = target.effectiveness_of_type_against_battler(calc_type, user)
typemod = target.effectiveness_of_type_against_battler(calc_type, user, @move)
multipliers[:final_damage_multiplier] *= typemod
# Burn
if @ai.trainer.high_skill? && user.status == :BURN && physicalMove?(calc_type) &&

View File

@@ -494,10 +494,6 @@ Battle::AI::Handlers::MoveEffectScore.add("RaiseUserMainStats1TrapUserInBattle",
next (move.damagingMove?) ? score : Battle::AI::MOVE_USELESS_SCORE
end
# Score for user becoming trapped in battle
# TODO: These checks are related to desire to switch, and there can be a lot
# more things to consider, e.g. effectiveness of the target's moves
# against its foes. Also applies to other code that calls
# can_become_trapped?
if user.effects[PBEffects::PerishSong] > 0 ||
user.effects[PBEffects::Attract] >= 0 ||
eor_damage > 0
@@ -632,7 +628,7 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("RaiseTargetAttack2Confu
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("RaiseTargetAttack2ConfuseTarget",
proc { |score, move, user, target, ai, battle|
if !target.has_active_ability?(:CONTRARY) || @battle.moldBreaker
if !target.has_active_ability?(:CONTRARY) || battle.moldBreaker
next Battle::AI::MOVE_USELESS_SCORE if !target.battler.pbCanConfuse?(user.battler, false, move.move)
end
# Score for stat raise
@@ -654,7 +650,7 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("RaiseTargetSpAtk1Confus
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("RaiseTargetSpAtk1ConfuseTarget",
proc { |score, move, user, target, ai, battle|
if !target.has_active_ability?(:CONTRARY) || @battle.moldBreaker
if !target.has_active_ability?(:CONTRARY) || battle.moldBreaker
next Battle::AI::MOVE_USELESS_SCORE if !target.battler.pbCanConfuse?(user.battler, false, move.move)
end
# Score for stat raise

View File

@@ -240,7 +240,7 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("ParalyzeTarget",
#===============================================================================
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("ParalyzeTargetIfNotTypeImmune",
proc { |move, user, target, ai, battle|
eff = target.effectiveness_of_type_against_battler(move.rough_type, user)
eff = target.effectiveness_of_type_against_battler(move.rough_type, user, move)
next true if Effectiveness.ineffective?(eff)
next true if move.statusMove? && !target.battler.pbCanParalyze?(user.battler, false, move.move)
next false

View File

@@ -1094,8 +1094,6 @@ Battle::AI::Handlers::MoveEffectScore.add("ProtectUserSideFromPriorityMoves",
useless = true
ai.each_foe_battler(user.side) do |b, i|
next if !b.can_attack?
# TODO: There are more calculations that could be done to get a more
# accurate priority number.
next if !b.check_for_move { |m| m.pbPriority(b.battler) > 0 && m.canProtectAgainst? }
useless = false
# General preference
@@ -1343,9 +1341,6 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("EnsureNextMoveAlwaysHits
# Prefer if the user knows moves with low accuracy
user.battler.eachMove do |m|
next if target.effects[PBEffects::Minimize] && m.tramplesMinimize? && Settings::MECHANICS_GENERATION >= 6
# TODO: There are other effects that make a move certain to hit. Account
# for those as well. Score this move useless if no moves would
# benefit from locking on.
acc = m.accuracy
acc = m.pbBaseAccuracy(user.battler, target.battler) if ai.trainer.medium_skill?
score += 5 if acc < 90 && acc != 0
@@ -1503,7 +1498,7 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TargetMovesBecomeElectri
next if !m.damagingMove?
m_type = m.pbCalcType(target.battler)
next if m_type == :ELECTRIC
eff = user.effectiveness_of_type_against_battler(m_type, target)
eff = user.effectiveness_of_type_against_battler(m_type, target, m)
eff *= 1.5 if target.has_type?(m_type) # STAB
case m_type
when :FIRE

View File

@@ -451,6 +451,7 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("TwoTurnAttackInvulnerab
proc { |move, user, target, ai, battle|
next true if !target.opposes?(user)
next true if target.effects[PBEffects::Substitute] > 0 && !move.move.ignoresSubstitute?(user.battler)
next true if target.has_type?(:FLYING)
next true if Settings::MECHANICS_GENERATION >= 6 && target.battler.pbWeight >= 2000 # 200.0kg
next true if target.battler.semiInvulnerable? || target.effects[PBEffects::SkyDrop] >= 0
next false

View File

@@ -11,10 +11,14 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("UserTakesTargetItem",
# User can steal the target's item; score it
user_item_preference = user.wants_item?(target.item_id)
user_no_item_preference = user.wants_item?(:NONE)
user_diff = user_item_preference - user_no_item_preference
user_diff = 0 if !user.item_active?
target_item_preference = target.wants_item?(target.item_id)
target_no_item_preference = target.wants_item?(:NONE)
score += user_item_preference - user_no_item_preference
score += target_item_preference - target_no_item_preference
target_diff = target_no_item_preference - target_item_preference
target_diff = 0 if !target.item_active?
score += user_diff * 4
score -= target_diff * 4
next score
}
)
@@ -33,10 +37,14 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TargetTakesUserItem",
proc { |score, move, user, target, ai, battle|
user_item_preference = user.wants_item?(user.item_id)
user_no_item_preference = user.wants_item?(:NONE)
user_diff = user_no_item_preference - user_item_preference
user_diff = 0 if !user.item_active?
target_item_preference = target.wants_item?(user.item_id)
target_no_item_preference = target.wants_item?(:NONE)
score += user_no_item_preference - user_item_preference
score += target_no_item_preference - target_item_preference
target_diff = target_item_preference - target_no_item_preference
target_diff = 0 if !target.item_active?
score += user_diff * 4
score -= target_diff * 4
next score
}
)
@@ -58,10 +66,14 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("UserTargetSwapItems",
proc { |score, move, user, target, ai, battle|
user_new_item_preference = user.wants_item?(target.item_id)
user_old_item_preference = user.wants_item?(user.item_id)
user_diff = user_new_item_preference - user_old_item_preference
user_diff = 0 if !user.item_active?
target_new_item_preference = target.wants_item?(user.item_id)
target_old_item_preference = target.wants_item?(target.item_id)
score += user_new_item_preference - user_old_item_preference
score += target_old_item_preference - target_new_item_preference
target_diff = target_new_item_preference - target_old_item_preference
target_diff = 0 if !target.item_active?
score += user_diff * 4
score -= target_diff * 4
# Don't prefer if user used this move in the last round
score -= 15 if user.battler.lastMoveUsed &&
GameData::Move.exists?(user.battler.lastMoveUsed) &&
@@ -80,9 +92,10 @@ Battle::AI::Handlers::MoveFailureCheck.add("RestoreUserConsumedItem",
)
Battle::AI::Handlers::MoveEffectScore.add("RestoreUserConsumedItem",
proc { |score, move, user, ai, battle|
user_new_item_preference = user.wants_item?(user.battler.recycleItem)
user_old_item_preference = user.wants_item?(:NONE)
score += (user_new_item_preference - user_old_item_preference) * 2
next Battle::AI::MOVE_USELESS_SCORE if !user.item_active?
item_preference = user.wants_item?(user.battler.recycleItem)
no_item_preference = user.wants_item?(:NONE)
score += (item_preference - no_item_preference) * 4
next score
}
)
@@ -101,10 +114,11 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("RemoveTargetItem",
next score if !target.item || target.battler.unlosableItem?(target.item)
next score if target.effects[PBEffects::Substitute] > 0
next score if target.has_active_ability?(:STICKYHOLD) && !battle.moldBreaker
next score if !target.item_active?
# User can knock off the target's item; score it
target_item_preference = target.wants_item?(target.item_id)
target_no_item_preference = target.wants_item?(:NONE)
score += (target_item_preference - target_no_item_preference) * 2
item_preference = target.wants_item?(target.item_id)
no_item_preference = target.wants_item?(:NONE)
score -= (no_item_preference - item_preference) * 4
next score
}
)
@@ -119,10 +133,11 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("DestroyTargetBerryOrGem"
next score if user.battler.unlosableItem?(target.item)
next score if target.effects[PBEffects::Substitute] > 0
next score if target.has_active_ability?(:STICKYHOLD) && !battle.moldBreaker
next score if !target.item_active?
# User can incinerate the target's item; score it
target_item_preference = target.wants_item?(target.item_id)
target_no_item_preference = target.wants_item?(:NONE)
score += (target_item_preference - target_no_item_preference) * 2
item_preference = target.wants_item?(target.item_id)
no_item_preference = target.wants_item?(:NONE)
score -= (no_item_preference - item_preference) * 4
next score
}
)
@@ -141,9 +156,11 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("CorrodeTargetItem",
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("CorrodeTargetItem",
proc { |score, move, user, target, ai, battle|
target_item_preference = target.wants_item?(target.item_id)
target_no_item_preference = target.wants_item?(:NONE)
score += (target_item_preference - target_no_item_preference) * 2
item_preference = target.wants_item?(target.item_id)
no_item_preference = target.wants_item?(:NONE)
target_diff = no_item_preference - item_preference
target_diff = 0 if !target.item_active?
score += target_diff * 4
next score
}
)
@@ -159,7 +176,8 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("StartTargetCannotUseIte
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("StartTargetCannotUseItem",
proc { |score, move, user, target, ai, battle|
next Battle::AI::MOVE_USELESS_SCORE if !target.item || !target.item_active?
# TODO: Useless if target's item cannot be negated.
# NOTE: We won't check if the item has an effect, because if a Pokémon is
# holding an item, it probably does.
item_score = target.wants_item?(target.item_id)
next Battle::AI::MOVE_USELESS_SCORE if item_score <= 0 # Item has no effect or is bad
score += item_score * 2
@@ -179,7 +197,8 @@ Battle::AI::Handlers::MoveEffectScore.add("StartNegateHeldItems",
next if !b.item
# Skip b if its item is disabled
if ai.trainer.medium_skill?
# TODO: Skip b if its item cannot be negated or it has no effect.
# NOTE: We won't check if the item has an effect, because if a Pokémon
# is holding an item, it probably does.
if battle.field.effects[PBEffects::MagicRoom] > 0
# NOTE: Same as b.item_active? but ignoring the Magic Room part.
next if b.effects[PBEffects::Embargo] > 0
@@ -297,9 +316,9 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("UserConsumeTargetBerry",
score -= 5 if target.has_active_ability?(:UNBURDEN)
end
# Score the target no longer having the item
target_item_preference = target.wants_item?(target.item_id)
target_no_item_preference = target.wants_item?(:NONE)
score += (target_item_preference - target_no_item_preference) * 2
item_preference = target.wants_item?(target.item_id)
no_item_preference = target.wants_item?(:NONE)
score -= (no_item_preference - item_preference) * 3
next score
}
)
@@ -349,9 +368,9 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("ThrowUserItemAtTarget",
end
# Prefer if the user doesn't want its held item/don't prefer if it wants to
# keep its held item
user_item_preference = user.wants_item?(user.item_id)
user_no_item_preference = user.wants_item?(:NONE)
score += (user_item_preference - user_no_item_preference) * 2
item_preference = user.wants_item?(user.item_id)
no_item_preference = user.wants_item?(:NONE)
score += (no_item_preference - item_preference) * 2
next score
}
)

View File

@@ -3,7 +3,7 @@
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("FleeFromBattle",
proc { |move, user, ai, battle|
next !battle.pbCanRun?(user.index)
next !battle.pbCanRun?(user.index) || (user.wild? && user.battler.allAllies.length > 0)
}
)
Battle::AI::Handlers::MoveEffectScore.add("FleeFromBattle",
@@ -14,35 +14,50 @@ Battle::AI::Handlers::MoveEffectScore.add("FleeFromBattle",
)
#===============================================================================
# TODO: Review score modifiers.
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("SwitchOutUserStatusMove",
proc { |move, user, ai, battle|
next !battle.pbCanRun?(user.index) if user.wild?
if user.wild?
next !battle.pbCanRun?(user.index) || user.battler.allAllies.length > 0
end
next !battle.pbCanChooseNonActive?(user.index)
}
)
Battle::AI::Handlers::MoveEffectScore.add("SwitchOutUserStatusMove",
proc { |score, move, user, ai, battle|
# Wild Pokémon run from battle
# Wild Pokémon run from battle - generally don't prefer (don't want to end the battle too easily)
next score - 20 if user.wild?
# Trainer-owned Pokémon switch out
if ai.trainer.has_skill_flag?("ReserveLastPokemon") && battle.pbTeamAbleNonActiveCount(user.index) == 1
next Battle::AI::MOVE_USELESS_SCORE # Don't switch in ace
end
# Prefer if the user switching out will lose a negative effect
score += 20 if user.effects[PBEffects::PerishSong] > 0
score += 10 if user.effects[PBEffects::Confusion] > 1
score += 10 if user.effects[PBEffects::Attract] >= 0
# Consider the user's stat stages
if user.stages.any? { |key, val| val >= 2 }
score -= 15
elsif user.stages.any? { |key, val| val < 0 }
score += 10
end
# Consider the user's end of round damage/healing
eor_damage = user.rough_end_of_round_damage
score += 15 if eor_damage > 0
score -= 15 if eor_damage < 0
# Prefer if the user doesn't have any damaging moves
# TODO: Check effectiveness of moves.
score += 15 if !user.check_for_move { |m| m.damagingMove? }
# Don't prefer the more stat raises the user has
GameData::Stat.each_battle { |s| score -= user.stages[s.id] * 5 }
score += 10 if !user.check_for_move { |m| m.damagingMove? }
# Don't prefer if the user's side has entry hazards on it
score -= 10 if user.pbOwnSide.effects[PBEffects::Spikes] > 0
score -= 10 if user.pbOwnSide.effects[PBEffects::ToxicSpikes] > 0
score -= 10 if user.pbOwnSide.effects[PBEffects::StealthRock]
next score
}
)
#===============================================================================
# TODO: Review score modifiers.
#
#===============================================================================
Battle::AI::Handlers::MoveEffectScore.add("SwitchOutUserDamagingMove",
proc { |score, move, user, ai, battle|
@@ -51,16 +66,29 @@ Battle::AI::Handlers::MoveEffectScore.add("SwitchOutUserDamagingMove",
score -= 20 if ai.trainer.has_skill_flag?("ReserveLastPokemon") &&
battle.pbTeamAbleNonActiveCount(user.index) == 1
# Prefer if the user switching out will lose a negative effect
score += 20 if user.effects[PBEffects::PerishSong] > 0
score += 10 if user.effects[PBEffects::Confusion] > 1
# Don't prefer the more stat raises the user has
GameData::Stat.each_battle { |s| score -= user.stages[s.id] * 5 }
score += 10 if user.effects[PBEffects::Attract] >= 0
# Consider the user's stat stages
if user.stages.any? { |key, val| val >= 2 }
score -= 15
elsif user.stages.any? { |key, val| val < 0 }
score += 10
end
# Consider the user's end of round damage/healing
eor_damage = user.rough_end_of_round_damage
score += 15 if eor_damage > 0
score -= 15 if eor_damage < 0
# Don't prefer if the user's side has entry hazards on it
score -= 10 if user.pbOwnSide.effects[PBEffects::Spikes] > 0
score -= 10 if user.pbOwnSide.effects[PBEffects::ToxicSpikes] > 0
score -= 10 if user.pbOwnSide.effects[PBEffects::StealthRock]
next score
}
)
#===============================================================================
# TODO: Review score modifiers.
# TODO: Might need both MoveEffectScore and MoveEffectAgainstTargetScore.
#
#===============================================================================
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("LowerTargetAtkSpAtk1SwitchOutUser",
proc { |move, user, target, ai, battle|
@@ -75,25 +103,14 @@ Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("LowerTargetAtkSpAtk1Swi
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("LowerTargetAtkSpAtk1SwitchOutUser",
proc { |score, move, user, target, ai, battle|
score = ai.get_score_for_target_stat_drop(score, target, move.move.statDown, false)
if battle.pbCanChooseNonActive?(user.index)
# Don't want to switch in ace
score -= 20 if ai.trainer.has_skill_flag?("ReserveLastPokemon") &&
battle.pbTeamAbleNonActiveCount(user.index) == 1
# Prefer if the user switching out will lose a negative effect
score += 10 if user.effects[PBEffects::Confusion] > 1
# Prefer if the user doesn't have any damaging moves
# TODO: Check effectiveness of moves.
score += 15 if !user.check_for_move { |m| m.damagingMove? }
# Don't prefer the more stat raises the user has
GameData::Stat.each_battle { |s| score -= user.stages[s.id] * 5 }
end
next score
next ai.get_score_for_target_stat_drop(score, target, move.move.statDown, false)
}
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.copy("SwitchOutUserDamagingMove",
"LowerTargetAtkSpAtk1SwitchOutUser")
#===============================================================================
# TODO: Review score modifiers.
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("SwitchOutUserPassOnEffects",
proc { |move, user, ai, battle|
@@ -107,56 +124,110 @@ Battle::AI::Handlers::MoveEffectScore.add("SwitchOutUserPassOnEffects",
battle.pbTeamAbleNonActiveCount(user.index) == 1
# Don't prefer if the user will pass on a negative effect
score -= 10 if user.effects[PBEffects::Confusion] > 1
score -= 15 if user.effects[PBEffects::Curse]
score -= 10 if user.effects[PBEffects::Embargo] > 1
score -= 15 if user.effects[PBEffects::GastroAcid]
score -= 10 if user.effects[PBEffects::HealBlock] > 1
score -= 10 if user.effects[PBEffects::LeechSeed] >= 0
score -= 20 if user.effects[PBEffects::PerishSong] > 0
# Prefer if the user will pass on a positive effect
score += 10 if user.effects[PBEffects::AquaRing]
score += 10 if user.effects[PBEffects::FocusEnergy] > 0
score += 10 if user.effects[PBEffects::Ingrain]
score += 8 if user.effects[PBEffects::MagnetRise] > 1
score += 10 if user.effects[PBEffects::Substitute] > 0
# Consider the user's stat stages
if user.stages.any? { |key, val| val >= 4 }
score += 25
elsif user.stages.any? { |key, val| val >= 2 }
score += 15
elsif user.stages.any? { |key, val| val < 0 }
score -= 15
end
# Consider the user's end of round damage/healing
eor_damage = user.rough_end_of_round_damage
score += 15 if eor_damage > 0
score -= 15 if eor_damage < 0
# Prefer if the user doesn't have any damaging moves
# TODO: Check effectiveness of moves.
score += 15 if !user.check_for_move { |m| m.damagingMove? }
# Prefer if the user will pass on good stat stages
GameData::Stat.each_battle { |s| score += user.stages[s.id] * 5 }
# Don't prefer if the user's side has entry hazards on it
score -= 10 if user.pbOwnSide.effects[PBEffects::Spikes] > 0
score -= 10 if user.pbOwnSide.effects[PBEffects::ToxicSpikes] > 0
score -= 10 if user.pbOwnSide.effects[PBEffects::StealthRock]
next score
}
)
#===============================================================================
# TODO: Review score modifiers.
#
#===============================================================================
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("SwitchOutTargetStatusMove",
proc { |move, user, target, ai, battle|
next true if (!battle.moldBreaker && target.has_active_ability?(:SUCTIONCUPS)) ||
target.effects[PBEffects::Ingrain]
next true if !battle.canRun
next true if battle.wildBattle? && target.level > user.level
if battle.trainerBattle?
will_fail = true
battle.eachInTeamFromBattlerIndex(target.index) do |_pkmn, i|
next if !battle.pbCanSwitchIn?(target.index, i)
will_fail = false
break
end
next will_fail
end
next false
next move.move.pbFailsAgainstTarget?(user.battler, target.battler, false)
}
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("SwitchOutTargetStatusMove",
proc { |score, move, user, target, ai, battle|
score += 15 if target.pbOwnSide.effects[PBEffects::Spikes] > 0
score += 15 if target.pbOwnSide.effects[PBEffects::ToxicSpikes] > 0
score += 15 if target.pbOwnSide.effects[PBEffects::StealthRock]
# Ends the battle - generally don't prefer (don't want to end the battle too easily)
next score - 10 if target.wild?
# Switches the target out
next Battle::AI::MOVE_USELESS_SCORE if target.effects[PBEffects::PerishSong] > 0
# Don't prefer if target is at low HP and could be knocked out instead
if ai.trainer.has_skill_flag?("HPAware")
score -= 10 if target.hp <= target.totalhp / 3
end
# Consider the target's stat stages
if target.stages.any? { |key, val| val >= 2 }
score += 15
elsif target.stages.any? { |key, val| val < 0 }
score -= 15
end
# Consider the target's end of round damage/healing
eor_damage = target.rough_end_of_round_damage
score -= 15 if eor_damage > 0
score += 15 if eor_damage < 0
# Prefer if the target's side has entry hazards on it
score += 10 if target.pbOwnSide.effects[PBEffects::Spikes] > 0
score += 10 if target.pbOwnSide.effects[PBEffects::ToxicSpikes] > 0
score += 10 if target.pbOwnSide.effects[PBEffects::StealthRock]
next score
}
)
#===============================================================================
# TODO: Review score modifiers.
#
#===============================================================================
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("SwitchOutTargetDamagingMove",
proc { |score, move, user, target, ai, battle|
if (battle.moldBreaker || !target.has_active_ability?(:SUCTIONCUPS)) &&
!target.effects[PBEffects::Ingrain]
score += 15 if target.pbOwnSide.effects[PBEffects::Spikes] > 0
score += 15 if target.pbOwnSide.effects[PBEffects::ToxicSpikes] > 0
score += 15 if target.pbOwnSide.effects[PBEffects::StealthRock]
next score if target.wild?
# No score modification if the target can't be made to switch out
next score if !battle.moldBreaker && target.has_active_ability?(:SUCTIONCUPS)
next score if target.effects[PBEffects::Ingrain]
# No score modification if the target can't be replaced
can_switch = false
battle.eachInTeamFromBattlerIndex(target.index) do |_pkmn, i|
can_switch = battle.pbCanSwitchIn?(target.index, i)
break if can_switch
end
next score if !can_switch
# Not score modification if the target has a Substitute
next score if target.effects[PBEffects::Substitute] > 0
# Don't want to switch out the target if it will faint from Perish Song
score -= 20 if target.effects[PBEffects::PerishSong] > 0
# Consider the target's stat stages
if target.stages.any? { |key, val| val >= 2 }
score += 15
elsif target.stages.any? { |key, val| val < 0 }
score -= 15
end
# Consider the target's end of round damage/healing
eor_damage = target.rough_end_of_round_damage
score -= 15 if eor_damage > 0
score += 15 if eor_damage < 0
# Prefer if the target's side has entry hazards on it
score += 10 if target.pbOwnSide.effects[PBEffects::Spikes] > 0
score += 10 if target.pbOwnSide.effects[PBEffects::ToxicSpikes] > 0
score += 10 if target.pbOwnSide.effects[PBEffects::StealthRock]
next score
}
)
@@ -242,12 +313,9 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TrapTargetInBattle",
next score if add_effect == -999 # Additional effect will be negated
score += add_effect
# Score for target becoming trapped in battle
# TODO: These checks are related to desire to switch, and there can be a lot
# more things to consider, e.g. effectiveness of the target's moves
# against its foes. Also applies to other code that calls
# can_become_trapped?
if target.effects[PBEffects::PerishSong] > 0 ||
target.effects[PBEffects::Attract] >= 0 ||
target.effects[PBEffects::Confusion] > 0 ||
eor_damage > 0
score += 15
end
@@ -290,12 +358,9 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TrapTargetInBattleLowerT
next (move.damagingMove?) ? score : Battle::AI::MOVE_USELESS_SCORE
end
# Score for target becoming trapped in battle
# TODO: These checks are related to desire to switch, and there can be a lot
# more things to consider, e.g. effectiveness of the target's moves
# against its foes. Also applies to other code that calls
# can_become_trapped?
if target.effects[PBEffects::PerishSong] > 0 ||
target.effects[PBEffects::Attract] >= 0 ||
target.effects[PBEffects::Confusion] > 0 ||
eor_damage > 0
score += 15
end
@@ -324,12 +389,9 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TrapUserAndTargetInBattl
next (move.damagingMove?) ? score : Battle::AI::MOVE_USELESS_SCORE
end
# Score for target becoming trapped in battle
# TODO: These checks are related to desire to switch, and there can be a lot
# more things to consider, e.g. effectiveness of the target's moves
# against its foes. Also applies to other code that calls
# can_become_trapped?
if target.effects[PBEffects::PerishSong] > 0 ||
target.effects[PBEffects::Attract] >= 0 ||
target.effects[PBEffects::Confusion] > 0 ||
eor_damage > 0
score += 15
end
@@ -339,16 +401,22 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TrapUserAndTargetInBattl
)
#===============================================================================
# TODO: Review score modifiers.
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("TrapAllBattlersInBattleForOneTurn",
proc { |move, user, ai, battle|
next battle.field.effects[PBEffects::FairyLock] > 0
}
)
Battle::AI::Handlers::MoveEffectScore.add("TrapAllBattlersInBattleForOneTurn",
proc { |score, move, user, ai, battle|
# Trapping for just one turn isn't so significant, so generally don't prefer
next score - 10
}
)
#===============================================================================
# TODO: Review score modifiers.
#
#===============================================================================
# PursueSwitchingFoe
@@ -661,7 +729,6 @@ Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("DisableTargetUsingDiffer
if move_data.status?
# Prefer encoring status moves
if [:User, :BothSides].include?(move_data.target)
# TODO: This target distinction was in the old code. Is it appropriate?
score += 10
else
score += 8

View File

@@ -242,8 +242,7 @@ Battle::ItemEffects::SpeedCalc.copy(:MACHOBRACE, :POWERANKLET, :POWERBAND,
Battle::ItemEffects::SpeedCalc.add(:QUICKPOWDER,
proc { |item, battler, mult|
next mult * 2 if battler.isSpecies?(:DITTO) &&
!battler.effects[PBEffects::Transform]
next mult * 2 if battler.isSpecies?(:DITTO) && !battler.effects[PBEffects::Transform]
}
)
@@ -263,7 +262,7 @@ Battle::ItemEffects::WeightCalc.add(:FLOATSTONE,
Battle::ItemEffects::HPHeal.add(:AGUAVBERRY,
proc { |item, battler, battle, forced|
next battler.pbConfusionBerry(item, forced, 4,
next battler.pbConfusionBerry(item, forced, :SPECIAL_DEFENSE,
_INTL("For {1}, the {2} was too bitter!", battler.pbThis(true), GameData::Item.get(item).name)
)
}
@@ -294,7 +293,7 @@ Battle::ItemEffects::HPHeal.add(:BERRYJUICE,
Battle::ItemEffects::HPHeal.add(:FIGYBERRY,
proc { |item, battler, battle, forced|
next battler.pbConfusionBerry(item, forced, 0,
next battler.pbConfusionBerry(item, forced, :ATTACK,
_INTL("For {1}, the {2} was too spicy!", battler.pbThis(true), GameData::Item.get(item).name)
)
}
@@ -308,7 +307,7 @@ Battle::ItemEffects::HPHeal.add(:GANLONBERRY,
Battle::ItemEffects::HPHeal.add(:IAPAPABERRY,
proc { |item, battler, battle, forced|
next battler.pbConfusionBerry(item, forced, 1,
next battler.pbConfusionBerry(item, forced, :DEFENSE,
_INTL("For {1}, the {2} was too sour!", battler.pbThis(true), GameData::Item.get(item).name)
)
}
@@ -338,7 +337,7 @@ Battle::ItemEffects::HPHeal.add(:LIECHIBERRY,
Battle::ItemEffects::HPHeal.add(:MAGOBERRY,
proc { |item, battler, battle, forced|
next battler.pbConfusionBerry(item, forced, 2,
next battler.pbConfusionBerry(item, forced, :SPEED,
_INTL("For {1}, the {2} was too sweet!", battler.pbThis(true), GameData::Item.get(item).name)
)
}
@@ -436,7 +435,7 @@ Battle::ItemEffects::HPHeal.add(:STARFBERRY,
Battle::ItemEffects::HPHeal.add(:WIKIBERRY,
proc { |item, battler, battle, forced|
next battler.pbConfusionBerry(item, forced, 3,
next battler.pbConfusionBerry(item, forced, :SPECIAL_ATTACK,
_INTL("For {1}, the {2} was too dry!", battler.pbThis(true), GameData::Item.get(item).name)
)
}