Reapplied various AI changes from previous atempt at improving the AI, split function code move score changes into handlers

This commit is contained in:
Maruno17
2022-08-21 15:59:49 +01:00
parent 4075204038
commit b094a2fd8e
24 changed files with 5987 additions and 4017 deletions

View File

@@ -91,94 +91,76 @@ class NamedEvent
end end
#=============================================================================== #===============================================================================
# Unused. # A class that stores code that can be triggered. Each piece of code has an
# associated ID, which can be anything that can be used as a key in a hash.
#=============================================================================== #===============================================================================
class HandlerHash class HandlerHash
def initialize(mod) def initialize
@mod = mod @hash = {}
@hash = {} @add_ifs = []
@addIfs = []
@symbolCache = {}
end end
def fromSymbol(sym) def [](id)
return sym unless sym.is_a?(Symbol) || sym.is_a?(String) return @hash[id] if id && @hash[id]
mod = Object.const_get(@mod) rescue nil @add_ifs.each do |add_if|
return nil if !mod return add_if[2] if add_if[1].call(id)
return mod.const_get(sym.to_sym) rescue nil
end
def toSymbol(sym)
return sym.to_sym if sym.is_a?(Symbol) || sym.is_a?(String)
ret = @symbolCache[sym]
return ret if ret
mod = Object.const_get(@mod) rescue nil
return nil if !mod
mod.constants.each do |key|
next if mod.const_get(key) != sym
ret = key.to_sym
@symbolCache[sym] = ret
break
end end
return ret return nil
end end
def addIf(conditionProc, handler = nil, &handlerBlock) def add(id, handler = nil, &handlerBlock)
if ![Proc, Hash].include?(handler.class) && !block_given? if ![Proc, Hash].include?(handler.class) && !block_given?
raise ArgumentError, "addIf call for #{self.class.name} has no valid handler (#{handler.inspect} was given)" raise ArgumentError, "#{self.class.name} for #{id.inspect} has no valid handler (#{handler.inspect} was given)"
end end
@addIfs.push([conditionProc, handler || handlerBlock]) @hash[id] = handler || handlerBlock if id && !id.empty?
end end
def add(sym, handler = nil, &handlerBlock) # 'sym' can be an ID or symbol def add_if(id, conditionProc, handler = nil, &handlerBlock)
if ![Proc, Hash].include?(handler.class) && !block_given? if ![Proc, Hash].include?(handler.class) && !block_given?
raise ArgumentError, "#{self.class.name} for #{sym.inspect} has no valid handler (#{handler.inspect} was given)" raise ArgumentError, "add_if call for #{self.class.name} has no valid handler (#{handler.inspect} was given)"
end end
id = fromSymbol(sym) @add_ifs.push([id, conditionProc, handler || handlerBlock])
@hash[id] = handler || handlerBlock if id
symbol = toSymbol(sym)
@hash[symbol] = handler || handlerBlock if symbol
end end
def copy(src, *dests) def copy(src, *dests)
handler = self[src] handler = self[src]
if handler return if !handler
dests.each do |dest| dests.each { |dest| add(dest, handler) }
self.add(dest, handler)
end
end
end end
def [](sym) # 'sym' can be an ID or symbol def remove(key)
id = fromSymbol(sym) if @hash.keys.include?(key)
ret = nil @hash.delete(key)
ret = @hash[id] if id && @hash[id] # Real ID from the item else
symbol = toSymbol(sym) @add_ifs.delete_if { |add_if| add_if[0] == key }
ret = @hash[symbol] if symbol && @hash[symbol] # Symbol or string
unless ret
@addIfs.each do |addif|
return addif[1] if addif[0].call(id)
end
end end
return ret
end
def trigger(sym, *args)
handler = self[sym]
return (handler) ? handler.call(fromSymbol(sym), *args) : nil
end end
def clear def clear
@hash.clear @hash.clear
@add_ifs.clear
end
def each
@hash.each_pair { |key, value| yield key, value }
end
def keys
return @hash.keys.clone
end
# NOTE: The call does not pass id as a parameter to the proc/block.
def trigger(id, *args)
handler = self[id]
return handler&.call(*args)
end end
end end
#=============================================================================== #===============================================================================
# A stripped-down version of class HandlerHash which only deals with symbols and # A stripped-down version of class HandlerHash which only deals with IDs that
# doesn't care about whether those symbols are defined as constants in a class # are symbols.
# or module.
#=============================================================================== #===============================================================================
class HandlerHash2 class HandlerHashSymbol
def initialize def initialize
@hash = {} @hash = {}
@add_ifs = [] @add_ifs = []
@@ -219,6 +201,7 @@ class HandlerHash2
def clear def clear
@hash.clear @hash.clear
@add_ifs.clear
end end
def trigger(sym, *args) def trigger(sym, *args)
@@ -229,32 +212,63 @@ class HandlerHash2
end end
#=============================================================================== #===============================================================================
# An even more stripped down version of class HandlerHash which just takes # A specialised version of class HandlerHash which only deals with IDs that are
# hashes with keys, no matter what the keys are. # constants in a particular class or module. That class or module must be
# defined when creating an instance of this class.
# Unused.
#=============================================================================== #===============================================================================
class HandlerHashBasic class HandlerHashEnum
def initialize def initialize(mod)
@hash = {} @mod = mod
@addIfs = [] @hash = {}
@addIfs = []
@symbolCache = {}
end end
def [](entry) def [](sym) # 'sym' can be an ID or symbol
id = fromSymbol(sym)
ret = nil ret = nil
ret = @hash[entry] if entry && @hash[entry] ret = @hash[id] if id && @hash[id] # Real ID from the item
symbol = toSymbol(sym)
ret = @hash[symbol] if symbol && @hash[symbol] # Symbol or string
unless ret unless ret
@addIfs.each do |addif| @addIfs.each do |addif|
return addif[1] if addif[0].call(entry) return addif[1] if addif[0].call(id)
end end
end end
return ret return ret
end end
def add(entry, handler = nil, &handlerBlock) def fromSymbol(sym)
if ![Proc, Hash].include?(handler.class) && !block_given? return sym unless sym.is_a?(Symbol) || sym.is_a?(String)
raise ArgumentError, "#{self.class.name} for #{entry.inspect} has no valid handler (#{handler.inspect} was given)" mod = Object.const_get(@mod) rescue nil
return nil if !mod
return mod.const_get(sym.to_sym) rescue nil
end
def toSymbol(sym)
return sym.to_sym if sym.is_a?(Symbol) || sym.is_a?(String)
ret = @symbolCache[sym]
return ret if ret
mod = Object.const_get(@mod) rescue nil
return nil if !mod
mod.constants.each do |key|
next if mod.const_get(key) != sym
ret = key.to_sym
@symbolCache[sym] = ret
break
end end
return if !entry || entry.empty? return ret
@hash[entry] = handler || handlerBlock end
def add(sym, handler = nil, &handlerBlock) # 'sym' can be an ID or symbol
if ![Proc, Hash].include?(handler.class) && !block_given?
raise ArgumentError, "#{self.class.name} for #{sym.inspect} has no valid handler (#{handler.inspect} was given)"
end
id = fromSymbol(sym)
@hash[id] = handler || handlerBlock if id
symbol = toSymbol(sym)
@hash[symbol] = handler || handlerBlock if symbol
end end
def addIf(conditionProc, handler = nil, &handlerBlock) def addIf(conditionProc, handler = nil, &handlerBlock)
@@ -267,42 +281,31 @@ class HandlerHashBasic
def copy(src, *dests) def copy(src, *dests)
handler = self[src] handler = self[src]
return if !handler return if !handler
dests.each { |dest| add(dest, handler) } dests.each { |dest| self.add(dest, handler) }
end
def remove(key)
@hash.delete(key)
end end
def clear def clear
@hash.clear @hash.clear
@addIfs.clear
end end
def each def trigger(sym, *args)
@hash.each_pair { |key, value| yield key, value } handler = self[sym]
end return (handler) ? handler.call(fromSymbol(sym), *args) : nil
def keys
return @hash.keys.clone
end
def trigger(entry, *args)
handler = self[entry]
return handler&.call(*args)
end end
end end
#=============================================================================== #===============================================================================
# #
#=============================================================================== #===============================================================================
class SpeciesHandlerHash < HandlerHash2 class SpeciesHandlerHash < HandlerHashSymbol
end end
class AbilityHandlerHash < HandlerHash2 class AbilityHandlerHash < HandlerHashSymbol
end end
class ItemHandlerHash < HandlerHash2 class ItemHandlerHash < HandlerHashSymbol
end end
class MoveHandlerHash < HandlerHash2 class MoveHandlerHash < HandlerHashSymbol
end end

View File

@@ -81,7 +81,7 @@ module MenuHandlers
@@handlers = {} @@handlers = {}
def self.add(menu, option, hash) def self.add(menu, option, hash)
@@handlers[menu] = HandlerHashBasic.new if !@@handlers.has_key?(menu) @@handlers[menu] = HandlerHash.new if !@@handlers.has_key?(menu)
@@handlers[menu].add(option, hash) @@handlers[menu].add(option, hash)
end end

View File

@@ -1,25 +1,34 @@
# AI skill levels: #===============================================================================
# 0: Wild Pokémon #
# 1-31: Basic trainer (young/inexperienced) #===============================================================================
# 32-47: Some skill
# 48-99: High skill
# 100+: Best trainers (Gym Leaders, Elite Four, Champion)
# NOTE: A trainer's skill value can range from 0-255, but by default only four
# distinct skill levels exist. The skill value is typically the same as
# the trainer's base money value.
module PBTrainerAI
# Minimum skill level to be in each AI category.
def self.minimumSkill; return 1; end
def self.mediumSkill; return 32; end
def self.highSkill; return 48; end
def self.bestSkill; return 100; end
end
class Battle::AI class Battle::AI
# AI skill levels:
# 0: Wild Pokémon
# 1-31: Basic trainer (young/inexperienced)
# 32-47: Some skill
# 48-99: High skill
# 100+: Best trainers (Gym Leaders, Elite Four, Champion)
# NOTE: A trainer's skill value can range from 0-255, but by default only four
# distinct skill levels exist. The skill value is typically the same as
# the trainer's base money value.
module AILevel
# Minimum skill level to be in each AI skill bracket.
def self.minimum; return 1; end
def self.medium; return 32; end
def self.high; return 48; end
def self.best; return 100; end
end
#=============================================================================
#
#=============================================================================
def initialize(battle) def initialize(battle)
@battle = battle @battle = battle
@skill = 0
@user = nil
@wildBattler = @battle.wildBattle? # Whether AI is choosing for a wild Pokémon
@roles = [Array.new(@battle.pbParty(0).length) { |i| determine_roles(0, i) },
Array.new(@battle.pbParty(1).length) { |i| determine_roles(1, i) }]
end end
def pbAIRandom(x); return rand(x); end def pbAIRandom(x); return rand(x); end
@@ -44,26 +53,66 @@ class Battle::AI
return Math.sqrt(varianceTimesN / n) return Math.sqrt(varianceTimesN / n)
end end
#============================================================================= # Decide whether the opponent should Mega Evolve their Pokémon.
# Decide whether the opponent should Mega Evolve their Pokémon def pbEnemyShouldMegaEvolve?
#============================================================================= if @battle.pbCanMegaEvolve?(@user.index) # Simple "always should if possible"
def pbEnemyShouldMegaEvolve?(idxBattler) PBDebug.log("[AI] #{@user.pbThis} (#{@user.index}) will Mega Evolve")
battler = @battle.battlers[idxBattler]
if @battle.pbCanMegaEvolve?(idxBattler) # Simple "always should if possible"
PBDebug.log("[AI] #{battler.pbThis} (#{idxBattler}) will Mega Evolve")
return true return true
end end
return false return false
end end
#============================================================================= # Choose an action.
# Choose an action
#=============================================================================
def pbDefaultChooseEnemyCommand(idxBattler) def pbDefaultChooseEnemyCommand(idxBattler)
return if pbEnemyShouldUseItem?(idxBattler) set_up(idxBattler)
return if pbEnemyShouldWithdraw?(idxBattler) choices = pbGetMoveScores
return if pbEnemyShouldUseItem?
return if pbEnemyShouldWithdraw?
return if @battle.pbAutoFightMenu(idxBattler) return if @battle.pbAutoFightMenu(idxBattler)
@battle.pbRegisterMegaEvolution(idxBattler) if pbEnemyShouldMegaEvolve?(idxBattler) @battle.pbRegisterMegaEvolution(idxBattler) if pbEnemyShouldMegaEvolve?
pbChooseMoves(idxBattler) pbChooseMove(choices)
end
# Set some class variables for the Pokémon whose action is being chosen
def set_up(idxBattler)
# TODO: Where relevant, pretend the user is Mega Evolved if it isn't but can
# be.
@user = @battle.battlers[idxBattler]
@wildBattler = (@battle.wildBattle? && @user.opposes?)
@skill = 0
if !@wildBattler
@skill = @battle.pbGetOwnerFromBattlerIndex(@user.index).skill_level || 0
@skill = AILevel.minimum if @skill < AILevel.minimum
end
end
def skill_check(threshold)
return @skill >= threshold
end
end
#===============================================================================
#
#===============================================================================
module Battle::AI::Handlers
MoveEffectScore = HandlerHash.new
MoveBasePower = HandlerHash.new
# Move type
# Move accuracy
# Move target
# Move additional effect chance
# Move unselectable check
# Move failure check
def self.apply_move_effect_score(function_code, score, *args)
function_code = function_code.to_sym
ret = MoveEffectScore.trigger(function_code, score, *args)
return (ret.nil?) ? score : ret
end
def self.get_base_power(function_code, power, *args)
function_code = function_code.to_sym
ret = MoveBasePower.trigger(function_code, *args)
return (ret.nil?) ? power : ret
end end
end end

View File

@@ -2,9 +2,8 @@ class Battle::AI
#============================================================================= #=============================================================================
# Decide whether the opponent should use an item on the Pokémon # Decide whether the opponent should use an item on the Pokémon
#============================================================================= #=============================================================================
def pbEnemyShouldUseItem?(idxBattler) def pbEnemyShouldUseItem?
user = @battle.battlers[idxBattler] item, idxTarget = pbEnemyItemToUse
item, idxTarget = pbEnemyItemToUse(idxBattler)
return false if !item return false if !item
# Determine target of item (always the Pokémon choosing the action) # Determine target of item (always the Pokémon choosing the action)
useType = GameData::Item.get(item).battle_use useType = GameData::Item.get(item).battle_use
@@ -12,19 +11,19 @@ class Battle::AI
idxTarget = @battle.battlers[idxTarget].pokemonIndex # Party Pokémon idxTarget = @battle.battlers[idxTarget].pokemonIndex # Party Pokémon
end end
# Register use of item # Register use of item
@battle.pbRegisterItem(idxBattler, item, idxTarget) @battle.pbRegisterItem(@user.index, item, idxTarget)
PBDebug.log("[AI] #{user.pbThis} (#{user.index}) will use item #{GameData::Item.get(item).name}") PBDebug.log("[AI] #{@user.pbThis} (#{@user.index}) will use item #{GameData::Item.get(item).name}")
return true return true
end end
# NOTE: The AI will only consider using an item on the Pokémon it's currently # NOTE: The AI will only consider using an item on the Pokémon it's currently
# choosing an action for. # choosing an action for.
def pbEnemyItemToUse(idxBattler) def pbEnemyItemToUse
return nil if !@battle.internalBattle return nil if !@battle.internalBattle
items = @battle.pbGetOwnerItems(idxBattler) items = @battle.pbGetOwnerItems(@user.index)
return nil if !items || items.length == 0 return nil if !items || items.length == 0
# Determine target of item (always the Pokémon choosing the action) # Determine target of item (always the Pokémon choosing the action)
idxTarget = idxBattler # Battler using the item idxTarget = @user.index # Battler using the item
battler = @battle.battlers[idxTarget] battler = @battle.battlers[idxTarget]
pkmn = battler.pokemon pkmn = battler.pokemon
# Item categories # Item categories

View File

@@ -2,26 +2,24 @@ class Battle::AI
#============================================================================= #=============================================================================
# Decide whether the opponent should switch Pokémon # Decide whether the opponent should switch Pokémon
#============================================================================= #=============================================================================
def pbEnemyShouldWithdraw?(idxBattler) def pbEnemyShouldWithdraw?
return pbEnemyShouldWithdrawEx?(idxBattler, false) return pbEnemyShouldWithdrawEx?(false)
end end
def pbEnemyShouldWithdrawEx?(idxBattler, forceSwitch) def pbEnemyShouldWithdrawEx?(forceSwitch)
return false if @battle.wildBattle? return false if @wildBattler
shouldSwitch = forceSwitch shouldSwitch = forceSwitch
batonPass = -1 batonPass = -1
moveType = nil moveType = nil
skill = @battle.pbGetOwnerFromBattlerIndex(idxBattler).skill_level || 0
battler = @battle.battlers[idxBattler]
# If Pokémon is within 6 levels of the foe, and foe's last move was # If Pokémon is within 6 levels of the foe, and foe's last move was
# super-effective and powerful # super-effective and powerful
if !shouldSwitch && battler.turnCount > 0 && skill >= PBTrainerAI.highSkill if !shouldSwitch && @user.turnCount > 0 && skill_check(AILevel.high)
target = battler.pbDirectOpposing(true) target = @user.pbDirectOpposing(true)
if !target.fainted? && target.lastMoveUsed && if !target.fainted? && target.lastMoveUsed &&
(target.level - battler.level).abs <= 6 (target.level - @user.level).abs <= 6
moveData = GameData::Move.get(target.lastMoveUsed) moveData = GameData::Move.get(target.lastMoveUsed)
moveType = moveData.type moveType = moveData.type
typeMod = pbCalcTypeMod(moveType, target, battler) typeMod = pbCalcTypeMod(moveType, target, @user)
if Effectiveness.super_effective?(typeMod) && moveData.base_damage > 50 if Effectiveness.super_effective?(typeMod) && moveData.base_damage > 50
switchChance = (moveData.base_damage > 70) ? 30 : 20 switchChance = (moveData.base_damage > 70) ? 30 : 20
shouldSwitch = (pbAIRandom(100) < switchChance) shouldSwitch = (pbAIRandom(100) < switchChance)
@@ -29,77 +27,76 @@ class Battle::AI
end end
end end
# Pokémon can't do anything (must have been in battle for at least 5 rounds) # Pokémon can't do anything (must have been in battle for at least 5 rounds)
if !@battle.pbCanChooseAnyMove?(idxBattler) && if !@battle.pbCanChooseAnyMove?(@user.index) &&
battler.turnCount && battler.turnCount >= 5 @user.turnCount && @user.turnCount >= 5
shouldSwitch = true shouldSwitch = true
end end
# Pokémon is Perish Songed and has Baton Pass # Pokémon is Perish Songed and has Baton Pass
if skill >= PBTrainerAI.highSkill && battler.effects[PBEffects::PerishSong] == 1 if skill_check(AILevel.high) && @user.effects[PBEffects::PerishSong] == 1
battler.eachMoveWithIndex do |m, i| @user.eachMoveWithIndex do |m, i|
next if m.function != "SwitchOutUserPassOnEffects" # Baton Pass next if m.function != "SwitchOutUserPassOnEffects" # Baton Pass
next if !@battle.pbCanChooseMove?(idxBattler, i, false) next if !@battle.pbCanChooseMove?(@user.index, i, false)
batonPass = i batonPass = i
break break
end end
end end
# Pokémon will faint because of bad poisoning at the end of this round, but # Pokémon will faint because of bad poisoning at the end of this round, but
# would survive at least one more round if it were regular poisoning instead # would survive at least one more round if it were regular poisoning instead
if battler.status == :POISON && battler.statusCount > 0 && if @user.status == :POISON && @user.statusCount > 0 && skill_check(AILevel.high)
skill >= PBTrainerAI.highSkill toxicHP = @user.totalhp / 16
toxicHP = battler.totalhp / 16 nextToxicHP = toxicHP * (@user.effects[PBEffects::Toxic] + 1)
nextToxicHP = toxicHP * (battler.effects[PBEffects::Toxic] + 1) if @user.hp <= nextToxicHP && @user.hp > toxicHP * 2
if battler.hp <= nextToxicHP && battler.hp > toxicHP * 2 && pbAIRandom(100) < 80 shouldSwitch = true if pbAIRandom(100) < 80
shouldSwitch = true
end end
end end
# Pokémon is Encored into an unfavourable move # Pokémon is Encored into an unfavourable move
if battler.effects[PBEffects::Encore] > 0 && skill >= PBTrainerAI.mediumSkill if @user.effects[PBEffects::Encore] > 0 && skill_check(AILevel.medium)
idxEncoredMove = battler.pbEncoredMoveIndex idxEncoredMove = @user.pbEncoredMoveIndex
if idxEncoredMove >= 0 if idxEncoredMove >= 0
scoreSum = 0 scoreSum = 0
scoreCount = 0 scoreCount = 0
battler.allOpposing.each do |b| @user.allOpposing.each do |b|
scoreSum += pbGetMoveScore(battler.moves[idxEncoredMove], battler, b, skill) scoreSum += pbGetMoveScore(@user.moves[idxEncoredMove], b)
scoreCount += 1 scoreCount += 1
end end
if scoreCount > 0 && scoreSum / scoreCount <= 20 && pbAIRandom(100) < 80 if scoreCount > 0 && scoreSum / scoreCount <= 20
shouldSwitch = true shouldSwitch = true if pbAIRandom(100) < 80
end end
end end
end end
# If there is a single foe and it is resting after Hyper Beam or is # If there is a single foe and it is resting after Hyper Beam or is
# Truanting (i.e. free turn) # Truanting (i.e. free turn)
if @battle.pbSideSize(battler.index + 1) == 1 && if @battle.pbSideSize(@user.index + 1) == 1 &&
!battler.pbDirectOpposing.fainted? && skill >= PBTrainerAI.highSkill !@user.pbDirectOpposing.fainted? && skill_check(AILevel.high)
opp = battler.pbDirectOpposing opp = @user.pbDirectOpposing
if (opp.effects[PBEffects::HyperBeam] > 0 || if (opp.effects[PBEffects::HyperBeam] > 0 ||
(opp.hasActiveAbility?(:TRUANT) && opp.effects[PBEffects::Truant])) && pbAIRandom(100) < 80 (opp.hasActiveAbility?(:TRUANT) && opp.effects[PBEffects::Truant]))
shouldSwitch = false shouldSwitch = false if pbAIRandom(100) < 80
end end
end end
# Sudden Death rule - I'm not sure what this means # Sudden Death rule - I'm not sure what this means
if @battle.rules["suddendeath"] && battler.turnCount > 0 if @battle.rules["suddendeath"] && @user.turnCount > 0
if battler.hp <= battler.totalhp / 4 && pbAIRandom(100) < 30 if @user.hp <= @user.totalhp / 4 && pbAIRandom(100) < 30
shouldSwitch = true shouldSwitch = true
elsif battler.hp <= battler.totalhp / 2 && pbAIRandom(100) < 80 elsif @user.hp <= @user.totalhp / 2 && pbAIRandom(100) < 80
shouldSwitch = true shouldSwitch = true
end end
end end
# Pokémon is about to faint because of Perish Song # Pokémon is about to faint because of Perish Song
if battler.effects[PBEffects::PerishSong] == 1 if @user.effects[PBEffects::PerishSong] == 1
shouldSwitch = true shouldSwitch = true
end end
if shouldSwitch if shouldSwitch
list = [] list = []
idxPartyStart, idxPartyEnd = @battle.pbTeamIndexRangeFromBattlerIndex(idxBattler) idxPartyStart, idxPartyEnd = @battle.pbTeamIndexRangeFromBattlerIndex(@user.index)
@battle.pbParty(idxBattler).each_with_index do |pkmn, i| @battle.pbParty(@user.index).each_with_index do |pkmn, i|
next if i == idxPartyEnd - 1 # Don't choose to switch in ace next if i == idxPartyEnd - 1 # Don't choose to switch in ace
next if !@battle.pbCanSwitch?(idxBattler, i) next if !@battle.pbCanSwitch?(@user.index, i)
# If perish count is 1, it may be worth it to switch # If perish count is 1, it may be worth it to switch
# even with Spikes, since Perish Song's effect will end # even with Spikes, since Perish Song's effect will end
if battler.effects[PBEffects::PerishSong] != 1 if @user.effects[PBEffects::PerishSong] != 1
# Will contain effects that recommend against switching # Will contain effects that recommend against switching
spikes = battler.pbOwnSide.effects[PBEffects::Spikes] spikes = @user.pbOwnSide.effects[PBEffects::Spikes]
# Don't switch to this if too little HP # Don't switch to this if too little HP
if spikes > 0 if spikes > 0
spikesDmg = [8, 6, 4][spikes - 1] spikesDmg = [8, 6, 4][spikes - 1]
@@ -108,17 +105,17 @@ class Battle::AI
end end
end end
# moveType is the type of the target's last used move # moveType is the type of the target's last used move
if moveType && Effectiveness.ineffective?(pbCalcTypeMod(moveType, battler, battler)) if moveType && Effectiveness.ineffective?(pbCalcTypeMod(moveType, @user, @user))
weight = 65 weight = 65
typeMod = pbCalcTypeModPokemon(pkmn, battler.pbDirectOpposing(true)) typeMod = pbCalcTypeModPokemon(pkmn, @user.pbDirectOpposing(true))
if Effectiveness.super_effective?(typeMod) if Effectiveness.super_effective?(typeMod)
# Greater weight if new Pokemon's type is effective against target # Greater weight if new Pokemon's type is effective against target
weight = 85 weight = 85
end end
list.unshift(i) if pbAIRandom(100) < weight # Put this Pokemon first list.unshift(i) if pbAIRandom(100) < weight # Put this Pokemon first
elsif moveType && Effectiveness.resistant?(pbCalcTypeMod(moveType, battler, battler)) elsif moveType && Effectiveness.resistant?(pbCalcTypeMod(moveType, @user, @user))
weight = 40 weight = 40
typeMod = pbCalcTypeModPokemon(pkmn, battler.pbDirectOpposing(true)) typeMod = pbCalcTypeModPokemon(pkmn, @user.pbDirectOpposing(true))
if Effectiveness.super_effective?(typeMod) if Effectiveness.super_effective?(typeMod)
# Greater weight if new Pokemon's type is effective against target # Greater weight if new Pokemon's type is effective against target
weight = 60 weight = 60
@@ -129,13 +126,13 @@ class Battle::AI
end end
end end
if list.length > 0 if list.length > 0
if batonPass >= 0 && @battle.pbRegisterMove(idxBattler, batonPass, false) if batonPass >= 0 && @battle.pbRegisterMove(@user.index, batonPass, false)
PBDebug.log("[AI] #{battler.pbThis} (#{idxBattler}) will use Baton Pass to avoid Perish Song") PBDebug.log("[AI] #{@user.pbThis} (#{@user.index}) will use Baton Pass to avoid Perish Song")
return true return true
end end
if @battle.pbRegisterSwitch(idxBattler, list[0]) if @battle.pbRegisterSwitch(@user.index, list[0])
PBDebug.log("[AI] #{battler.pbThis} (#{idxBattler}) will switch with " + PBDebug.log("[AI] #{@user.pbThis} (#{@user.index}) will switch with " +
@battle.pbParty(idxBattler)[list[0]].name) @battle.pbParty(@user.index)[list[0]].name)
return true return true
end end
end end
@@ -143,10 +140,10 @@ class Battle::AI
return false return false
end end
#============================================================================= # Choose a replacement Pokémon (called directly from @battle, not part of
# Choose a replacement Pokémon # action choosing).
#=============================================================================
def pbDefaultChooseNewEnemy(idxBattler, party) def pbDefaultChooseNewEnemy(idxBattler, party)
set_up(idxBattler)
enemies = [] enemies = []
idxPartyStart, idxPartyEnd = @battle.pbTeamIndexRangeFromBattlerIndex(idxBattler) idxPartyStart, idxPartyEnd = @battle.pbTeamIndexRangeFromBattlerIndex(idxBattler)
party.each_with_index do |_p, i| party.each_with_index do |_p, i|

View File

@@ -3,25 +3,7 @@ class Battle::AI
# Main move-choosing method (moves with higher scores are more likely to be # Main move-choosing method (moves with higher scores are more likely to be
# chosen) # chosen)
#============================================================================= #=============================================================================
def pbChooseMoves(idxBattler) def pbChooseMove(choices)
user = @battle.battlers[idxBattler]
wildBattler = user.wild?
skill = 0
if !wildBattler
skill = @battle.pbGetOwnerFromBattlerIndex(user.index).skill_level || 0
end
# Get scores and targets for each move
# NOTE: A move is only added to the choices array if it has a non-zero
# score.
choices = []
user.eachMoveWithIndex do |_m, i|
next if !@battle.pbCanChooseMove?(idxBattler, i, false)
if wildBattler
pbRegisterMoveWild(user, i, choices)
else
pbRegisterMoveTrainer(user, i, choices, skill)
end
end
# Figure out useful information about the choices # Figure out useful information about the choices
totalScore = 0 totalScore = 0
maxScore = 0 maxScore = 0
@@ -29,18 +11,9 @@ class Battle::AI
totalScore += c[1] totalScore += c[1]
maxScore = c[1] if maxScore < c[1] maxScore = c[1] if maxScore < c[1]
end end
# Log the available choices
if $INTERNAL
logMsg = "[AI] Move choices for #{user.pbThis(true)} (#{user.index}): "
choices.each_with_index do |c, i|
logMsg += "#{user.moves[c[0]].name}=#{c[1]}"
logMsg += " (target #{c[2]})" if c[2] >= 0
logMsg += ", " if i < choices.length - 1
end
PBDebug.log(logMsg)
end
# Find any preferred moves and just choose from them # Find any preferred moves and just choose from them
if !wildBattler && skill >= PBTrainerAI.highSkill && maxScore > 100 if skill_check(AILevel.high) && maxScore > 100
stDev = pbStdDev(choices) stDev = pbStdDev(choices)
if stDev >= 40 && pbAIRandom(100) < 90 if stDev >= 40 && pbAIRandom(100) < 90
preferredMoves = [] preferredMoves = []
@@ -51,97 +24,141 @@ class Battle::AI
end end
if preferredMoves.length > 0 if preferredMoves.length > 0
m = preferredMoves[pbAIRandom(preferredMoves.length)] m = preferredMoves[pbAIRandom(preferredMoves.length)]
PBDebug.log("[AI] #{user.pbThis} (#{user.index}) prefers #{user.moves[m[0]].name}") PBDebug.log("[AI] #{@user.pbThis} (#{@user.index}) prefers #{@user.moves[m[0]].name}")
@battle.pbRegisterMove(idxBattler, m[0], false) @battle.pbRegisterMove(@user.index, m[0], false)
@battle.pbRegisterTarget(idxBattler, m[2]) if m[2] >= 0 @battle.pbRegisterTarget(@user.index, m[2]) if m[2] >= 0
return return
end end
end end
end end
# Decide whether all choices are bad, and if so, try switching instead # Decide whether all choices are bad, and if so, try switching instead
if !wildBattler && skill >= PBTrainerAI.highSkill if !@wildBattler && skill_check(AILevel.high)
badMoves = false badMoves = false
if ((maxScore <= 20 && user.turnCount > 2) || if (maxScore <= 20 && @user.turnCount > 2) ||
(maxScore <= 40 && user.turnCount > 5)) && pbAIRandom(100) < 80 (maxScore <= 40 && @user.turnCount > 5)
badMoves = true badMoves = true if pbAIRandom(100) < 80
end end
if !badMoves && totalScore < 100 && user.turnCount > 1 if !badMoves && totalScore < 100 && @user.turnCount > 1
badMoves = true badMoves = true
choices.each do |c| choices.each do |c|
next if !user.moves[c[0]].damagingMove? next if !@user.moves[c[0]].damagingMove?
badMoves = false badMoves = false
break break
end end
badMoves = false if badMoves && pbAIRandom(100) < 10 badMoves = false if badMoves && pbAIRandom(100) < 10
end end
if badMoves && pbEnemyShouldWithdrawEx?(idxBattler, true) if badMoves && pbEnemyShouldWithdrawEx?(true)
if $INTERNAL if $INTERNAL
PBDebug.log("[AI] #{user.pbThis} (#{user.index}) will switch due to terrible moves") PBDebug.log("[AI] #{@user.pbThis} (#{@user.index}) will switch due to terrible moves")
end end
return return
end end
end end
# If there are no calculated choices, pick one at random # If there are no calculated choices, pick one at random
if choices.length == 0 if choices.length == 0
PBDebug.log("[AI] #{user.pbThis} (#{user.index}) doesn't want to use any moves; picking one at random") PBDebug.log("[AI] #{@user.pbThis} (#{@user.index}) doesn't want to use any moves; picking one at random")
user.eachMoveWithIndex do |_m, i| @user.eachMoveWithIndex do |_m, i|
next if !@battle.pbCanChooseMove?(idxBattler, i, false) next if !@battle.pbCanChooseMove?(@user.index, i, false)
choices.push([i, 100, -1]) # Move index, score, target choices.push([i, 100, -1]) # Move index, score, target
end end
if choices.length == 0 # No moves are physically possible to use; use Struggle if choices.length == 0 # No moves are physically possible to use; use Struggle
@battle.pbAutoChooseMove(user.index) @battle.pbAutoChooseMove(@user.index)
end end
end end
# Randomly choose a move from the choices and register it # Randomly choose a move from the choices and register it
randNum = pbAIRandom(totalScore) randNum = pbAIRandom(totalScore)
choices.each do |c| choices.each do |c|
randNum -= c[1] randNum -= c[1]
next if randNum >= 0 next if randNum >= 0
@battle.pbRegisterMove(idxBattler, c[0], false) @battle.pbRegisterMove(@user.index, c[0], false)
@battle.pbRegisterTarget(idxBattler, c[2]) if c[2] >= 0 @battle.pbRegisterTarget(@user.index, c[2]) if c[2] >= 0
break break
end end
# Log the result # Log the result
if @battle.choices[idxBattler][2] if @battle.choices[@user.index][2]
PBDebug.log("[AI] #{user.pbThis} (#{user.index}) will use #{@battle.choices[idxBattler][2].name}") PBDebug.log("[AI] #{@user.pbThis} (#{@user.index}) will use #{@battle.choices[@user.index][2].name}")
end end
end end
#=============================================================================
# Get scores for the user's moves (done before any action is assessed)
# NOTE: A move is only added to the choices array if it has a non-zero score.
#=============================================================================
def pbGetMoveScores
# Get scores and targets for each move
choices = []
# TODO: Split this into two, the first part being the calculation of all
# predicted damages and the second part being the score calculations
# (which are based on the predicted damages). Note that this requires
# saving each of the scoresAndTargets entries in here rather than in
# def pbRegisterMoveTrainer, and only at the very end are they
# whittled down to one per move which are chosen from. Multi-target
# moves could be fiddly since damages should be calculated for each
# target but they're all related.
@user.eachMoveWithIndex do |_m, i|
next if !@battle.pbCanChooseMove?(@user.index, i, false)
if @wildBattler
pbRegisterMoveWild(i, choices)
else
pbRegisterMoveTrainer(i, choices)
end
end
# Log the available choices
if $INTERNAL
logMsg = "[AI] Move choices for #{@user.pbThis(true)} (#{@user.index}): "
choices.each_with_index do |c, i|
logMsg += "#{@user.moves[c[0]].name}=#{c[1]}"
logMsg += " (target #{c[2]})" if c[2] >= 0
logMsg += ", " if i < choices.length-1
end
PBDebug.log(logMsg)
end
return choices
end
#============================================================================= #=============================================================================
# Get scores for the given move against each possible target # Get scores for the given move against each possible target
#============================================================================= #=============================================================================
# Wild Pokémon choose their moves randomly. # Wild Pokémon choose their moves randomly.
def pbRegisterMoveWild(_user, idxMove, choices) def pbRegisterMoveWild(idxMove, choices)
score = 100
# Doubly prefer one of the user's moves (the choice is random but consistent
# and does not correlate to any other property of the user)
score *= 2 if @user.pokemon.personalID % @user.moves.length == idxMove
choices.push([idxMove, 100, -1]) # Move index, score, target choices.push([idxMove, 100, -1]) # Move index, score, target
end end
# Trainer Pokémon calculate how much they want to use each of their moves. # Trainer Pokémon calculate how much they want to use each of their moves.
def pbRegisterMoveTrainer(user, idxMove, choices, skill) def pbRegisterMoveTrainer(idxMove, choices)
move = user.moves[idxMove] move = @user.moves[idxMove]
target_data = move.pbTarget(user) target_data = move.pbTarget(@user)
# TODO: Alter target_data if user has Protean and move is Curse.
if [:UserAndAllies, :AllAllies, :AllBattlers].include?(target_data.id) || if [:UserAndAllies, :AllAllies, :AllBattlers].include?(target_data.id) ||
target_data.num_targets == 0 target_data.num_targets == 0
# If move has no targets, affects the user, a side or the whole field, or # If move has no targets, affects the user, a side or the whole field, or
# specially affects multiple Pokémon and the AI calculates an overall # specially affects multiple Pokémon and the AI calculates an overall
# score at once instead of per target # score at once instead of per target
score = pbGetMoveScore(move, user, user, skill) score = pbGetMoveScore(move, @user)
choices.push([idxMove, score, -1]) if score > 0 choices.push([idxMove, score, -1]) if score > 0
elsif target_data.num_targets > 1 elsif target_data.num_targets > 1
# If move affects multiple battlers and you don't choose a particular one # If move affects multiple battlers and you don't choose a particular one
totalScore = 0 totalScore = 0
@battle.allBattlers.each do |b| @battle.allBattlers.each do |b|
next if !@battle.pbMoveCanTarget?(user.index, b.index, target_data) next if !@battle.pbMoveCanTarget?(@user.index, b.index, target_data)
score = pbGetMoveScore(move, user, b, skill) score = pbGetMoveScore(move, b)
totalScore += ((user.opposes?(b)) ? score : -score) totalScore += ((@user.opposes?(b)) ? score : -score)
end end
choices.push([idxMove, totalScore, -1]) if totalScore > 0 choices.push([idxMove, totalScore, -1]) if totalScore > 0
else else
# If move affects one battler and you have to choose which one # If move affects one battler and you have to choose which one
scoresAndTargets = [] scoresAndTargets = []
@battle.allBattlers.each do |b| @battle.allBattlers.each do |b|
next if !@battle.pbMoveCanTarget?(user.index, b.index, target_data) next if !@battle.pbMoveCanTarget?(@user.index, b.index, target_data)
next if target_data.targets_foe && !user.opposes?(b) next if target_data.targets_foe && !@user.opposes?(b)
score = pbGetMoveScore(move, user, b, skill) score = pbGetMoveScore(move, b)
scoresAndTargets.push([score, b.index]) if score > 0 scoresAndTargets.push([score, b.index]) if score > 0
end end
if scoresAndTargets.length > 0 if scoresAndTargets.length > 0
@@ -152,34 +169,134 @@ class Battle::AI
end end
end end
#=============================================================================
# Set some class variables for the move being assessed
#=============================================================================
def set_up_move_check(move, target)
@move = move
@target = target
# TODO: Calculate pbRoughType once here.
# Determine whether user or target is faster, and store that result so it
# doesn't need recalculating
if @target
user_speed = pbRoughStat(@user, :SPEED)
target_speed = pbRoughStat(@target, :SPEED)
@user_faster = (user_speed > target_speed) ^ (@battle.field.effects[PBEffects::TrickRoom] > 0)
else
@user_faster = false # Won't be used if there is no target
end
end
#============================================================================= #=============================================================================
# Get a score for the given move being used against the given target # Get a score for the given move being used against the given target
#============================================================================= #=============================================================================
def pbGetMoveScore(move, user, target, skill = 100) def pbGetMoveScore(move, target = nil)
skill = PBTrainerAI.minimumSkill if skill < PBTrainerAI.minimumSkill set_up_move_check(move, target)
score = 100
score = pbGetMoveScoreFunctionCode(score, move, user, target, skill) # Get the base score for the move
if @move.damagingMove?
# Is also the predicted damage amount as a percentage of target's current HP
score = pbGetDamagingMoveBaseScore
else # Status moves
# Depends on the move's effect
score = pbGetStatusMoveBaseScore
end
# Modify the score according to the move's effect
score = Battle::AI::Handlers.apply_move_effect_score(move.function,
score, move, @user, target, @skill, self, @battle)
# A score of 0 here means it absolutely should not be used # A score of 0 here means it absolutely should not be used
return 0 if score <= 0 return 0 if score <= 0
if skill >= PBTrainerAI.mediumSkill
# TODO: High priority checks:
# => Prefer move if it will KO the target (moreso if user is slower than target)
# => 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
# => Discard move if user is slower than the target and target is semi-
# invulnerable (and move won't hit it)
# => Check memory for whether target has previously used Quick Guard, and
# don't prefer move if so
# TODO: Low priority checks:
# => Don't prefer move if user is faster than the target
# => Prefer move if user is faster than the target and target is semi-
# invulnerable
# Don't prefer a dancing move if the target has the Dancer ability
# TODO: Check all battlers, not just the target.
if skill_check(AILevel.high) && @move.danceMove? && @target.hasActiveAbility?(:DANCER)
score /= 2
end
# 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.).
# TODO: Discard move if it can be redirected by a non-target's ability
# (Lightning Rod/Storm Drain). Include checking for a previous use of
# Ion Deluge and this move being Normal-type.
# => If non-target is a user's ally, don't prefer move (rather than discarding
# it)
# TODO: Discard move if it's sound-based and user has been Throat Chopped.
# Don't prefer move if user hasn't been Throat Chopped but target has
# previously used Throat Chop. The first part of this would probably
# go elsewhere (damage calc?).
# 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).
# TODO: Don't prefer damaging moves if target is Destiny Bonding.
# => Also don't prefer damaging moves if user is slower than the target, move
# is likely to be lethal, and target has previously used Destiny Bond
# TODO: Don't prefer a move that is stopped by Wide Guard if target has
# previously used Wide Guard.
# TODO: Don't prefer Fire-type moves if target has previously used Powder.
# 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: Don't prefer a status move if user has a damaging move that will KO
# the target.
# => If target has previously used a move that will hurt the user by 30% of
# its current HP or more, moreso don't prefer a status move.
if skill_check(AILevel.medium)
# Prefer damaging moves if AI has no more Pokémon or AI is less clever # Prefer damaging moves if AI has no more Pokémon or AI is less clever
if @battle.pbAbleNonActiveCount(user.idxOwnSide) == 0 && if @battle.pbAbleNonActiveCount(@user.idxOwnSide) == 0 &&
!(skill >= PBTrainerAI.highSkill && @battle.pbAbleNonActiveCount(target.idxOwnSide) > 0) !(skill_check(AILevel.high) && @battle.pbAbleNonActiveCount(@target.idxOwnSide) > 0)
if move.statusMove? if @move.statusMove?
score /= 1.5 score *= 0.9
elsif target.hp <= target.totalhp / 2 elsif @target.hp <= @target.totalhp / 2
score *= 1.5 score *= 1.1
end end
end end
# Don't prefer attacking the target if they'd be semi-invulnerable # Don't prefer attacking the target if they'd be semi-invulnerable
if skill >= PBTrainerAI.highSkill && move.accuracy > 0 && if skill_check(AILevel.high) && @move.accuracy > 0 && @user_faster &&
(target.semiInvulnerable? || target.effects[PBEffects::SkyDrop] >= 0) (@target.semiInvulnerable? || @target.effects[PBEffects::SkyDrop] >= 0)
miss = true miss = true
miss = false if user.hasActiveAbility?(:NOGUARD) || target.hasActiveAbility?(:NOGUARD) miss = false if @user.hasActiveAbility?(:NOGUARD)
if miss && pbRoughStat(user, :SPEED, skill) > pbRoughStat(target, :SPEED, skill) miss = false if skill_check(AILevel.best) && @target.hasActiveAbility?(:NOGUARD)
if skill_check(AILevel.best) && miss
# Knows what can get past semi-invulnerability # Knows what can get past semi-invulnerability
if target.effects[PBEffects::SkyDrop] >= 0 || if @target.effects[PBEffects::SkyDrop] >= 0 ||
target.inTwoTurnAttack?("TwoTurnAttackInvulnerableInSky", @target.inTwoTurnAttack?("TwoTurnAttackInvulnerableInSky",
"TwoTurnAttackInvulnerableInSkyParalyzeTarget", "TwoTurnAttackInvulnerableInSkyParalyzeTarget",
"TwoTurnAttackInvulnerableInSkyTargetCannotAct") "TwoTurnAttackInvulnerableInSkyTargetCannotAct")
miss = false if move.hitsFlyingTargets? miss = false if move.hitsFlyingTargets?
@@ -189,107 +306,388 @@ class Battle::AI
miss = false if move.hitsDivingTargets? miss = false if move.hitsDivingTargets?
end end
end end
score -= 80 if miss score = 0 if miss
end end
# Pick a good move for the Choice items # Pick a good move for the Choice items
if user.hasActiveItem?([:CHOICEBAND, :CHOICESPECS, :CHOICESCARF]) || if @user.hasActiveItem?([:CHOICEBAND, :CHOICESPECS, :CHOICESCARF]) ||
user.hasActiveAbility?(:GORILLATACTICS) @user.hasActiveAbility?(:GORILLATACTICS)
if move.baseDamage >= 60 # Really don't prefer status moves (except Trick)
score += 60 score *= 0.1 if @move.statusMove? && @move.function != "UserTargetSwapItems"
elsif move.damagingMove? # Don't prefer moves of certain types
score += 30 move_type = pbRoughType(@move)
elsif move.function == "UserTargetSwapItems" # Most unpreferred types are 0x effective against another type, except
score += 70 # Trick # Fire/Water/Grass
else # TODO: Actually check through the types for 0x instead of hardcoding
score -= 60 # them.
end # TODO: Reborn separately doesn't prefer Fire/Water/Grass/Electric, also
# with a 0.95x score, meaning Electric can be 0.95x twice. Why are
# these four types not preferred? Maybe because they're all not
# very effective against Dragon.
unpreferred_types = [:NORMAL, :FIGHTING, :POISON, :GROUND, :GHOST,
:FIRE, :WATER, :GRASS, :ELECTRIC, :PSYCHIC, :DRAGON]
score *= 0.95 if unpreferred_types.include?(move_type)
# Don't prefer moves with lower accuracy
score *= @move.accuracy / 100.0 if @move.accuracy > 0
# Don't prefer moves with low PP
score *= 0.9 if @move.pp < 6
end end
# If user is asleep, prefer moves that are usable while asleep
if user.status == :SLEEP && !move.usableWhenAsleep? # If user is asleep, don't prefer moves that can't be used while asleep
user.eachMove do |m| if skill_check(AILevel.medium) && @user.asleep? && @user.statusCount > 1 &&
next unless m.usableWhenAsleep? !@move.usableWhenAsleep?
score -= 60 score *= 0.2
break
end
end end
# If user is frozen, prefer a move that can thaw the user # If user is frozen, prefer a move that can thaw the user
if user.status == :FROZEN if skill_check(AILevel.medium) && @user.status == :FROZEN
if move.thawsUser? if @move.thawsUser?
score += 40 score += 30
else else
user.eachMove do |m| @user.eachMove do |m|
next unless m.thawsUser? next unless m.thawsUser?
score -= 60 score = 0 # Discard this move if user knows another move that thaws
break break
end end
end end
end end
# If target is frozen, don't prefer moves that could thaw them # If target is frozen, don't prefer moves that could thaw them
if target.status == :FROZEN if @target.status == :FROZEN
user.eachMove do |m| if pbRoughType(@move) == :FIRE || (Settings::MECHANICS_GENERATION >= 6 && @move.thawsUser?)
next if m.thawsUser? score *= 0.1
score -= 60
break
end end
end end
end end
# Don't prefer moves that are ineffective because of abilities or effects
return 0 if pbCheckMoveImmunity(score, move, user, target, skill) # Don't prefer hitting a wild shiny Pokémon
# Adjust score based on how much damage it can deal if @battle.wildBattle? && @target.opposes? && @target.shiny?
if move.damagingMove? score *= 0.15
score = pbGetMoveScoreDamage(score, move, user, target, skill)
else # Status moves
# Don't prefer attacks which don't deal damage
score -= 10
# Account for accuracy of move
accuracy = pbRoughAccuracy(move, user, target, skill)
score *= accuracy / 100.0
score = 0 if score <= 10 && skill >= PBTrainerAI.highSkill
end end
# TODO: Discard a move that can be Magic Coated if either opponent has Magic
# Bounce.
# Account for accuracy of move
accuracy = pbRoughAccuracy(@move, @target)
score *= accuracy / 100.0
# Prefer flinching external effects (note that move effects which cause
# flinching are dealt with in the function code part of score calculation)
if skill_check(AILevel.medium)
if !@target.hasActiveAbility?([:INNERFOCUS, :SHIELDDUST]) &&
@target.effects[PBEffects::Substitute] == 0
if @move.flinchingMove? ||
(@move.damagingMove? &&
(@user.hasActiveItem?([:KINGSROCK, :RAZORFANG]) ||
@user.hasActiveAbility?(:STENCH)))
score *= 1.3
end
end
end
# # Adjust score based on how much damage it can deal
# if move.damagingMove?
# score = pbGetMoveScoreDamage(score, move, @user, @target, @skill)
# else # Status moves
# # Don't prefer attacks which don't deal damage
# score -= 10
# # Account for accuracy of move
# accuracy = pbRoughAccuracy(move, target)
# score *= accuracy / 100.0
# score = 0 if score <= 10 && skill_check(AILevel.high)
# end
score = score.to_i score = score.to_i
score = 0 if score < 0 score = 0 if score < 0
return score return score
end end
#============================================================================= #=============================================================================
# Add to a move's score based on how much damage it will deal (as a percentage # Calculate how much damage a move is likely to do to a given target (as a
# of the target's current HP) # percentage of the target's current HP)
#============================================================================= #=============================================================================
def pbGetMoveScoreDamage(score, move, user, target, skill) def pbGetDamagingMoveBaseScore
return 0 if score <= 0 # Don't prefer moves that are ineffective because of abilities or effects
return 0 if pbCheckMoveImmunity(@move, @target)
# Calculate how much damage the move will do (roughly) # Calculate how much damage the move will do (roughly)
baseDmg = pbMoveBaseDamage(move, user, target, skill) base_damage = pbMoveBaseDamage(@move, @target)
realDamage = pbRoughDamage(move, user, target, skill, baseDmg) calc_damage = pbRoughDamage(@move, @target, base_damage)
# Account for accuracy of move
accuracy = pbRoughAccuracy(move, user, target, skill) # TODO: Maybe move this check elsewhere? Note that Reborn's base score does
realDamage *= accuracy / 100.0 # not include this halving, but the predicted damage does.
# Two-turn attacks waste 2 turns to deal one lot of damage # Two-turn attacks waste 2 turns to deal one lot of damage
if move.chargingTurnMove? || move.function == "AttackAndSkipNextTurn" # Hyper Beam calc_damage /= 2 if @move.chargingTurnMove?
realDamage *= 2 / 3 # Not halved because semi-invulnerable during use or hits first turn
end # TODO: Maybe move this check elsewhere?
# Prefer flinching external effects (note that move effects which cause # Increased critical hit rate
# flinching are dealt with in the function code part of score calculation) if skill_check(AILevel.medium)
if skill >= PBTrainerAI.mediumSkill && !move.flinchingMove? && crit_stage = pbRoughCriticalHitStage(@move, @target)
!target.hasActiveAbility?(:INNERFOCUS) && if crit_stage >= 0
!target.hasActiveAbility?(:SHIELDDUST) && crit_fraction = (crit_stage > 50) ? 1 : Battle::Move::CRITICAL_HIT_RATIOS[crit_stage]
target.effects[PBEffects::Substitute] == 0 crit_mult = (Settings::NEW_CRITICAL_HIT_RATE_MECHANICS) ? 0.5 : 1
canFlinch = false calc_damage *= (1 + crit_mult / crit_fraction)
if user.hasActiveItem?([:KINGSROCK, :RAZORFANG]) ||
user.hasActiveAbility?(:STENCH)
canFlinch = true
end end
realDamage *= 1.3 if canFlinch
end end
# Convert damage to percentage of target's remaining HP # Convert damage to percentage of target's remaining HP
damagePercentage = realDamage * 100.0 / target.hp damage_percentage = calc_damage * 100.0 / @target.hp
# Don't prefer weak attacks # Don't prefer weak attacks
# damagePercentage /= 2 if damagePercentage<20 # damage_percentage /= 2 if damage_percentage < 20
# Prefer damaging attack if level difference is significantly high # Prefer damaging attack if level difference is significantly high
damagePercentage *= 1.2 if user.level - 10 > target.level # damage_percentage *= 1.2 if @user.level - 10 > @target.level
# Adjust score # Adjust score
damagePercentage = 120 if damagePercentage > 120 # Treat all lethal moves the same damage_percentage = 110 if damage_percentage > 110 # Treat all lethal moves the same
damagePercentage += 40 if damagePercentage > 100 # Prefer moves likely to be lethal damage_percentage += 40 if damage_percentage > 100 # Prefer moves likely to be lethal
score += damagePercentage.to_i
return score return damage_percentage.to_i
end
def pbGetStatusMoveBaseScore
# TODO: Call pbCheckMoveImmunity here too, not just for damaging moves
# (only if this status move will be affected).
# TODO: Make sure all status moves are accounted for.
# TODO: Duplicates in Reborn's AI:
# "SleepTarget" Grass Whistle (15), Hypnosis (15), Sing (15),
# Lovely Kiss (20), Sleep Powder (20), Spore (60)
# "PoisonTarget" - Poison Powder (15), Poison Gas (20)
# "ParalyzeTarget" - Stun Spore (25), Glare (30)
# "ConfuseTarget" - Teeter Dance (5), Supersonic (10),
# Sweet Kiss (20), Confuse Ray (25)
# "RaiseUserAttack1" - Howl (10), Sharpen (10), Medicate (15)
# "RaiseUserSpeed2" - Agility (15), Rock Polish (25)
# "LowerTargetAttack1" - Growl (10), Baby-Doll Eyes (15)
# "LowerTargetAccuracy1" - Sand Attack (5), Flash (10), Kinesis (10), Smokescreen (10)
# "LowerTargetAttack2" - Charm (10), Feather Dance (15)
# "LowerTargetSpeed2" - String Shot (10), Cotton Spore (15), Scary Face (15)
# "LowerTargetSpDef2" - Metal Sound (10), Fake Tears (15)
case @move.function
when "ConfuseTarget",
"LowerTargetAccuracy1",
"LowerTargetEvasion1RemoveSideEffects",
"UserTargetSwapAtkSpAtkStages",
"UserTargetSwapDefSpDefStages",
"UserSwapBaseAtkDef",
"UserTargetAverageBaseAtkSpAtk",
"UserTargetAverageBaseDefSpDef",
"SetUserTypesToUserMoveType",
"SetTargetTypesToWater",
"SetUserTypesToTargetTypes",
"SetTargetAbilityToUserAbility",
"UserTargetSwapAbilities",
"PowerUpAllyMove",
"StartWeakenElectricMoves",
"StartWeakenFireMoves",
"EnsureNextMoveAlwaysHits",
"StartNegateTargetEvasionStatStageAndGhostImmunity",
"StartNegateTargetEvasionStatStageAndDarkImmunity",
"ProtectUserSideFromPriorityMoves",
"ProtectUserSideFromMultiTargetDamagingMoves",
"BounceBackProblemCausingStatusMoves",
"StealAndUseBeneficialStatusMove",
"DisableTargetMovesKnownByUser",
"DisableTargetHealingMoves",
"SetAttackerMovePPTo0IfUserFaints",
"UserEnduresFaintingThisTurn",
"RestoreUserConsumedItem",
"StartNegateHeldItems",
"StartDamageTargetEachTurnIfTargetAsleep",
"HealUserDependingOnUserStockpile",
"StartGravity",
"StartUserAirborne",
"UserSwapsPositionsWithAlly",
"StartSwapAllBattlersBaseDefensiveStats",
"RaiseTargetSpDef1",
"RaiseGroundedGrassBattlersAtkSpAtk1",
"RaiseGrassBattlersDef1",
"AddGrassTypeToTarget",
"TrapAllBattlersInBattleForOneTurn",
"EnsureNextCriticalHit",
"UserTargetSwapBaseSpeed",
"RedirectAllMovesToTarget",
"TargetUsesItsLastUsedMoveAgain"
return 5
when "RaiseUserAttack1",
"RaiseUserDefense1",
"RaiseUserDefense1CurlUpUser",
"RaiseUserCriticalHitRate2",
"RaiseUserAtkSpAtk1",
"RaiseUserAtkSpAtk1Or2InSun",
"RaiseUserAtkAcc1",
"RaiseTargetRandomStat2",
"LowerTargetAttack1",
"LowerTargetDefense1",
"LowerTargetAccuracy1",
"LowerTargetAttack2",
"LowerTargetSpeed2",
"LowerTargetSpDef2",
"ResetAllBattlersStatStages",
"UserCopyTargetStatStages",
"SetUserTypesBasedOnEnvironment",
"DisableTargetUsingSameMoveConsecutively",
"StartTargetCannotUseItem",
"LowerTargetAttack1BypassSubstitute",
"LowerTargetAtkSpAtk1",
"LowerTargetSpAtk1",
"TargetNextFireMoveDamagesTarget"
return 10
when "SleepTarget",
"SleepTargetIfUserDarkrai",
"SleepTargetChangeUserMeloettaForm",
"PoisonTarget",
"CureUserBurnPoisonParalysis",
"RaiseUserAttack1",
"RaiseUserSpDef1PowerUpElectricMove",
"RaiseUserEvasion1",
"RaiseUserSpeed2",
"LowerTargetAttack1",
"LowerTargetAtkDef1",
"LowerTargetAttack2",
"LowerTargetDefense2",
"LowerTargetSpeed2",
"LowerTargetSpAtk2IfCanAttract",
"LowerTargetSpDef2",
"ReplaceMoveThisBattleWithTargetLastMoveUsed",
"ReplaceMoveWithTargetLastMoveUsed",
"SetUserAbilityToTargetAbility",
"UseMoveTargetIsAboutToUse",
"UseRandomMoveFromUserParty",
"StartHealUserEachTurnTrapUserInBattle",
"HealTargetHalfOfTotalHP",
"UserFaintsHealAndCureReplacement",
"UserFaintsHealAndCureReplacementRestorePP",
"StartSunWeather",
"StartRainWeather",
"StartSandstormWeather",
"StartHailWeather",
"RaisePlusMinusUserAndAlliesDefSpDef1",
"LowerTargetSpAtk2",
"LowerPoisonedTargetAtkSpAtkSpd1",
"AddGhostTypeToTarget",
"LowerTargetAtkSpAtk1SwitchOutUser",
"RaisePlusMinusUserAndAlliesAtkSpAtk1",
"HealTargetDependingOnGrassyTerrain"
return 15
when "SleepTarget",
"SleepTargetChangeUserMeloettaForm",
"SleepTargetNextTurn",
"PoisonTarget",
"ConfuseTarget",
"RaiseTargetSpAtk1ConfuseTarget",
"RaiseTargetAttack2ConfuseTarget",
"UserTargetSwapStatStages",
"StartUserSideImmunityToStatStageLowering",
"SetUserTypesToResistLastAttack",
"SetTargetAbilityToSimple",
"SetTargetAbilityToInsomnia",
"NegateTargetAbility",
"TransformUserIntoTarget",
"UseLastMoveUsedByTarget",
"UseLastMoveUsed",
"UseRandomMove",
"HealUserFullyAndFallAsleep",
"StartHealUserEachTurn",
"StartPerishCountsForAllBattlers",
"SwitchOutTargetStatusMove",
"TrapTargetInBattle",
"TargetMovesBecomeElectric",
"NormalMovesBecomeElectric",
"PoisonTargetLowerTargetSpeed1"
return 20
when "BadPoisonTarget",
"ParalyzeTarget",
"BurnTarget",
"ConfuseTarget",
"AttractTarget",
"GiveUserStatusToTarget",
"RaiseUserDefSpDef1",
"RaiseUserDefense2",
"RaiseUserSpeed2",
"RaiseUserSpeed2LowerUserWeight",
"RaiseUserSpDef2",
"RaiseUserEvasion2MinimizeUser",
"RaiseUserDefense3",
"MaxUserAttackLoseHalfOfTotalHP",
"UserTargetAverageHP",
"ProtectUser",
"DisableTargetLastMoveUsed",
"DisableTargetStatusMoves",
"HealUserHalfOfTotalHP",
"HealUserHalfOfTotalHPLoseFlyingTypeThisTurn",
"HealUserPositionNextTurn",
"HealUserDependingOnWeather",
"StartLeechSeedTarget",
"AttackerFaintsIfUserFaints",
"UserTargetSwapItems",
"UserMakeSubstitute",
"UserAddStockpileRaiseDefSpDef1",
"RedirectAllMovesToUser",
"InvertTargetStatStages",
"HealUserByTargetAttackLowerTargetAttack1",
"HealUserDependingOnSandstorm"
return 25
when "ParalyzeTarget",
"ParalyzeTargetIfNotTypeImmune",
"RaiseUserAtkDef1",
"RaiseUserAtkDefAcc1",
"RaiseUserSpAtkSpDef1",
"UseMoveDependingOnEnvironment",
"UseRandomUserMoveIfAsleep",
"DisableTargetUsingDifferentMove",
"SwitchOutUserPassOnEffects",
"AddSpikesToFoeSide",
"AddToxicSpikesToFoeSide",
"AddStealthRocksToFoeSide",
"CurseTargetOrLowerUserSpd1RaiseUserAtkDef1",
"StartSlowerBattlersActFirst",
"ProtectUserFromTargetingMovesSpikyShield",
"StartElectricTerrain",
"StartGrassyTerrain",
"StartMistyTerrain",
"StartPsychicTerrain",
"CureTargetStatusHealUserHalfOfTotalHP"
return 30
when "CureUserPartyStatus",
"RaiseUserAttack2",
"RaiseUserSpAtk2",
"RaiseUserSpAtk3",
"StartUserSideDoubleSpeed",
"StartWeakenPhysicalDamageAgainstUserSide",
"StartWeakenSpecialDamageAgainstUserSide",
"ProtectUserSideFromDamagingMovesIfUserFirstTurn",
"ProtectUserFromDamagingMovesKingsShield",
"ProtectUserBanefulBunker"
return 35
when "RaiseUserAtkSpd1",
"RaiseUserSpAtkSpDefSpd1",
"LowerUserDefSpDef1RaiseUserAtkSpAtkSpd2",
"RaiseUserAtk1Spd2",
"TwoTurnAttackRaiseUserSpAtkSpDefSpd2"
return 40
when "SleepTarget",
"SleepTargetChangeUserMeloettaForm",
"AddStickyWebToFoeSide",
"StartWeakenDamageAgainstUserSideIfHail"
return 60
end
# "DoesNothingUnusableInGravity",
# "StartUserSideImmunityToInflictedStatus",
# "LowerTargetEvasion1",
# "LowerTargetEvasion2",
# "StartPreventCriticalHitsAgainstUserSide",
# "UserFaintsLowerTargetAtkSpAtk2",
# "FleeFromBattle",
# "SwitchOutUserStatusMove"
# "TargetTakesUserItem",
# "LowerPPOfTargetLastMoveBy4",
# "StartTargetAirborneAndAlwaysHitByMoves",
# "TargetActsNext",
# "TargetActsLast",
# "ProtectUserSideFromStatusMoves"
return 0
end end
end end

File diff suppressed because it is too large Load Diff

View File

@@ -1,575 +0,0 @@
class Battle::AI
#=============================================================================
# Get a score for the given move based on its effect
#=============================================================================
alias aiEffectScorePart1_pbGetMoveScoreFunctionCode pbGetMoveScoreFunctionCode
def pbGetMoveScoreFunctionCode(score, move, user, target, skill = 100)
case move.function
#---------------------------------------------------------------------------
when "SleepTarget", "SleepTargetIfUserDarkrai", "SleepTargetChangeUserMeloettaForm"
if target.pbCanSleep?(user, false)
score += 30
if skill >= PBTrainerAI.mediumSkill
score -= 30 if target.effects[PBEffects::Yawn] > 0
end
if skill >= PBTrainerAI.highSkill
score -= 30 if target.hasActiveAbility?(:MARVELSCALE)
end
if skill >= PBTrainerAI.bestSkill
if target.pbHasMoveFunction?("FlinchTargetFailsIfUserNotAsleep",
"UseRandomUserMoveIfAsleep") # Snore, Sleep Talk
score -= 50
end
end
elsif skill >= PBTrainerAI.mediumSkill
score -= 90 if move.statusMove?
end
#---------------------------------------------------------------------------
when "SleepTargetNextTurn"
if target.effects[PBEffects::Yawn] > 0 || !target.pbCanSleep?(user, false)
score -= 90 if skill >= PBTrainerAI.mediumSkill
else
score += 30
if skill >= PBTrainerAI.highSkill
score -= 30 if target.hasActiveAbility?(:MARVELSCALE)
end
if skill >= PBTrainerAI.bestSkill
if target.pbHasMoveFunction?("FlinchTargetFailsIfUserNotAsleep",
"UseRandomUserMoveIfAsleep") # Snore, Sleep Talk
score -= 50
end
end
end
#---------------------------------------------------------------------------
when "PoisonTarget"
if target.pbCanPoison?(user, false)
score += 30
if skill >= PBTrainerAI.mediumSkill
score += 30 if target.hp <= target.totalhp / 4
score += 50 if target.hp <= target.totalhp / 8
score -= 40 if target.effects[PBEffects::Yawn] > 0
end
if skill >= PBTrainerAI.highSkill
score += 10 if pbRoughStat(target, :DEFENSE, skill) > 100
score += 10 if pbRoughStat(target, :SPECIAL_DEFENSE, skill) > 100
score -= 40 if target.hasActiveAbility?([:GUTS, :MARVELSCALE, :TOXICBOOST])
end
elsif skill >= PBTrainerAI.mediumSkill
score -= 90 if move.statusMove?
end
#---------------------------------------------------------------------------
when "PoisonTargetLowerTargetSpeed1"
if !target.pbCanPoison?(user, false) && !target.pbCanLowerStatStage?(:SPEED, user)
score -= 90
else
if target.pbCanPoison?(user, false)
score += 30
if skill >= PBTrainerAI.mediumSkill
score += 30 if target.hp <= target.totalhp / 4
score += 50 if target.hp <= target.totalhp / 8
score -= 40 if target.effects[PBEffects::Yawn] > 0
end
if skill >= PBTrainerAI.highSkill
score += 10 if pbRoughStat(target, :DEFENSE, skill) > 100
score += 10 if pbRoughStat(target, :SPECIAL_DEFENSE, skill) > 100
score -= 40 if target.hasActiveAbility?([:GUTS, :MARVELSCALE, :TOXICBOOST])
end
end
if target.pbCanLowerStatStage?(:SPEED, user)
score += target.stages[:SPEED] * 10
if skill >= PBTrainerAI.highSkill
aspeed = pbRoughStat(user, :SPEED, skill)
ospeed = pbRoughStat(target, :SPEED, skill)
score += 30 if aspeed < ospeed && aspeed * 2 > ospeed
end
end
end
#---------------------------------------------------------------------------
when "BadPoisonTarget"
if target.pbCanPoison?(user, false)
score += 30
if skill >= PBTrainerAI.mediumSkill
score += 30 if target.hp <= target.totalhp / 4
score += 50 if target.hp <= target.totalhp / 8
score -= 40 if target.effects[PBEffects::Yawn] > 0
end
if skill >= PBTrainerAI.highSkill
score += 10 if pbRoughStat(target, :DEFENSE, skill) > 100
score += 10 if pbRoughStat(target, :SPECIAL_DEFENSE, skill) > 100
score -= 40 if target.hasActiveAbility?([:GUTS, :MARVELSCALE, :TOXICBOOST])
end
elsif skill >= PBTrainerAI.mediumSkill
score -= 90 if move.statusMove?
end
#---------------------------------------------------------------------------
when "ParalyzeTarget", "ParalyzeTargetIfNotTypeImmune",
"ParalyzeTargetAlwaysHitsInRainHitsTargetInSky", "ParalyzeFlinchTarget"
if target.pbCanParalyze?(user, false) &&
!(skill >= PBTrainerAI.mediumSkill &&
move.id == :THUNDERWAVE &&
Effectiveness.ineffective?(pbCalcTypeMod(move.type, user, target)))
score += 30
if skill >= PBTrainerAI.mediumSkill
aspeed = pbRoughStat(user, :SPEED, skill)
ospeed = pbRoughStat(target, :SPEED, skill)
if aspeed < ospeed
score += 30
elsif aspeed > ospeed
score -= 40
end
end
if skill >= PBTrainerAI.highSkill
score -= 40 if target.hasActiveAbility?([:GUTS, :MARVELSCALE, :QUICKFEET])
end
elsif skill >= PBTrainerAI.mediumSkill
score -= 90 if move.statusMove?
end
#---------------------------------------------------------------------------
when "BurnTarget"
if target.pbCanBurn?(user, false)
score += 30
if skill >= PBTrainerAI.highSkill
score -= 40 if target.hasActiveAbility?([:GUTS, :MARVELSCALE, :QUICKFEET, :FLAREBOOST])
end
elsif skill >= PBTrainerAI.mediumSkill
score -= 90 if move.statusMove?
end
#---------------------------------------------------------------------------
when "BurnTargetIfTargetStatsRaisedThisTurn"
if target.pbCanBurn?(user, false)
score += 40
if skill >= PBTrainerAI.highSkill
score -= 40 if target.hasActiveAbility?([:GUTS, :MARVELSCALE, :QUICKFEET, :FLAREBOOST])
end
else
score -= 90
end
#---------------------------------------------------------------------------
when "BurnFlinchTarget"
if target.pbCanBurn?(user, false)
score += 30
if skill >= PBTrainerAI.highSkill
score -= 40 if target.hasActiveAbility?([:GUTS, :MARVELSCALE, :QUICKFEET, :FLAREBOOST])
end
elsif skill >= PBTrainerAI.mediumSkill
score -= 90 if move.statusMove?
end
#---------------------------------------------------------------------------
when "FreezeTarget"
if target.pbCanFreeze?(user, false)
score += 30
if skill >= PBTrainerAI.highSkill
score -= 20 if target.hasActiveAbility?(:MARVELSCALE)
end
elsif skill >= PBTrainerAI.mediumSkill
score -= 90 if move.statusMove?
end
#---------------------------------------------------------------------------
when "FreezeTargetSuperEffectiveAgainstWater"
if target.pbCanFreeze?(user, false)
score += 30
if skill >= PBTrainerAI.highSkill
score -= 20 if target.hasActiveAbility?(:MARVELSCALE)
end
end
#---------------------------------------------------------------------------
when "FreezeTargetAlwaysHitsInHail", "FreezeFlinchTarget"
if target.pbCanFreeze?(user, false)
score += 30
if skill >= PBTrainerAI.highSkill
score -= 20 if target.hasActiveAbility?(:MARVELSCALE)
end
elsif skill >= PBTrainerAI.mediumSkill
score -= 90 if move.statusMove?
end
#---------------------------------------------------------------------------
when "ParalyzeBurnOrFreezeTarget"
score += 30 if target.status == :NONE
#---------------------------------------------------------------------------
when "GiveUserStatusToTarget"
if user.status == :NONE
score -= 90
else
score += 40
end
#---------------------------------------------------------------------------
when "CureUserBurnPoisonParalysis"
case user.status
when :POISON
score += 40
if skill >= PBTrainerAI.mediumSkill
if user.hp < user.totalhp / 8
score += 60
elsif skill >= PBTrainerAI.highSkill &&
user.hp < (user.effects[PBEffects::Toxic] + 1) * user.totalhp / 16
score += 60
end
end
when :BURN, :PARALYSIS
score += 40
else
score -= 90
end
#---------------------------------------------------------------------------
when "CureUserPartyStatus"
statuses = 0
@battle.pbParty(user.index).each do |pkmn|
statuses += 1 if pkmn && pkmn.status != :NONE
end
if statuses == 0
score -= 80
else
score += 20 * statuses
end
#---------------------------------------------------------------------------
when "CureTargetBurn"
if target.opposes?(user)
score -= 40 if target.status == :BURN
elsif target.status == :BURN
score += 40
end
#---------------------------------------------------------------------------
when "StartUserSideImmunityToInflictedStatus"
if user.pbOwnSide.effects[PBEffects::Safeguard] > 0
score -= 80
elsif user.status != :NONE
score -= 40
else
score += 30
end
#---------------------------------------------------------------------------
when "FlinchTarget"
score += 30
if skill >= PBTrainerAI.highSkill
score += 30 if !target.hasActiveAbility?(:INNERFOCUS) &&
target.effects[PBEffects::Substitute] == 0
end
#---------------------------------------------------------------------------
when "FlinchTargetFailsIfUserNotAsleep"
if user.asleep?
score += 100 # Because it can only be used while asleep
if skill >= PBTrainerAI.highSkill
score += 30 if !target.hasActiveAbility?(:INNERFOCUS) &&
target.effects[PBEffects::Substitute] == 0
end
else
score -= 90 # Because it will fail here
score = 0 if skill >= PBTrainerAI.bestSkill
end
#---------------------------------------------------------------------------
when "FlinchTargetFailsIfNotUserFirstTurn"
if user.turnCount == 0
if skill >= PBTrainerAI.highSkill
score += 30 if !target.hasActiveAbility?(:INNERFOCUS) &&
target.effects[PBEffects::Substitute] == 0
end
else
score -= 90 # Because it will fail here
score = 0 if skill >= PBTrainerAI.bestSkill
end
#---------------------------------------------------------------------------
when "FlinchTargetDoublePowerIfTargetInSky"
if skill >= PBTrainerAI.highSkill
score += 30 if !target.hasActiveAbility?(:INNERFOCUS) &&
target.effects[PBEffects::Substitute] == 0
end
#---------------------------------------------------------------------------
when "ConfuseTarget", "ConfuseTargetAlwaysHitsInRainHitsTargetInSky"
if target.pbCanConfuse?(user, false)
score += 30
elsif skill >= PBTrainerAI.mediumSkill
score -= 90 if move.statusMove?
end
#---------------------------------------------------------------------------
when "AttractTarget"
canattract = true
agender = user.gender
ogender = target.gender
if agender == 2 || ogender == 2 || agender == ogender
score -= 90
canattract = false
elsif target.effects[PBEffects::Attract] >= 0
score -= 80
canattract = false
elsif skill >= PBTrainerAI.bestSkill && target.hasActiveAbility?(:OBLIVIOUS)
score -= 80
canattract = false
end
if skill >= PBTrainerAI.highSkill
if canattract && target.hasActiveItem?(:DESTINYKNOT) &&
user.pbCanAttract?(target, false)
score -= 30
end
end
#---------------------------------------------------------------------------
when "SetUserTypesBasedOnEnvironment"
if !user.canChangeType?
score -= 90
elsif skill >= PBTrainerAI.mediumSkill
new_type = nil
case @battle.field.terrain
when :Electric
new_type = :ELECTRIC if GameData::Type.exists?(:ELECTRIC)
when :Grassy
new_type = :GRASS if GameData::Type.exists?(:GRASS)
when :Misty
new_type = :FAIRY if GameData::Type.exists?(:FAIRY)
when :Psychic
new_type = :PSYCHIC if GameData::Type.exists?(:PSYCHIC)
end
if !new_type
envtypes = {
:None => :NORMAL,
:Grass => :GRASS,
:TallGrass => :GRASS,
:MovingWater => :WATER,
:StillWater => :WATER,
:Puddle => :WATER,
:Underwater => :WATER,
:Cave => :ROCK,
:Rock => :GROUND,
:Sand => :GROUND,
:Forest => :BUG,
:ForestGrass => :BUG,
:Snow => :ICE,
:Ice => :ICE,
:Volcano => :FIRE,
:Graveyard => :GHOST,
:Sky => :FLYING,
:Space => :DRAGON,
:UltraSpace => :PSYCHIC
}
new_type = envtypes[@battle.environment]
new_type = nil if !GameData::Type.exists?(new_type)
new_type ||= :NORMAL
end
score -= 90 if !user.pbHasOtherType?(new_type)
end
#---------------------------------------------------------------------------
when "SetUserTypesToResistLastAttack"
if !user.canChangeType?
score -= 90
elsif !target.lastMoveUsed || !target.lastMoveUsedType ||
GameData::Type.get(target.lastMoveUsedType).pseudo_type
score -= 90
else
aType = nil
target.eachMove do |m|
next if m.id != target.lastMoveUsed
aType = m.pbCalcType(user)
break
end
if aType
has_possible_type = false
GameData::Type.each do |t|
next if t.pseudo_type || user.pbHasType?(t.id) ||
!Effectiveness.resistant_type?(target.lastMoveUsedType, t.id)
has_possible_type = true
break
end
score -= 90 if !has_possible_type
else
score -= 90
end
end
#---------------------------------------------------------------------------
when "SetUserTypesToTargetTypes"
if !user.canChangeType? || target.pbTypes(true).length == 0
score -= 90
elsif user.pbTypes == target.pbTypes &&
user.effects[PBEffects::Type3] == target.effects[PBEffects::Type3]
score -= 90
end
#---------------------------------------------------------------------------
when "SetUserTypesToUserMoveType"
if user.canChangeType?
has_possible_type = false
user.eachMoveWithIndex do |m, i|
break if Settings::MECHANICS_GENERATION >= 6 && i > 0
next if GameData::Type.get(m.type).pseudo_type
next if user.pbHasType?(m.type)
has_possible_type = true
break
end
score -= 90 if !has_possible_type
else
score -= 90
end
#---------------------------------------------------------------------------
when "SetTargetTypesToPsychic"
if target.pbHasOtherType?(:PSYCHIC)
score -= 90
elsif !target.canChangeType?
score -= 90
end
#---------------------------------------------------------------------------
when "SetTargetTypesToWater"
if target.effects[PBEffects::Substitute] > 0 || !target.canChangeType?
score -= 90
elsif !target.pbHasOtherType?(:WATER)
score -= 90
end
#---------------------------------------------------------------------------
when "AddGhostTypeToTarget"
score -= 90 if target.pbHasType?(:GHOST)
#---------------------------------------------------------------------------
when "AddGrassTypeToTarget"
score -= 90 if target.pbHasType?(:GRASS)
#---------------------------------------------------------------------------
when "UserLosesFireType"
score -= 90 if !user.pbHasType?(:FIRE)
#---------------------------------------------------------------------------
when "SetTargetAbilityToSimple"
if target.effects[PBEffects::Substitute] > 0
score -= 90
elsif skill >= PBTrainerAI.mediumSkill
if target.unstoppableAbility? || [:TRUANT, :SIMPLE].include?(target.ability)
score -= 90
end
end
#---------------------------------------------------------------------------
when "SetTargetAbilityToInsomnia"
if target.effects[PBEffects::Substitute] > 0
score -= 90
elsif skill >= PBTrainerAI.mediumSkill
if target.unstoppableAbility? || [:TRUANT, :INSOMNIA].include?(target.ability_id)
score -= 90
end
end
#---------------------------------------------------------------------------
when "SetUserAbilityToTargetAbility"
score -= 40 # don't prefer this move
if skill >= PBTrainerAI.mediumSkill
if !target.ability || user.ability == target.ability ||
[:MULTITYPE, :RKSSYSTEM].include?(user.ability_id) ||
[:FLOWERGIFT, :FORECAST, :ILLUSION, :IMPOSTER, :MULTITYPE, :RKSSYSTEM,
:TRACE, :WONDERGUARD, :ZENMODE].include?(target.ability_id)
score -= 90
end
end
if skill >= PBTrainerAI.highSkill
if target.ability == :TRUANT && user.opposes?(target)
score -= 90
elsif target.ability == :SLOWSTART && user.opposes?(target)
score -= 90
end
end
#---------------------------------------------------------------------------
when "SetTargetAbilityToUserAbility"
score -= 40 # don't prefer this move
if target.effects[PBEffects::Substitute] > 0
score -= 90
elsif skill >= PBTrainerAI.mediumSkill
if !user.ability || user.ability == target.ability ||
[:MULTITYPE, :RKSSYSTEM, :TRUANT].include?(target.ability_id) ||
[:FLOWERGIFT, :FORECAST, :ILLUSION, :IMPOSTER, :MULTITYPE, :RKSSYSTEM,
:TRACE, :ZENMODE].include?(user.ability_id)
score -= 90
end
if skill >= PBTrainerAI.highSkill
if user.ability == :TRUANT && user.opposes?(target)
score += 90
elsif user.ability == :SLOWSTART && user.opposes?(target)
score += 90
end
end
end
#---------------------------------------------------------------------------
when "UserTargetSwapAbilities"
score -= 40 # don't prefer this move
if skill >= PBTrainerAI.mediumSkill
if (!user.ability && !target.ability) ||
user.ability == target.ability ||
[:ILLUSION, :MULTITYPE, :RKSSYSTEM, :WONDERGUARD].include?(user.ability_id) ||
[:ILLUSION, :MULTITYPE, :RKSSYSTEM, :WONDERGUARD].include?(target.ability_id)
score -= 90
end
end
if skill >= PBTrainerAI.highSkill
if target.ability == :TRUANT && user.opposes?(target)
score -= 90
elsif target.ability == :SLOWSTART && user.opposes?(target)
score -= 90
end
end
#---------------------------------------------------------------------------
when "NegateTargetAbility"
if target.effects[PBEffects::Substitute] > 0 ||
target.effects[PBEffects::GastroAcid]
score -= 90
elsif skill >= PBTrainerAI.highSkill
score -= 90 if [:MULTITYPE, :RKSSYSTEM, :SLOWSTART, :TRUANT].include?(target.ability_id)
end
#---------------------------------------------------------------------------
when "NegateTargetAbilityIfTargetActed"
if skill >= PBTrainerAI.mediumSkill
userSpeed = pbRoughStat(user, :SPEED, skill)
targetSpeed = pbRoughStat(target, :SPEED, skill)
if userSpeed < targetSpeed
score += 30
end
else
score += 30
end
#---------------------------------------------------------------------------
when "IgnoreTargetAbility"
#---------------------------------------------------------------------------
when "StartUserAirborne"
if user.effects[PBEffects::MagnetRise] > 0 ||
user.effects[PBEffects::Ingrain] ||
user.effects[PBEffects::SmackDown]
score -= 90
end
#---------------------------------------------------------------------------
when "StartTargetAirborneAndAlwaysHitByMoves"
if target.effects[PBEffects::Telekinesis] > 0 ||
target.effects[PBEffects::Ingrain] ||
target.effects[PBEffects::SmackDown]
score -= 90
end
#---------------------------------------------------------------------------
when "HitsTargetInSky"
#---------------------------------------------------------------------------
when "HitsTargetInSkyGroundsTarget"
if skill >= PBTrainerAI.mediumSkill
score += 20 if target.effects[PBEffects::MagnetRise] > 0
score += 20 if target.effects[PBEffects::Telekinesis] > 0
score += 20 if target.inTwoTurnAttack?("TwoTurnAttackInvulnerableInSky",
"TwoTurnAttackInvulnerableInSkyParalyzeTarget")
score += 20 if target.pbHasType?(:FLYING)
score += 20 if target.hasActiveAbility?(:LEVITATE)
score += 20 if target.hasActiveItem?(:AIRBALLOON)
end
#---------------------------------------------------------------------------
when "StartGravity"
if @battle.field.effects[PBEffects::Gravity] > 0
score -= 90
elsif skill >= PBTrainerAI.mediumSkill
score -= 30
score -= 20 if user.effects[PBEffects::SkyDrop] >= 0
score -= 20 if user.effects[PBEffects::MagnetRise] > 0
score -= 20 if user.effects[PBEffects::Telekinesis] > 0
score -= 20 if user.pbHasType?(:FLYING)
score -= 20 if user.hasActiveAbility?(:LEVITATE)
score -= 20 if user.hasActiveItem?(:AIRBALLOON)
score += 20 if target.effects[PBEffects::SkyDrop] >= 0
score += 20 if target.effects[PBEffects::MagnetRise] > 0
score += 20 if target.effects[PBEffects::Telekinesis] > 0
score += 20 if target.inTwoTurnAttack?("TwoTurnAttackInvulnerableInSky",
"TwoTurnAttackInvulnerableInSkyParalyzeTarget",
"TwoTurnAttackInvulnerableInSkyTargetCannotAct")
score += 20 if target.pbHasType?(:FLYING)
score += 20 if target.hasActiveAbility?(:LEVITATE)
score += 20 if target.hasActiveItem?(:AIRBALLOON)
end
#---------------------------------------------------------------------------
when "TransformUserIntoTarget"
score -= 70
#---------------------------------------------------------------------------
else
return aiEffectScorePart1_pbGetMoveScoreFunctionCode(score, move, user, target, skill)
end
return score
end
end

File diff suppressed because it is too large Load Diff

View File

@@ -2,21 +2,21 @@ class Battle::AI
#============================================================================= #=============================================================================
# #
#============================================================================= #=============================================================================
def pbTargetsMultiple?(move, user) def pbTargetsMultiple?(move)
target_data = move.pbTarget(user) target_data = move.pbTarget(@user)
return false if target_data.num_targets <= 1 return false if target_data.num_targets <= 1
num_targets = 0 num_targets = 0
case target_data.id case target_data.id
when :AllAllies when :AllAllies
@battle.allSameSideBattlers(user).each { |b| num_targets += 1 if b.index != user.index } @battle.allSameSideBattlers(@user).each { |b| num_targets += 1 if b.index != @user.index }
when :UserAndAllies when :UserAndAllies
@battle.allSameSideBattlers(user).each { |_b| num_targets += 1 } @battle.allSameSideBattlers(@user).each { |_b| num_targets += 1 }
when :AllNearFoes when :AllNearFoes
@battle.allOtherSideBattlers(user).each { |b| num_targets += 1 if b.near?(user) } @battle.allOtherSideBattlers(@user).each { |b| num_targets += 1 if b.near?(@user) }
when :AllFoes when :AllFoes
@battle.allOtherSideBattlers(user).each { |_b| num_targets += 1 } @battle.allOtherSideBattlers(@user).each { |_b| num_targets += 1 }
when :AllNearOthers when :AllNearOthers
@battle.allBattlers.each { |b| num_targets += 1 if b.near?(user) } @battle.allBattlers.each { |b| num_targets += 1 if b.near?(@user) }
when :AllBattlers when :AllBattlers
@battle.allBattlers.each { |_b| num_targets += 1 } @battle.allBattlers.each { |_b| num_targets += 1 }
end end
@@ -83,11 +83,11 @@ class Battle::AI
# For switching. Determines the effectiveness of a potential switch-in against # For switching. Determines the effectiveness of a potential switch-in against
# an opposing battler. # an opposing battler.
def pbCalcTypeModPokemon(battlerThis, _battlerOther) def pbCalcTypeModPokemon(pkmn, target)
mod1 = Effectiveness.calculate(battlerThis.types[0], target.types[0], target.types[1]) mod1 = Effectiveness.calculate(pkmn.types[0], target.types[0], target.types[1])
mod2 = Effectiveness::NORMAL_EFFECTIVE mod2 = Effectiveness::NORMAL_EFFECTIVE
if battlerThis.types.length > 1 if pkmn.types.length > 1
mod2 = Effectiveness.calculate(battlerThis.types[1], target.types[0], target.types[1]) mod2 = Effectiveness.calculate(pkmn.types[1], target.types[0], target.types[1])
mod2 = mod2.to_f / Effectivenesss::NORMAL_EFFECTIVE mod2 = mod2.to_f / Effectivenesss::NORMAL_EFFECTIVE
end end
return mod1 * mod2 return mod1 * mod2
@@ -96,15 +96,18 @@ class Battle::AI
#============================================================================= #=============================================================================
# Immunity to a move because of the target's ability, item or other effects # Immunity to a move because of the target's ability, item or other effects
#============================================================================= #=============================================================================
def pbCheckMoveImmunity(score, move, user, target, skill) def pbCheckMoveImmunity(move, target)
type = pbRoughType(move, user, skill) # TODO: Add consideration of user's Mold Breaker.
typeMod = pbCalcTypeMod(type, user, target) move_type = pbRoughType(move)
typeMod = pbCalcTypeMod(move_type, @user, target)
# Type effectiveness # Type effectiveness
return true if (move.damagingMove? && Effectiveness.ineffective?(typeMod)) || score <= 0 return true if move.damagingMove? && Effectiveness.ineffective?(typeMod)
# Immunity due to ability/item/other effects # Immunity due to ability/item/other effects
if skill >= PBTrainerAI.mediumSkill if skill_check(AILevel.medium)
case type case move_type
when :GROUND when :GROUND
# TODO: Split target.airborne? into separate parts to allow different
# skill levels to apply to each part.
return true if target.airborne? && !move.hitsFlyingTargets? return true if target.airborne? && !move.hitsFlyingTargets?
when :FIRE when :FIRE
return true if target.hasActiveAbility?(:FLASHFIRE) return true if target.hasActiveAbility?(:FLASHFIRE)
@@ -117,24 +120,25 @@ class Battle::AI
end end
return true if move.damagingMove? && Effectiveness.not_very_effective?(typeMod) && return true if move.damagingMove? && Effectiveness.not_very_effective?(typeMod) &&
target.hasActiveAbility?(:WONDERGUARD) target.hasActiveAbility?(:WONDERGUARD)
return true if move.damagingMove? && user.index != target.index && !target.opposes?(user) && return true if move.damagingMove? && @user.index != target.index && !target.opposes?(@user) &&
target.hasActiveAbility?(:TELEPATHY) target.hasActiveAbility?(:TELEPATHY)
return true if move.statusMove? && move.canMagicCoat? && target.hasActiveAbility?(:MAGICBOUNCE) && return true if move.statusMove? && move.canMagicCoat? && target.hasActiveAbility?(:MAGICBOUNCE) &&
target.opposes?(user) target.opposes?(@user)
return true if move.soundMove? && target.hasActiveAbility?(:SOUNDPROOF) return true if move.soundMove? && target.hasActiveAbility?(:SOUNDPROOF)
return true if move.bombMove? && target.hasActiveAbility?(:BULLETPROOF) return true if move.bombMove? && target.hasActiveAbility?(:BULLETPROOF)
if move.powderMove? if move.powderMove?
return true if target.pbHasType?(:GRASS) return true if target.pbHasType?(:GRASS)
return true if target.hasActiveAbility?(:OVERCOAT) return true if skill_check(AILevel.best) && target.hasActiveAbility?(:OVERCOAT)
return true if target.hasActiveItem?(:SAFETYGOGGLES) return true if skill_check(AILevel.high) && target.hasActiveItem?(:SAFETYGOGGLES)
end end
return true if move.statusMove? && target.effects[PBEffects::Substitute] > 0 && return true if move.statusMove? && target.effects[PBEffects::Substitute] > 0 &&
!move.ignoresSubstitute?(user) && user.index != target.index !move.ignoresSubstitute?(@user) && @user.index != target.index
return true if move.statusMove? && Settings::MECHANICS_GENERATION >= 7 && return true if move.statusMove? && Settings::MECHANICS_GENERATION >= 7 &&
user.hasActiveAbility?(:PRANKSTER) && target.pbHasType?(:DARK) && @user.hasActiveAbility?(:PRANKSTER) && target.pbHasType?(:DARK) &&
target.opposes?(user) target.opposes?(@user)
return true if move.priority > 0 && @battle.field.terrain == :Psychic && return true if move.priority > 0 && @battle.field.terrain == :Psychic &&
target.affectedByTerrain? && target.opposes?(user) target.affectedByTerrain? && target.opposes?(@user)
# TODO: Dazzling/Queenly Majesty go here.
end end
return false return false
end end
@@ -142,16 +146,14 @@ class Battle::AI
#============================================================================= #=============================================================================
# Get approximate properties for a battler # Get approximate properties for a battler
#============================================================================= #=============================================================================
def pbRoughType(move, user, skill) def pbRoughType(move)
ret = move.type ret = move.type
if skill >= PBTrainerAI.highSkill ret = move.pbCalcType(@user) if skill_check(AILevel.high)
ret = move.pbCalcType(user)
end
return ret return ret
end end
def pbRoughStat(battler, stat, skill) def pbRoughStat(battler, stat)
return battler.pbSpeed if skill >= PBTrainerAI.highSkill && stat == :SPEED return battler.pbSpeed if skill_check(AILevel.high) && stat == :SPEED
stageMul = [2, 2, 2, 2, 2, 2, 2, 3, 4, 5, 6, 7, 8] 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] stageDiv = [8, 7, 6, 5, 4, 3, 2, 2, 2, 2, 2, 2, 2]
stage = battler.stages[stat] + 6 stage = battler.stages[stat] + 6
@@ -169,25 +171,25 @@ class Battle::AI
#============================================================================= #=============================================================================
# Get a better move's base damage value # Get a better move's base damage value
#============================================================================= #=============================================================================
def pbMoveBaseDamage(move, user, target, skill) def pbMoveBaseDamage(move, target)
baseDmg = move.baseDamage baseDmg = move.baseDamage
baseDmg = 60 if baseDmg == 1 baseDmg = 60 if baseDmg == 1
return baseDmg if skill < PBTrainerAI.mediumSkill return baseDmg if !skill_check(AILevel.medium)
# Covers all function codes which have their own def pbBaseDamage # Covers all function codes which have their own def pbBaseDamage
case move.function case move.function
# Sonic Boom, Dragon Rage, Super Fang, Night Shade, Endeavor # Sonic Boom, Dragon Rage, Super Fang, Night Shade, Endeavor
when "FixedDamage20", "FixedDamage40", "FixedDamageHalfTargetHP", when "FixedDamage20", "FixedDamage40", "FixedDamageHalfTargetHP",
"FixedDamageUserLevel", "LowerTargetHPToUserHP" "FixedDamageUserLevel", "LowerTargetHPToUserHP"
baseDmg = move.pbFixedDamage(user, target) baseDmg = move.pbFixedDamage(@user, target)
when "FixedDamageUserLevelRandom" # Psywave when "FixedDamageUserLevelRandom" # Psywave
baseDmg = user.level baseDmg = @user.level
when "OHKO", "OHKOIce", "OHKOHitsUndergroundTarget" when "OHKO", "OHKOIce", "OHKOHitsUndergroundTarget"
baseDmg = 200 baseDmg = 200
when "CounterPhysicalDamage", "CounterSpecialDamage", "CounterDamagePlusHalf" when "CounterPhysicalDamage", "CounterSpecialDamage", "CounterDamagePlusHalf"
baseDmg = 60 baseDmg = 60
when "DoublePowerIfTargetUnderwater", "DoublePowerIfTargetUnderground", when "DoublePowerIfTargetUnderwater", "DoublePowerIfTargetUnderground",
"BindTargetDoublePowerIfTargetUnderwater" "BindTargetDoublePowerIfTargetUnderwater"
baseDmg = move.pbModifyDamage(baseDmg, user, target) baseDmg = move.pbModifyDamage(baseDmg, @user, target)
# Gust, Twister, Venoshock, Smelling Salts, Wake-Up Slap, Facade, Hex, Brine, # Gust, Twister, Venoshock, Smelling Salts, Wake-Up Slap, Facade, Hex, Brine,
# Retaliate, Weather Ball, Return, Frustration, Eruption, Crush Grip, # Retaliate, Weather Ball, Return, Frustration, Eruption, Crush Grip,
# Stored Power, Punishment, Hidden Power, Fury Cutter, Echoed Voice, # Stored Power, Punishment, Hidden Power, Fury Cutter, Echoed Voice,
@@ -217,12 +219,12 @@ class Battle::AI
"PowerHigherWithTargetWeight", "PowerHigherWithTargetWeight",
"ThrowUserItemAtTarget", "ThrowUserItemAtTarget",
"PowerDependsOnUserStockpile" "PowerDependsOnUserStockpile"
baseDmg = move.pbBaseDamage(baseDmg, user, target) baseDmg = move.pbBaseDamage(baseDmg, @user, target)
when "DoublePowerIfUserHasNoItem" # Acrobatics when "DoublePowerIfUserHasNoItem" # Acrobatics
baseDmg *= 2 if !user.item || user.hasActiveItem?(:FLYINGGEM) baseDmg *= 2 if !@user.item || @user.hasActiveItem?(:FLYINGGEM)
when "PowerHigherWithTargetFasterThanUser" # Gyro Ball when "PowerHigherWithTargetFasterThanUser" # Gyro Ball
targetSpeed = pbRoughStat(target, :SPEED, skill) targetSpeed = pbRoughStat(target, :SPEED)
userSpeed = pbRoughStat(user, :SPEED, skill) userSpeed = pbRoughStat(@user, :SPEED)
baseDmg = [[(25 * targetSpeed / userSpeed).floor, 150].min, 1].max baseDmg = [[(25 * targetSpeed / userSpeed).floor, 150].min, 1].max
when "RandomlyDamageOrHealTarget" # Present when "RandomlyDamageOrHealTarget" # Present
baseDmg = 50 baseDmg = 50
@@ -230,46 +232,46 @@ class Battle::AI
baseDmg = 71 baseDmg = 71
baseDmg *= 2 if target.inTwoTurnAttack?("TwoTurnAttackInvulnerableUnderground") # Dig baseDmg *= 2 if target.inTwoTurnAttack?("TwoTurnAttackInvulnerableUnderground") # Dig
when "TypeAndPowerDependOnUserBerry" # Natural Gift when "TypeAndPowerDependOnUserBerry" # Natural Gift
baseDmg = move.pbNaturalGiftBaseDamage(user.item_id) baseDmg = move.pbNaturalGiftBaseDamage(@user.item_id)
when "PowerHigherWithUserHeavierThanTarget" # Heavy Slam when "PowerHigherWithUserHeavierThanTarget" # Heavy Slam
baseDmg = move.pbBaseDamage(baseDmg, user, target) baseDmg = move.pbBaseDamage(baseDmg, @user, target)
baseDmg *= 2 if Settings::MECHANICS_GENERATION >= 7 && skill >= PBTrainerAI.mediumSkill && baseDmg *= 2 if Settings::MECHANICS_GENERATION >= 7 && skill_check(AILevel.medium) &&
target.effects[PBEffects::Minimize] target.effects[PBEffects::Minimize]
when "AlwaysCriticalHit", "HitTwoTimes", "HitTwoTimesPoisonTarget" # Frost Breath, Double Kick, Twineedle when "AlwaysCriticalHit", "HitTwoTimes", "HitTwoTimesPoisonTarget" # Frost Breath, Double Kick, Twineedle
baseDmg *= 2 baseDmg *= 2
when "HitThreeTimesPowersUpWithEachHit" # Triple Kick when "HitThreeTimesPowersUpWithEachHit" # Triple Kick
baseDmg *= 6 # Hits do x1, x2, x3 baseDmg in turn, for x6 in total baseDmg *= 6 # Hits do x1, x2, x3 baseDmg in turn, for x6 in total
when "HitTwoToFiveTimes" # Fury Attack when "HitTwoToFiveTimes" # Fury Attack
if user.hasActiveAbility?(:SKILLLINK) if @user.hasActiveAbility?(:SKILLLINK)
baseDmg *= 5 baseDmg *= 5
else else
baseDmg = (baseDmg * 31 / 10).floor # Average damage dealt baseDmg = (baseDmg * 31 / 10).floor # Average damage dealt
end end
when "HitTwoToFiveTimesOrThreeForAshGreninja" when "HitTwoToFiveTimesOrThreeForAshGreninja"
if user.isSpecies?(:GRENINJA) && user.form == 2 if @user.isSpecies?(:GRENINJA) && @user.form == 2
baseDmg *= 4 # 3 hits at 20 power = 4 hits at 15 power baseDmg *= 4 # 3 hits at 20 power = 4 hits at 15 power
elsif user.hasActiveAbility?(:SKILLLINK) elsif @user.hasActiveAbility?(:SKILLLINK)
baseDmg *= 5 baseDmg *= 5
else else
baseDmg = (baseDmg * 31 / 10).floor # Average damage dealt baseDmg = (baseDmg * 31 / 10).floor # Average damage dealt
end end
when "HitOncePerUserTeamMember" # Beat Up when "HitOncePerUserTeamMember" # Beat Up
mult = 0 mult = 0
@battle.eachInTeamFromBattlerIndex(user.index) do |pkmn, _i| @battle.eachInTeamFromBattlerIndex(@user.index) do |pkmn, _i|
mult += 1 if pkmn&.able? && pkmn.status == :NONE mult += 1 if pkmn&.able? && pkmn.status == :NONE
end end
baseDmg *= mult baseDmg *= mult
when "TwoTurnAttackOneTurnInSun" # Solar Beam when "TwoTurnAttackOneTurnInSun" # Solar Beam
baseDmg = move.pbBaseDamageMultiplier(baseDmg, user, target) baseDmg = move.pbBaseDamageMultiplier(baseDmg, @user, target)
when "MultiTurnAttackPowersUpEachTurn" # Rollout when "MultiTurnAttackPowersUpEachTurn" # Rollout
baseDmg *= 2 if user.effects[PBEffects::DefenseCurl] baseDmg *= 2 if @user.effects[PBEffects::DefenseCurl]
when "MultiTurnAttackBideThenReturnDoubleDamage" # Bide when "MultiTurnAttackBideThenReturnDoubleDamage" # Bide
baseDmg = 40 baseDmg = 40
when "UserFaintsFixedDamageUserHP" # Final Gambit when "UserFaintsFixedDamageUserHP" # Final Gambit
baseDmg = user.hp baseDmg = @user.hp
when "EffectivenessIncludesFlyingType" # Flying Press when "EffectivenessIncludesFlyingType" # Flying Press
if GameData::Type.exists?(:FLYING) if GameData::Type.exists?(:FLYING)
if skill >= PBTrainerAI.highSkill if skill_check(AILevel.high)
targetTypes = target.pbTypes(true) targetTypes = target.pbTypes(true)
mult = Effectiveness.calculate( mult = Effectiveness.calculate(
:FLYING, targetTypes[0], targetTypes[1], targetTypes[2] :FLYING, targetTypes[0], targetTypes[1], targetTypes[2]
@@ -281,12 +283,12 @@ class Battle::AI
end end
baseDmg = (baseDmg.to_f * mult / Effectiveness::NORMAL_EFFECTIVE).round baseDmg = (baseDmg.to_f * mult / Effectiveness::NORMAL_EFFECTIVE).round
end end
baseDmg *= 2 if skill >= PBTrainerAI.mediumSkill && target.effects[PBEffects::Minimize] baseDmg *= 2 if skill_check(AILevel.medium) && target.effects[PBEffects::Minimize]
when "DoublePowerIfUserLastMoveFailed" # Stomping Tantrum when "DoublePowerIfUserLastMoveFailed" # Stomping Tantrum
baseDmg *= 2 if user.lastRoundMoveFailed baseDmg *= 2 if @user.lastRoundMoveFailed
when "HitTwoTimesFlinchTarget" # Double Iron Bash when "HitTwoTimesFlinchTarget" # Double Iron Bash
baseDmg *= 2 baseDmg *= 2
baseDmg *= 2 if skill >= PBTrainerAI.mediumSkill && target.effects[PBEffects::Minimize] baseDmg *= 2 if skill_check(AILevel.medium) && target.effects[PBEffects::Minimize]
end end
return baseDmg return baseDmg
end end
@@ -294,29 +296,33 @@ class Battle::AI
#============================================================================= #=============================================================================
# Damage calculation # Damage calculation
#============================================================================= #=============================================================================
def pbRoughDamage(move, user, target, skill, baseDmg) def pbRoughDamage(move, target, baseDmg)
# Fixed damage moves # Fixed damage moves
return baseDmg if move.is_a?(Battle::Move::FixedDamageMove) return baseDmg if move.is_a?(Battle::Move::FixedDamageMove)
# Get the move's type # Get the move's type
type = pbRoughType(move, user, skill) type = pbRoughType(move)
##### Calculate user's attack stat ##### ##### Calculate user's attack stat #####
atk = pbRoughStat(user, :ATTACK, skill) atk = pbRoughStat(@user, :ATTACK)
if move.function == "UseTargetAttackInsteadOfUserAttack" # Foul Play if move.function == "UseTargetAttackInsteadOfUserAttack" # Foul Play
atk = pbRoughStat(target, :ATTACK, skill) atk = pbRoughStat(target, :ATTACK)
elsif move.function == "UseUserBaseDefenseInsteadOfUserBaseAttack" # Body Press elsif move.function == "UseUserBaseDefenseInsteadOfUserBaseAttack" # Body Press
atk = pbRoughStat(user, :DEFENSE, skill) atk = pbRoughStat(@user, :DEFENSE)
elsif move.specialMove?(type) elsif move.specialMove?(type)
if move.function == "UseTargetAttackInsteadOfUserAttack" # Foul Play if move.function == "UseTargetAttackInsteadOfUserAttack" # Foul Play
atk = pbRoughStat(target, :SPECIAL_ATTACK, skill) atk = pbRoughStat(target, :SPECIAL_ATTACK)
else else
atk = pbRoughStat(user, :SPECIAL_ATTACK, skill) atk = pbRoughStat(@user, :SPECIAL_ATTACK)
end end
end end
##### Calculate target's defense stat ##### ##### Calculate target's defense stat #####
defense = pbRoughStat(target, :DEFENSE, skill) defense = pbRoughStat(target, :DEFENSE)
if move.specialMove?(type) && move.function != "UseTargetDefenseInsteadOfTargetSpDef" # Psyshock if move.specialMove?(type) && move.function != "UseTargetDefenseInsteadOfTargetSpDef" # Psyshock
defense = pbRoughStat(target, :SPECIAL_DEFENSE, skill) defense = pbRoughStat(target, :SPECIAL_DEFENSE)
end end
##### Calculate all multiplier effects ##### ##### Calculate all multiplier effects #####
multipliers = { multipliers = {
:base_damage_multiplier => 1.0, :base_damage_multiplier => 1.0,
@@ -325,11 +331,9 @@ class Battle::AI
:final_damage_multiplier => 1.0 :final_damage_multiplier => 1.0
} }
# Ability effects that alter damage # Ability effects that alter damage
moldBreaker = false moldBreaker = skill_check(AILevel.high) && target.hasMoldBreaker?
if skill >= PBTrainerAI.highSkill && target.hasMoldBreaker?
moldBreaker = true if skill_check(AILevel.medium) && @user.abilityActive?
end
if skill >= PBTrainerAI.mediumSkill && user.abilityActive?
# NOTE: These abilities aren't suitable for checking at the start of the # NOTE: These abilities aren't suitable for checking at the start of the
# round. # round.
abilityBlacklist = [:ANALYTIC, :SNIPER, :TINTEDLENS, :AERILATE, :PIXILATE, :REFRIGERATE] abilityBlacklist = [:ANALYTIC, :SNIPER, :TINTEDLENS, :AERILATE, :PIXILATE, :REFRIGERATE]
@@ -341,19 +345,21 @@ class Battle::AI
end end
if canCheck if canCheck
Battle::AbilityEffects.triggerDamageCalcFromUser( Battle::AbilityEffects.triggerDamageCalcFromUser(
user.ability, user, target, move, multipliers, baseDmg, type @user.ability, @user, target, move, multipliers, baseDmg, type
) )
end end
end end
if skill >= PBTrainerAI.mediumSkill && !moldBreaker
user.allAllies.each do |b| if skill_check(AILevel.medium) && !moldBreaker
@user.allAllies.each do |b|
next if !b.abilityActive? next if !b.abilityActive?
Battle::AbilityEffects.triggerDamageCalcFromAlly( Battle::AbilityEffects.triggerDamageCalcFromAlly(
b.ability, user, target, move, multipliers, baseDmg, type b.ability, @user, target, move, multipliers, baseDmg, type
) )
end end
end end
if skill >= PBTrainerAI.bestSkill && !moldBreaker && target.abilityActive?
if skill_check(AILevel.best) && !moldBreaker && target.abilityActive?
# NOTE: These abilities aren't suitable for checking at the start of the # NOTE: These abilities aren't suitable for checking at the start of the
# round. # round.
abilityBlacklist = [:FILTER, :SOLIDROCK] abilityBlacklist = [:FILTER, :SOLIDROCK]
@@ -365,40 +371,45 @@ class Battle::AI
end end
if canCheck if canCheck
Battle::AbilityEffects.triggerDamageCalcFromTarget( Battle::AbilityEffects.triggerDamageCalcFromTarget(
target.ability, user, target, move, multipliers, baseDmg, type target.ability, @user, target, move, multipliers, baseDmg, type
) )
end end
end end
if skill >= PBTrainerAI.bestSkill && !moldBreaker
if skill_check(AILevel.best) && !moldBreaker
target.allAllies.each do |b| target.allAllies.each do |b|
next if !b.abilityActive? next if !b.abilityActive?
Battle::AbilityEffects.triggerDamageCalcFromTargetAlly( Battle::AbilityEffects.triggerDamageCalcFromTargetAlly(
b.ability, user, target, move, multipliers, baseDmg, type b.ability, @user, target, move, multipliers, baseDmg, type
) )
end end
end end
# Item effects that alter damage # Item effects that alter damage
# NOTE: Type-boosting gems aren't suitable for checking at the start of the # NOTE: Type-boosting gems aren't suitable for checking at the start of the
# round. # round.
if skill >= PBTrainerAI.mediumSkill && user.itemActive? if skill_check(AILevel.medium) && @user.itemActive?
# NOTE: These items aren't suitable for checking at the start of the # NOTE: These items aren't suitable for checking at the start of the
# round. # round.
itemBlacklist = [:EXPERTBELT, :LIFEORB] itemBlacklist = [:EXPERTBELT, :LIFEORB]
if !itemBlacklist.include?(user.item_id) if !itemBlacklist.include?(@user.item_id)
Battle::ItemEffects.triggerDamageCalcFromUser( Battle::ItemEffects.triggerDamageCalcFromUser(
user.item, user, target, move, multipliers, baseDmg, type @user.item, @user, target, move, multipliers, baseDmg, type
) )
user.effects[PBEffects::GemConsumed] = nil # Untrigger consuming of Gems @user.effects[PBEffects::GemConsumed] = nil # Untrigger consuming of Gems
end end
# TODO: Prefer (1.5x?) if item will be consumed and user has Unburden.
end end
if skill >= PBTrainerAI.bestSkill &&
if skill_check(AILevel.best) &&
target.itemActive? && target.item && !target.item.is_berry? target.itemActive? && target.item && !target.item.is_berry?
Battle::ItemEffects.triggerDamageCalcFromTarget( Battle::ItemEffects.triggerDamageCalcFromTarget(
target.item, user, target, move, multipliers, baseDmg, type target.item, @user, target, move, multipliers, baseDmg, type
) )
end end
# Global abilities # Global abilities
if skill >= PBTrainerAI.mediumSkill && if skill_check(AILevel.medium) &&
((@battle.pbCheckGlobalAbility(:DARKAURA) && type == :DARK) || ((@battle.pbCheckGlobalAbility(:DARKAURA) && type == :DARK) ||
(@battle.pbCheckGlobalAbility(:FAIRYAURA) && type == :FAIRY)) (@battle.pbCheckGlobalAbility(:FAIRYAURA) && type == :FAIRY))
if @battle.pbCheckGlobalAbility(:AURABREAK) if @battle.pbCheckGlobalAbility(:AURABREAK)
@@ -407,20 +418,25 @@ class Battle::AI
multipliers[:base_damage_multiplier] *= 4 / 3.0 multipliers[:base_damage_multiplier] *= 4 / 3.0
end end
end end
# Parental Bond # Parental Bond
if skill >= PBTrainerAI.mediumSkill && user.hasActiveAbility?(:PARENTALBOND) if skill_check(AILevel.medium) && @user.hasActiveAbility?(:PARENTALBOND)
multipliers[:base_damage_multiplier] *= 1.25 multipliers[:base_damage_multiplier] *= 1.25
end end
# Me First # Me First
# TODO # TODO
# Helping Hand - n/a # Helping Hand - n/a
# Charge # Charge
if skill >= PBTrainerAI.mediumSkill && if skill_check(AILevel.medium) &&
user.effects[PBEffects::Charge] > 0 && type == :ELECTRIC @user.effects[PBEffects::Charge] > 0 && type == :ELECTRIC
multipliers[:base_damage_multiplier] *= 2 multipliers[:base_damage_multiplier] *= 2
end end
# Mud Sport and Water Sport # Mud Sport and Water Sport
if skill >= PBTrainerAI.mediumSkill if skill_check(AILevel.medium)
if type == :ELECTRIC if type == :ELECTRIC
if @battle.allBattlers.any? { |b| b.effects[PBEffects::MudSport] } if @battle.allBattlers.any? { |b| b.effects[PBEffects::MudSport] }
multipliers[:base_damage_multiplier] /= 3 multipliers[:base_damage_multiplier] /= 3
@@ -438,34 +454,40 @@ class Battle::AI
end end
end end
end end
# Terrain moves # Terrain moves
if skill >= PBTrainerAI.mediumSkill if skill_check(AILevel.medium)
case @battle.field.terrain case @battle.field.terrain
when :Electric when :Electric
multipliers[:base_damage_multiplier] *= 1.5 if type == :ELECTRIC && user.affectedByTerrain? multipliers[:base_damage_multiplier] *= 1.5 if type == :ELECTRIC && @user.affectedByTerrain?
when :Grassy when :Grassy
multipliers[:base_damage_multiplier] *= 1.5 if type == :GRASS && user.affectedByTerrain? multipliers[:base_damage_multiplier] *= 1.5 if type == :GRASS && @user.affectedByTerrain?
when :Psychic when :Psychic
multipliers[:base_damage_multiplier] *= 1.5 if type == :PSYCHIC && user.affectedByTerrain? multipliers[:base_damage_multiplier] *= 1.5 if type == :PSYCHIC && @user.affectedByTerrain?
when :Misty when :Misty
multipliers[:base_damage_multiplier] /= 2 if type == :DRAGON && target.affectedByTerrain? multipliers[:base_damage_multiplier] /= 2 if type == :DRAGON && target.affectedByTerrain?
end end
end end
# Badge multipliers # Badge multipliers
if skill >= PBTrainerAI.highSkill && @battle.internalBattle && target.pbOwnedByPlayer? if skill_check(AILevel.high) && @battle.internalBattle && target.pbOwnedByPlayer?
# Don't need to check the Atk/Sp Atk-boosting badges because the AI
# won't control the player's Pokémon.
if move.physicalMove?(type) && @battle.pbPlayer.badge_count >= Settings::NUM_BADGES_BOOST_DEFENSE if move.physicalMove?(type) && @battle.pbPlayer.badge_count >= Settings::NUM_BADGES_BOOST_DEFENSE
multipliers[:defense_multiplier] *= 1.1 multipliers[:defense_multiplier] *= 1.1
elsif move.specialMove?(type) && @battle.pbPlayer.badge_count >= Settings::NUM_BADGES_BOOST_SPDEF elsif move.specialMove?(type) && @battle.pbPlayer.badge_count >= Settings::NUM_BADGES_BOOST_SPDEF
multipliers[:defense_multiplier] *= 1.1 multipliers[:defense_multiplier] *= 1.1
end end
end end
# Multi-targeting attacks # Multi-targeting attacks
if skill >= PBTrainerAI.highSkill && pbTargetsMultiple?(move, user) if skill_check(AILevel.high) && pbTargetsMultiple?(move)
multipliers[:final_damage_multiplier] *= 0.75 multipliers[:final_damage_multiplier] *= 0.75
end end
# Weather # Weather
if skill >= PBTrainerAI.mediumSkill if skill_check(AILevel.medium)
case user.effectiveWeather case @user.effectiveWeather
when :Sun, :HarshSun when :Sun, :HarshSun
case type case type
when :FIRE when :FIRE
@@ -487,30 +509,36 @@ class Battle::AI
end end
end end
end end
# Critical hits - n/a # Critical hits - n/a
# Random variance - n/a # Random variance - n/a
# STAB # STAB
if skill >= PBTrainerAI.mediumSkill && type && user.pbHasType?(type) if skill_check(AILevel.medium) && type && @user.pbHasType?(type)
if user.hasActiveAbility?(:ADAPTABILITY) if @user.hasActiveAbility?(:ADAPTABILITY)
multipliers[:final_damage_multiplier] *= 2 multipliers[:final_damage_multiplier] *= 2
else else
multipliers[:final_damage_multiplier] *= 1.5 multipliers[:final_damage_multiplier] *= 1.5
end end
end end
# Type effectiveness # Type effectiveness
if skill >= PBTrainerAI.mediumSkill if skill_check(AILevel.medium)
typemod = pbCalcTypeMod(type, user, target) typemod = pbCalcTypeMod(type, @user, target)
multipliers[:final_damage_multiplier] *= typemod.to_f / Effectiveness::NORMAL_EFFECTIVE multipliers[:final_damage_multiplier] *= typemod.to_f / Effectiveness::NORMAL_EFFECTIVE
end end
# Burn # Burn
if skill >= PBTrainerAI.highSkill && move.physicalMove?(type) && if skill_check(AILevel.high) && move.physicalMove?(type) &&
user.status == :BURN && !user.hasActiveAbility?(:GUTS) && @user.status == :BURN && !@user.hasActiveAbility?(:GUTS) &&
!(Settings::MECHANICS_GENERATION >= 6 && !(Settings::MECHANICS_GENERATION >= 6 &&
move.function == "DoublePowerIfUserPoisonedBurnedParalyzed") # Facade move.function == "DoublePowerIfUserPoisonedBurnedParalyzed") # Facade
multipliers[:final_damage_multiplier] /= 2 multipliers[:final_damage_multiplier] /= 2
end end
# Aurora Veil, Reflect, Light Screen # Aurora Veil, Reflect, Light Screen
if skill >= PBTrainerAI.highSkill && !move.ignoresReflect? && !user.hasActiveAbility?(:INFILTRATOR) if skill_check(AILevel.high) && !move.ignoresReflect? && !@user.hasActiveAbility?(:INFILTRATOR)
if target.pbOwnSide.effects[PBEffects::AuroraVeil] > 0 if target.pbOwnSide.effects[PBEffects::AuroraVeil] > 0
if @battle.pbSideBattlerCount(target) > 1 if @battle.pbSideBattlerCount(target) > 1
multipliers[:final_damage_multiplier] *= 2 / 3.0 multipliers[:final_damage_multiplier] *= 2 / 3.0
@@ -531,80 +559,93 @@ class Battle::AI
end end
end end
end end
# Minimize # Minimize
if skill >= PBTrainerAI.highSkill && target.effects[PBEffects::Minimize] && move.tramplesMinimize? if skill_check(AILevel.high) && target.effects[PBEffects::Minimize] && move.tramplesMinimize?
multipliers[:final_damage_multiplier] *= 2 multipliers[:final_damage_multiplier] *= 2
end end
# Move-specific base damage modifiers # Move-specific base damage modifiers
# TODO # TODO
# Move-specific final damage modifiers # Move-specific final damage modifiers
# TODO # TODO
##### Main damage calculation ##### ##### Main damage calculation #####
baseDmg = [(baseDmg * multipliers[:base_damage_multiplier]).round, 1].max baseDmg = [(baseDmg * multipliers[:base_damage_multiplier]).round, 1].max
atk = [(atk * multipliers[:attack_multiplier]).round, 1].max atk = [(atk * multipliers[:attack_multiplier]).round, 1].max
defense = [(defense * multipliers[:defense_multiplier]).round, 1].max defense = [(defense * multipliers[:defense_multiplier]).round, 1].max
damage = ((((2.0 * user.level / 5) + 2).floor * baseDmg * atk / defense).floor / 50).floor + 2 damage = ((((2.0 * @user.level / 5) + 2).floor * baseDmg * atk / defense).floor / 50).floor + 2
damage = [(damage * multipliers[:final_damage_multiplier]).round, 1].max damage = [(damage * multipliers[:final_damage_multiplier]).round, 1].max
# "AI-specific calculations below"
# Increased critical hit rates
if skill >= PBTrainerAI.mediumSkill
c = 0
# Ability effects that alter critical hit rate
if c >= 0 && user.abilityActive?
c = Battle::AbilityEffects.triggerCriticalCalcFromUser(user.ability, user, target, c)
end
if skill >= PBTrainerAI.bestSkill && c >= 0 && !moldBreaker && target.abilityActive?
c = Battle::AbilityEffects.triggerCriticalCalcFromTarget(target.ability, user, target, c)
end
# Item effects that alter critical hit rate
if c >= 0 && user.itemActive?
c = Battle::ItemEffects.triggerCriticalCalcFromUser(user.item, user, target, c)
end
if skill >= PBTrainerAI.bestSkill && c >= 0 && target.itemActive?
c = Battle::ItemEffects.triggerCriticalCalcFromTarget(target.item, user, target, c)
end
# Other efffects
c = -1 if target.pbOwnSide.effects[PBEffects::LuckyChant] > 0
if c >= 0
c += 1 if move.highCriticalRate?
c += user.effects[PBEffects::FocusEnergy]
c += 1 if user.inHyperMode? && move.type == :SHADOW
end
if c >= 0
c = 4 if c > 4
damage += damage * 0.1 * c
end
end
return damage.floor return damage.floor
end end
#=============================================================================
# Critical hit rate calculation
#=============================================================================
def pbRoughCriticalHitStage(move, target)
return -1 if target.pbOwnSide.effects[PBEffects::LuckyChant] > 0
mold_breaker = (skill_check(AILevel.medium) && @user.hasMoldBreaker?)
crit_stage = 0
# Ability effects that alter critical hit rate
if skill_check(AILevel.medium) && @user.abilityActive?
crit_stage = BattleHandlers.triggerCriticalCalcUserAbility(@user.ability, @user, target, crit_stage)
return -1 if crit_stage < 0
end
if skill_check(AILevel.best) && !mold_breaker && target.abilityActive?
crit_stage = BattleHandlers.triggerCriticalCalcTargetAbility(target.ability, @user, target, crit_stage)
return -1 if crit_stage < 0
end
# Item effects that alter critical hit rate
if skill_check(AILevel.medium) && @user.itemActive?
crit_stage = BattleHandlers.triggerCriticalCalcUserItem(@user.item, @user, target, crit_stage)
return -1 if crit_stage < 0
end
if skill_check(AILevel.high) && target.itemActive?
crit_stage = BattleHandlers.triggerCriticalCalcTargetItem(target.item, @user, target, crit_stage)
return -1 if crit_stage < 0
end
# Other effects
case move.pbCritialOverride(@user, target)
when 1 then return 99
when -1 then return -1
end
return 99 if crit_stage > 50 # Merciless
return 99 if @user.effects[PBEffects::LaserFocus] > 0
crit_stage += 1 if move.highCriticalRate?
crit_stage += @user.effects[PBEffects::FocusEnergy]
crit_stage += 1 if @user.inHyperMode? && move.type == :SHADOW
crit_stage = [crit_stage, Battle::Move::CRITICAL_HIT_RATIOS.length - 1].min
return crit_stage
end
#============================================================================= #=============================================================================
# Accuracy calculation # Accuracy calculation
#============================================================================= #=============================================================================
def pbRoughAccuracy(move, user, target, skill) def pbRoughAccuracy(move, target)
# "Always hit" effects and "always hit" accuracy # "Always hit" effects and "always hit" accuracy
if skill >= PBTrainerAI.mediumSkill if skill_check(AILevel.medium)
return 125 if target.effects[PBEffects::Minimize] && move.tramplesMinimize? && return 100 if target.effects[PBEffects::Minimize] && move.tramplesMinimize? &&
Settings::MECHANICS_GENERATION >= 6 Settings::MECHANICS_GENERATION >= 6
return 125 if target.effects[PBEffects::Telekinesis] > 0 return 100 if target.effects[PBEffects::Telekinesis] > 0
end end
# Get base accuracy
baseAcc = move.accuracy baseAcc = move.accuracy
if skill >= PBTrainerAI.highSkill baseAcc = move.pbBaseAccuracy(@user, target) if skill_check(AILevel.medium)
baseAcc = move.pbBaseAccuracy(user, target) return 100 if baseAcc == 0 && skill_check(AILevel.medium)
end
return 125 if baseAcc == 0 && skill >= PBTrainerAI.mediumSkill
# Get the move's type # Get the move's type
type = pbRoughType(move, user, skill) type = pbRoughType(move)
# Calculate all modifier effects # Calculate all modifier effects
modifiers = {} modifiers = {}
modifiers[:base_accuracy] = baseAcc modifiers[:base_accuracy] = baseAcc
modifiers[:accuracy_stage] = user.stages[:ACCURACY] modifiers[:accuracy_stage] = @user.stages[:ACCURACY]
modifiers[:evasion_stage] = target.stages[:EVASION] modifiers[:evasion_stage] = target.stages[:EVASION]
modifiers[:accuracy_multiplier] = 1.0 modifiers[:accuracy_multiplier] = 1.0
modifiers[:evasion_multiplier] = 1.0 modifiers[:evasion_multiplier] = 1.0
pbCalcAccuracyModifiers(user, target, modifiers, move, type, skill) pbCalcAccuracyModifiers(target, modifiers, move, type)
# Check if move can't miss # Check if move certainly misses/can't miss
return 125 if modifiers[:base_accuracy] == 0 return 0 if modifiers[:base_accuracy] < 0
return 100 if modifiers[:base_accuracy] == 0
# Calculation # Calculation
accStage = [[modifiers[:accuracy_stage], -6].max, 6].min + 6 accStage = [[modifiers[:accuracy_stage], -6].max, 6].min + 6
evaStage = [[modifiers[:evasion_stage], -6].max, 6].min + 6 evaStage = [[modifiers[:evasion_stage], -6].max, 6].min + 6
@@ -618,70 +659,82 @@ class Battle::AI
return modifiers[:base_accuracy] * accuracy / evasion return modifiers[:base_accuracy] * accuracy / evasion
end end
def pbCalcAccuracyModifiers(user, target, modifiers, move, type, skill) def pbCalcAccuracyModifiers(target, modifiers, move, type)
moldBreaker = false moldBreaker = (skill_check(AILevel.medium) && target.hasMoldBreaker?)
if skill >= PBTrainerAI.highSkill && target.hasMoldBreaker?
moldBreaker = true
end
# Ability effects that alter accuracy calculation # Ability effects that alter accuracy calculation
if skill >= PBTrainerAI.mediumSkill if skill_check(AILevel.medium) && @user.abilityActive?
if user.abilityActive? Battle::AbilityEffects.triggerAccuracyCalcFromUser(
Battle::AbilityEffects.triggerAccuracyCalcFromUser( @user.ability, modifiers, @user, target, move, type
user.ability, modifiers, user, target, move, type )
) end
end if skill_check(AILevel.high)
user.allAllies.each do |b| @user.allAllies.each do |b|
next if !b.abilityActive? next if !b.abilityActive?
Battle::AbilityEffects.triggerAccuracyCalcFromAlly( Battle::AbilityEffects.triggerAccuracyCalcFromAlly(
b.ability, modifiers, user, target, move, type b.ability, modifiers, @user, target, move, type
) )
end end
end end
if skill >= PBTrainerAI.bestSkill && target.abilityActive? && !moldBreaker if skill_check(AILevel.best) && target.abilityActive? && !moldBreaker
Battle::AbilityEffects.triggerAccuracyCalcFromTarget( Battle::AbilityEffects.triggerAccuracyCalcFromTarget(
target.ability, modifiers, user, target, move, type target.ability, modifiers, @user, target, move, type
) )
end end
# Item effects that alter accuracy calculation # Item effects that alter accuracy calculation
if skill >= PBTrainerAI.mediumSkill && user.itemActive? if skill_check(AILevel.medium) && @user.itemActive?
# TODO: Zoom Lens needs to be checked differently (compare speeds of
# user and target).
Battle::ItemEffects.triggerAccuracyCalcFromUser( Battle::ItemEffects.triggerAccuracyCalcFromUser(
user.item, modifiers, user, target, move, type @user.item, modifiers, @user, target, move, type
) )
end end
if skill >= PBTrainerAI.bestSkill && target.itemActive? if skill_check(AILevel.high) && target.itemActive?
Battle::ItemEffects.triggerAccuracyCalcFromTarget( Battle::ItemEffects.triggerAccuracyCalcFromTarget(
target.item, modifiers, user, target, move, type target.item, modifiers, @user, target, move, type
) )
end end
# Other effects, inc. ones that set accuracy_multiplier or evasion_stage to specific values # Other effects, inc. ones that set accuracy_multiplier or evasion_stage to specific values
if skill >= PBTrainerAI.mediumSkill if @battle.field.effects[PBEffects::Gravity] > 0
if @battle.field.effects[PBEffects::Gravity] > 0 modifiers[:accuracy_multiplier] *= 5 / 3.0
modifiers[:accuracy_multiplier] *= 5 / 3.0 end
end if skill_check(AILevel.medium)
if user.effects[PBEffects::MicleBerry] if @user.effects[PBEffects::MicleBerry]
modifiers[:accuracy_multiplier] *= 1.2 modifiers[:accuracy_multiplier] *= 1.2
end end
modifiers[:evasion_stage] = 0 if target.effects[PBEffects::Foresight] && modifiers[:evasion_stage] > 0 modifiers[:evasion_stage] = 0 if target.effects[PBEffects::Foresight] && modifiers[:evasion_stage] > 0
modifiers[:evasion_stage] = 0 if target.effects[PBEffects::MiracleEye] && modifiers[:evasion_stage] > 0 modifiers[:evasion_stage] = 0 if target.effects[PBEffects::MiracleEye] && modifiers[:evasion_stage] > 0
end end
# "AI-specific calculations below" # "AI-specific calculations below"
if skill >= PBTrainerAI.mediumSkill modifiers[:evasion_stage] = 0 if move.function == "IgnoreTargetDefSpDefEvaStatStages" # Chip Away
modifiers[:evasion_stage] = 0 if move.function == "IgnoreTargetDefSpDefEvaStatStages" # Chip Away if skill_check(AILevel.medium)
modifiers[:base_accuracy] = 0 if user.effects[PBEffects::LockOn] > 0 && modifiers[:base_accuracy] = 0 if @user.effects[PBEffects::LockOn] > 0 &&
user.effects[PBEffects::LockOnPos] == target.index @user.effects[PBEffects::LockOnPos] == target.index
end end
if skill >= PBTrainerAI.highSkill if skill_check(AILevel.medium)
if move.function == "BadPoisonTarget" && # Toxic if move.function == "BadPoisonTarget" && # Toxic
Settings::MORE_TYPE_EFFECTS && move.statusMove? && user.pbHasType?(:POISON) Settings::MORE_TYPE_EFFECTS && move.statusMove? && @user.pbHasType?(:POISON)
modifiers[:base_accuracy] = 0 modifiers[:base_accuracy] = 0
end end
if ["OHKO", "OHKOIce", "OHKOHitsUndergroundTarget"].include?(move.function) if ["OHKO", "OHKOIce", "OHKOHitsUndergroundTarget"].include?(move.function)
modifiers[:base_accuracy] = move.accuracy + user.level - target.level modifiers[:base_accuracy] = move.accuracy + @user.level - target.level
modifiers[:accuracy_multiplier] = 0 if target.level > user.level modifiers[:accuracy_multiplier] = 0 if target.level > @user.level
if skill >= PBTrainerAI.bestSkill && target.hasActiveAbility?(:STURDY) if skill_check(AILevel.best) && target.hasActiveAbility?(:STURDY)
modifiers[:accuracy_multiplier] = 0 modifiers[:accuracy_multiplier] = 0
end end
end end
end end
end end
#=============================================================================
# Check if battler has a move that meets the criteria in the block provided
#=============================================================================
def check_for_move(battler)
ret = false
battler.eachMove do |move|
next unless yield move
ret = true
break
end
return ret
end
end end

View File

@@ -0,0 +1,169 @@
class Battle::AI
#=============================================================================
#
#=============================================================================
# TODO: 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.
module BattleRole
PHAZER = 0
CLERIC = 1
STALLBREAKER = 2
STATUSABSORBER = 3
BATONPASSER = 4
SPINNER = 5
FIELDSETTER = 6
WEATHERSETTER = 7
SWEEPER = 8
PIVOT = 9
PHYSICALWALL = 10
SPECIALWALL = 11
TANK = 12
TRAPPER = 13
SCREENER = 14
ACE = 15
LEAD = 16
SECOND = 17
end
#=============================================================================
# Determine the roles filled by a Pokémon on a given side at a given party
# index.
#=============================================================================
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
hasPivotMove = 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(BattleRole::PHAZER)
when "CureUserPartyStatus" # Aromatherapy/Heal Bell
ret.push(BattleRole::CLERIC)
when "DisableTargetStatusMoves" # Taunt
ret.push(BattleRole::STALLBREAKER)
when "HealUserPositionNextTurn" # Wish
ret.push(BattleRole::CLERIC) if pkmn.ev[:HP] == Pokemon::EV_STAT_LIMIT
when "HealUserFullyAndFallAsleep" # Rest
ret.push(BattleRole::STATUSABSORBER)
when "SwitchOutUserPassOnEffects" # Baton Pass
ret.push(BattleRole::BATONPASSER)
when "SwitchOutUserStatusMove", "SwitchOutUserDamagingMove" # Teleport (Gen 8+), U-turn
hasPivotMove = true
when "RemoveUserBindingAndEntryHazards" # Rapid Spin
ret.push(BattleRole::SPINNER)
when "StartElectricTerrain", "StartGrassyTerrain",
"StartMistyTerrain", "StartPsychicTerrain" # Terrain moves
ret.push(BattleRole::FIELDSETTER)
else
ret.push(BattleRole::WEATHERSETTER) 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(BattleRole::SWEEPER)
end
end
if hasHealMove
ret.push(BattleRole::PIVOT) if hasPivotMove
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(BattleRole::PHYSICALWALL) 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(BattleRole::SPECIALWALL) if pkmn.ev[:SPECIAL_DEFENSE] == Pokemon::EV_STAT_LIMIT
end
else
ret.push(BattleRole::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(BattleRole::PIVOT)
when :GUTS, :QUICKFEET, :FLAREBOOST, :TOXICBOOST, :NATURALCURE, :MAGICGUARD,
:MAGICBOUNCE, :HYDRATION
ret.push(BattleRole::STATUSABSORBER)
when :SHADOWTAG, :ARENATRAP, :MAGNETPULL
ret.push(BattleRole::TRAPPER)
when :DROUGHT, :DRIZZLE, :SANDSTREAM, :SNOWWARNING, :PRIMORDIALSEA,
:DESOLATELAND, :DELTASTREAM
ret.push(BattleRole::WEATHERSETTER)
when :GRASSYSURGE, :ELECTRICSURGE, :MISTYSURGE, :PSYCHICSURGE
ret.push(BattleRole::FIELDSETTER)
end
# Check for items indicative of particular roles
case pkmn.item_id
when :LIGHTCLAY
ret.push(BattleRole::SCREENER)
when :ASSAULTVEST
ret.push(BattleRole::TANK)
when :CHOICEBAND, :CHOICESPECS
ret.push(BattleRole::STALLBREAKER)
ret.push(BattleRole::SWEEPER) if pkmn.ev[:SPEED] == Pokemon::EV_STAT_LIMIT
when :CHOICESCARF
ret.push(BattleRole::SWEEPER) if pkmn.ev[:SPEED] == Pokemon::EV_STAT_LIMIT
when :TOXICORB, :FLAMEORB
ret.push(BattleRole::STATUSABSORBER)
when :TERRAINEXTENDER
ret.push(BattleRole::FIELDSETTER)
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(BattleRole::ACE)
else
ret.push(BattleRole::LEAD) if partyStarts.include?(index)
idxTrainer = @battle.pbGetOwnerIndexFromPartyIndex(side, index)
maxLevel = @battle.pbMaxLevelInTeam(side, idxTrainer)
if pkmn.level >= maxLevel
ret.push(BattleRole::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(BattleRole::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

@@ -0,0 +1,431 @@
class Battle::AI
#=============================================================================
# Apply additional effect chance to a move's score
# TODO: Apply all the additional effect chance modifiers.
#=============================================================================
def apply_effect_chance_to_score(score)
if @move.damagingMove?
# TODO: Doesn't return the correct value for "014" (Chatter).
effect_chance = @move.addlEffect
if effect_chance > 0
effect_chance *= 2 if @user.hasActiveAbility?(:SERENEGRACE) ||
@user.pbOwnSide.effects[PBEffects::Rainbow] > 0
effect_multiplier = [effect_chance.to_f, 100].min / 100
score = ((score - 1) * effect_multiplier) + 1
end
end
return score
end
#=============================================================================
#
#=============================================================================
# TODO: These function codes need to have an attr_reader :statUp and for them
# to be set when the move is initialised.
# 035 Shell Smash
# 037 Acupressure
# 137 Magnetic Flux
# 15C Gear Up
def calc_user_stat_raise_mini_score
mini_score = 1.0
# Determine whether the move boosts Attack, Special Attack or Speed (Bulk Up
# is sometimes not considered a sweeping move)
sweeping_stat = false
offensive_stat = false
@move.stat_up.each_with_index do |stat, idx|
next if idx.odd?
next if ![:ATTACK, :SPATK, :SPEED].include?(stat)
sweeping_stat = true
next if @move.function == "024" # Bulk Up (+Atk +Def)
offensive_stat = true
break
end
# Prefer if user has most of its HP
if @user.hp >= @user.totalhp * 3 / 4
mini_score *= (sweeping_stat) ? 1.2 : 1.1
end
# Prefer if user hasn't been in battle for long
if @user.turnCount < 2
mini_score *= (sweeping_stat) ? 1.2 : 1.1
end
# Prefer if user has the ability Simple
mini_score *= 2 if @user.hasActiveAbility?(:SIMPLE)
# TODO: Prefer if user's moves won't do much damage.
# Prefer if user has something that will limit damage taken
mini_score *= 1.3 if @user.effects[PBEffects::Substitute] > 0 ||
(@user.form == 0 && @user.ability_id == :DISGUISE)
# Don't prefer if user doesn't have much HP left
mini_score *= 0.3 if @user.hp < @user.totalhp / 3
# Don't prefer if user is badly poisoned
mini_score *= 0.2 if @user.effects[PBEffects::Toxic] > 0 && !offensive_stat
# Don't prefer if user is confused
if @user.effects[PBEffects::Confusion] > 0
# TODO: Especially don't prefer if the move raises Atk. Even more so if
# the move raises the stat by 2+. Not quite so much if the move also
# raises Def.
mini_score *= 0.5
end
# Don't prefer if user is infatuated or Leech Seeded
if @user.effects[PBEffects::Attract] >= 0 || @user.effects[PBEffects::LeechSeed] >= 0
mini_score *= (offensive_stat) ? 0.6 : 0.3
end
# Don't prefer if user has an ability or item that will force it to switch
# out
if @user.hp < @user.totalhp * 3 / 4
mini_score *= 0.3 if @user.hasActiveAbility?([:EMERGENCYEXIT, :WIMPOUT])
mini_score *= 0.3 if @user.hasActiveItem?(:EJECTBUTTON)
end
# Prefer if target has a status problem
if @target.status != PBStatuses::NONE
mini_score *= (sweeping_stat) ? 1.2 : 1.1
case @target.status
when PBStatuses::SLEEP, PBStatuses::FROZEN
mini_score *= 1.3
when PBStatuses::BURN
# TODO: Prefer if the move boosts Sp Def.
mini_score *= 1.1 if !offensive_stat
end
end
# Prefer if target is yawning
if @target.effects[PBEffects::Yawn] > 0
mini_score *= (sweeping_stat) ? 1.7 : 1.3
end
# Prefer if target is recovering after Hyper Beam
if @target.effects[PBEffects::HyperBeam] > 0
mini_score *= (sweeping_stat) ? 1.3 : 1.2
end
# Prefer if target is Encored into a status move
if @target.effects[PBEffects::Encore] > 0 &&
GameData::Move.get(@target.effects[PBEffects::EncoreMove]).category == 2 # Status move
# TODO: Why should this check greatly prefer raising both the user's defences?
if sweeping_stat || @move.function == "02A" # +Def +SpDef
mini_score *= 1.5
else
mini_score *= 1.3
end
end
# TODO: Don't prefer if target has previously used a move that would force
# the user to switch (or Yawn/Perish Song which encourage it). Prefer
# instead if the move raises evasion. Note this comes after the
# dissociation of Bulk Up from sweeping_stat.
if skill_check(AILevel.medium)
# TODO: Prefer if the maximum damage the target has dealt wouldn't hurt
# the user much.
end
# Don't prefer if foe's side is able to use a boosted Retaliate
# TODO: I think this is what Reborn means. Reborn doesn't check for the
# existence of the move Retaliate, just whether it can be boosted.
if @user.pbOpposingSide.effects[PBEffects::LastRoundFainted] == @battle.turnCount - 1
mini_score *= 0.3
end
# Don't prefer if it's not a single battle
if !@battle.singleBattle?
mini_score *= (offensive_stat) ? 0.25 : 0.5
end
return mini_score
end
#=============================================================================
#
#=============================================================================
# TODO: This method doesn't take the increment into account but should.
def calc_user_stat_raise_one(stat, increment)
mini_score = 1.0
# Ignore if user won't benefit from the stat being raised
# TODO: Exception if user knows Baton Pass? Exception if user knows Power Trip?
case stat
when :ATTACK
has_physical_move = false
@user.eachMove do |m|
next if !m.physicalMove?(m.type) || m.function == "121" # Foul Play
has_physical_move = true
break
end
return mini_score if !has_physical_move
when :SPECIAL_ATTACK
has_special_move = false
@user.eachMove do |m|
next if !m.specialMove?(m.type)
has_special_move = true
break
end
return mini_score if !has_special_move
end
case stat
when :ATTACK
# Prefer if user can definitely survive a hit no matter how powerful, and
# it won't be hurt by weather
if @user.hp == @user.totalhp &&
(@user.hasActiveItem?(:FOCUSSASH) || @user.hasActiveAbility?(:STURDY))
if !(@battle.pbWeather == PBWeather::Sandstorm && @user.takesSandstormDamage?) &&
!(@battle.pbWeather == PBWeather::Hail && @user.takesHailDamage?) &&
!(@battle.pbWeather == PBWeather::ShadowSky && @user.takesShadowSkyDamage?)
mini_score *= 1.4
end
end
# Prefer if user has the Sweeper role
# TODO: Is 1.1x for 025 Coil (+Atk, +Def, +acc).
mini_score *= 1.3 if check_battler_role(@user, BattleRole::SWEEPER)
# Don't prefer if user is burned or paralysed
mini_score *= 0.5 if @user.status == PBStatuses::BURN || @user.status == PBStatuses::PARALYSIS
# Don't prefer if user's Speed stat is lowered
sum_stages = @user.stages[:SPEED]
mini_score *= 1 + sum_stages * 0.05 if sum_stages < 0
# TODO: Prefer if target has previously used a HP-restoring move.
# TODO: Don't prefer if some of foes' stats are raised
sum_stages = 0
[:ATTACK, :SPECIAL_ATTACK, :SPEED].each do |s|
sum_stages += @target.stages[s]
end
mini_score *= 1 - sum_stages * 0.05 if sum_stages > 0
# TODO: Don't prefer if target has Speed Boost (+Spd at end of each round).
mini_score *= 0.6 if @target.hasActiveAbility?(:SPEEDBOOST)
# TODO: Don't prefer if target has previously used a move that benefits
# from user's Attack being boosted.
mini_score *= 0.3 if check_for_move(@target) { |move| move.function == "121" } # Foul Play
# TODO: Don't prefer if the target has previously used a priority move.
when :DEFENSE
# Prefer if user has a healing item
# TODO: Is 1.1x for 025 Coil (+Atk, +Def, +acc).
mini_score *= 1.2 if @user.hasActiveItem?(:LEFTOVERS) ||
(@user.hasActiveItem?(:BLACKSLUDGE) && @user.pbHasType?(:POISON))
# Prefer if user knows any healing moves
# TODO: Is 1.2x for 025 Coil (+Atk, +Def, +acc).
mini_score *= 1.3 if check_for_move(@user) { |move| move.healingMove? }
# Prefer if user knows Pain Split or Leech Seed
# TODO: Leech Seed is 1.2x for 025 Coil (+Atk, +Def, +acc).
mini_score *= 1.2 if @user.pbHasMoveFunction?("05A") # Pain Split
mini_score *= 1.3 if @user.pbHasMoveFunction?("0DC") # Leech Seed
# Prefer if user has certain roles
# TODO: Is 1.1x for 025 Coil (+Atk, +Def, +acc).
mini_score *= 1.3 if check_battler_role(@user, BattleRole::PHYSICALWALL, BattleRole::SPECIALWALL)
# Don't prefer if user is badly poisoned
mini_score *= 0.2 if @user.effects[PBEffects::Toxic] > 0
# Don't prefer if user's Defense stat is raised
sum_stages = @user.stages[:DEFENSE]
mini_score *= 1 - sum_stages * 0.15 if sum_stages > 0
# TODO: Prefer if foes have higher Attack than Special Attack, and user
# doesn't have a wall role, user is faster and user has at least 75%
# HP. Don't prefer instead if user is slower (ignore HP).
# TODO: Don't prefer if previous damage done by foes wouldn't hurt the
# user much.
when :SPEED
# Prefer if user can definitely survive a hit no matter how powerful, and
# it won't be hurt by weather
if @user.hp == @user.totalhp &&
(@user.hasActiveItem?(:FOCUSSASH) || @user.hasActiveAbility?(:STURDY))
if !(@battle.pbWeather == PBWeather::Sandstorm && @user.takesSandstormDamage?) &&
!(@battle.pbWeather == PBWeather::Hail && @user.takesHailDamage?) &&
!(@battle.pbWeather == PBWeather::ShadowSky && @user.takesShadowSkyDamage?)
mini_score *= 1.4
end
end
# Prefer if user's Attack/SpAtk stat (whichever is higher) is lowered
# TODO: Why?
if @user.attack > @user.spatk
sum_stages = @user.stages[:ATTACK]
mini_score *= 1 - sum_stages * 0.05 if sum_stages < 0
else
sum_stages = @user.stages[:SPATK]
mini_score *= 1 - sum_stages * 0.05 if sum_stages < 0
end
# Prefer if user has lowered Speed
# TODO: Is a flat 1.3x for 026 Dragon Dance (+Atk, +Spd).
sum_stages = @user.stages[:SPEED]
mini_score *= 1 - sum_stages * 0.05 if sum_stages < 0
# Prefer if user has Moxie
mini_score *= 1.3 if @user.hasActiveAbility?(:MOXIE)
# Prefer if user has the Sweeper role
mini_score *= 1.3 if check_battler_role(@user, BattleRole::SWEEPER)
# Don't prefer if user is burned or paralysed
mini_score *= 0.2 if @user.status == PBStatuses::PARALYSIS
# Don't prefer if user has Speed Boost
mini_score *= 0.6 if @user.hasActiveAbility?(:SPEEDBOOST)
# TODO: Don't prefer if target has raised defenses.
sum_stages = 0
[:DEFENSE, :SPECIAL_DEFENSE].each { |s| sum_stages += @target.stages[s] }
mini_score *= 1 - sum_stages * 0.05 if sum_stages > 0
# TODO: Don't prefer if the target has previously used a priority move.
# TODO: Don't prefer if Trick Room applies or any foe has previously used
# Trick Room.
mini_score *= 0.2 if @battle.field.effects[PBEffects::TrickRoom] > 0
# TODO: Don't prefer if user is already faster than the target. Exception
# for moves that benefit from a raised user's Speed?
# TODO: Don't prefer if user is already faster than the target and there's
# only 1 unfainted foe (this check is done by Agility/Autotomize
# (both +2 Spd) only in Reborn.)
when :SPECIAL_ATTACK
# Prefer if user can definitely survive a hit no matter how powerful, and
# it won't be hurt by weather
if @user.hp == @user.totalhp &&
(@user.hasActiveItem?(:FOCUSSASH) || @user.hasActiveAbility?(:STURDY))
if !(@battle.pbWeather == PBWeather::Sandstorm && @user.takesSandstormDamage?) &&
!(@battle.pbWeather == PBWeather::Hail && @user.takesHailDamage?) &&
!(@battle.pbWeather == PBWeather::ShadowSky && @user.takesShadowSkyDamage?)
mini_score *= 1.4
end
end
# Prefer if user has the Sweeper role
mini_score *= 1.3 if check_battler_role(@user, BattleRole::SWEEPER)
# Don't prefer if user's Speed stat is lowered
sum_stages = @user.stages[:SPEED]
mini_score *= 1 + sum_stages * 0.05 if sum_stages < 0
# TODO: Prefer if target has previously used a HP-restoring move.
# TODO: Don't prefer if some of foes' stats are raised
sum_stages = 0
[:ATTACK, :SPECIAL_ATTACK, :SPEED].each do |s|
sum_stages += @target.stages[s]
end
mini_score *= 1 - sum_stages * 0.05 if sum_stages > 0
# TODO: Don't prefer if target has Speed Boost (+Spd at end of each round)
mini_score *= 0.6 if @target.hasActiveAbility?(:SPEEDBOOST)
# TODO: Don't prefer if the target has previously used a priority move.
when :SPECIAL_DEFENSE
# Prefer if user has a healing item
mini_score *= 1.2 if @user.hasActiveItem?(:LEFTOVERS) ||
(@user.hasActiveItem?(:BLACKSLUDGE) && @user.pbHasType?(:POISON))
# Prefer if user knows any healing moves
mini_score *= 1.3 if check_for_move(@user) { |move| move.healingMove? }
# Prefer if user knows Pain Split or Leech Seed
mini_score *= 1.2 if @user.pbHasMoveFunction?("05A") # Pain Split
mini_score *= 1.3 if @user.pbHasMoveFunction?("0DC") # Leech Seed
# Prefer if user has certain roles
mini_score *= 1.3 if check_battler_role(@user, BattleRole::PHYSICALWALL, BattleRole::SPECIALWALL)
# Don't prefer if user's Defense stat is raised
sum_stages = @user.stages[:SPECIAL_DEFENSE]
mini_score *= 1 - sum_stages * 0.15 if sum_stages > 0
# TODO: Prefer if foes have higher Special Attack than Attack.
# TODO: Don't prefer if previous damage done by foes wouldn't hurt the
# user much.
when :ACCURACY
# Prefer if user knows any weaker moves
mini_score *= 1.1 if check_for_move(@user) { |move| move.damagingMove? && move.basedamage < 95 }
# Prefer if target has a raised evasion
sum_stages = @target.stages[:EVASION]
mini_score *= 1 + sum_stages * 0.05 if sum_stages > 0
# Prefer if target has an item that lowers foes' accuracy
mini_score *= 1.1 if @target.hasActiveItem?([:BRIGHTPOWDER, :LAXINCENSE])
# Prefer if target has an ability that lowers foes' accuracy
# TODO: Tangled Feet while user is confused?
if (@battle.pbWeather == PBWeather::Sandstorm && @target.hasActiveAbility?(:SANDVEIL)) ||
(@battle.pbWeather == PBWeather::Hail && @target.hasActiveAbility?(:SNOWCLOAK))
mini_score *= 1.1
end
when :EVASION
# Prefer if user has a healing item
mini_score *= 1.2 if @user.hasActiveItem?(:LEFTOVERS) ||
(@user.hasActiveItem?(:BLACKSLUDGE) && @user.pbHasType?(:POISON))
# Prefer if user has an item that lowers foes' accuracy
mini_score *= 1.3 if @user.hasActiveItem?([:BRIGHTPOWDER, :LAXINCENSE])
# Prefer if user has an ability that lowers foes' accuracy
# TODO: Tangled Feet while user is confused?
if (@battle.pbWeather == PBWeather::Sandstorm && @user.hasActiveAbility?(:SANDVEIL)) ||
(@battle.pbWeather == PBWeather::Hail && @user.hasActiveAbility?(:SNOWCLOAK))
mini_score *= 1.3
end
# Prefer if user knows any healing moves
mini_score *= 1.3 if check_for_move(@user) { |move| move.healingMove? }
# Prefer if user knows Pain Split or Leech Seed
mini_score *= 1.2 if @user.pbHasMoveFunction?("05A") # Pain Split
mini_score *= 1.3 if @user.pbHasMoveFunction?("0DC") # Leech Seed
# Prefer if user has certain roles
mini_score *= 1.3 if check_battler_role(@user, BattleRole::PHYSICALWALL, BattleRole::SPECIALWALL)
# TODO: Don't prefer if user's evasion stat is raised
# TODO: Don't prefer if target has No Guard.
mini_score *= 0.2 if @target.hasActiveAbility?(:NOGUARD)
# TODO: Don't prefer if target has previously used any moves that never miss.
end
# Don't prefer if user has Contrary
mini_score *= 0.5 if @user.hasActiveAbility?(:CONTRARY)
# TODO: Don't prefer if target has Unaware? Reborn resets mini_score to 1.
# This check needs more consideration. Note that @target is user for
# status moves, so that part is wrong.
# TODO: Is 0x for 025, 026, 026 (all moves that raise multiple stats)
mini_score *= 0.5 if @move.statusMove? && @target.hasActiveAbility?(:UNAWARE)
# TODO: Don't prefer if any foe has previously used a stat stage-clearing
# move (050, 051 Clear Smog/Haze).
mini_score *= 0.3 if check_for_move(@target) { |move| ["050", "051"].include?(move.function) } # Clear Smog, Haze
# TODO: Prefer if user is faster than the target.
# TODO: Is 1.3x for 025 Coil (+Atk, +Def, +acc).
mini_score *= 1.5 if @user_faster
# TODO: Don't prefer if target is a higher level than the user
if @target.level > @user.level + 5
mini_score *= 0.6
if @target.level > @user.level + 10
mini_score *= 0.2
end
end
return mini_score
end
#=============================================================================
#
#=============================================================================
def get_score_for_user_stat_raise(score)
# Discard status move if user has Contrary
return 0 if @move.statusMove? && @user.hasActiveAbility?(:CONTRARY)
# Discard move if it can't raise any stats
can_change_any_stat = false
@move.stat_up.each_with_index do |stat, idx|
next if idx.odd?
next if @user.statStageAtMax?(stat)
can_change_any_stat = true
break
end
if !can_change_any_stat
return (@move.statusMove?) ? 0 : score
end
# Get the main mini-score
main_mini_score = calc_user_stat_raise_mini_score
# For each stat to be raised in turn, calculate a mini-score describing how
# beneficial that stat being raised will be
mini_score = 0
num_stats = 0
@move.stat_up.each_with_index do |stat, idx|
next if idx.odd?
next if @user.statStageAtMax?(stat)
# TODO: Use the effective increment (e.g. 1 if the stat is raised by 2 but
# the stat is already at +5).
mini_score += calc_user_stat_raise_one(stat, @move.stat_up[idx + 1])
num_stats += 1
end
# Apply the average mini-score to the actual score
score = apply_effect_chance_to_score(main_mini_score * mini_score / num_stats)
return score
end
end

View File

@@ -0,0 +1,249 @@
#===============================================================================
#
#===============================================================================
# Struggle
# None
Battle::AI::Handlers::MoveEffectScore.add("DoesNothingCongratulations",
proc { |score, move, user, target, skill, ai, battle|
next 0 if ai.skill_check(Battle::AI::AILevel.high)
next score - 95
}
)
Battle::AI::Handlers::MoveEffectScore.copy("DoesNothingCongratulations",
"DoesNothingFailsIfNoAlly",
"DoesNothingUnusableInGravity",
"DoubleMoneyGainedFromBattle")
# AddMoneyGainedFromBattle
Battle::AI::Handlers::MoveEffectScore.add("FailsIfNotUserFirstTurn",
proc { |score, move, user, target, skill, ai, battle|
next score - 90 if user.turnCount > 0
}
)
# FailsIfUserHasUnusedMove
Battle::AI::Handlers::MoveEffectScore.add("FailsIfUserNotConsumedBerry",
proc { |score, move, user, target, skill, ai, battle|
next score - 90 if !user.belched?
}
)
Battle::AI::Handlers::MoveEffectScore.add("FailsIfTargetHasNoItem",
proc { |score, move, user, target, skill, ai, battle|
if ai.skill_check(Battle::AI::AILevel.medium)
next score - 90 if !target.item || !target.itemActive?
next score + 50
end
}
)
Battle::AI::Handlers::MoveEffectScore.add("FailsUnlessTargetSharesTypeWithUser",
proc { |score, move, user, target, skill, ai, battle|
if !(user.types[0] && target.pbHasType?(user.types[0])) &&
!(user.types[1] && target.pbHasType?(user.types[1]))
next score - 90
end
}
)
Battle::AI::Handlers::MoveEffectScore.add("FailsIfUserDamagedThisTurn",
proc { |score, move, user, target, skill, ai, battle|
score += 50 if target.effects[PBEffects::HyperBeam] > 0
score -= 35 if target.hp <= target.totalhp / 2 # If target is weak, no
score -= 70 if target.hp <= target.totalhp / 4 # need to risk this move
next score
}
)
# FailsIfTargetActed
Battle::AI::Handlers::MoveEffectScore.add("CrashDamageIfFailsUnusableInGravity",
proc { |score, move, user, target, skill, ai, battle|
next score + 10 * (user.stages[:ACCURACY] - target.stages[:EVASION])
}
)
Battle::AI::Handlers::MoveEffectScore.add("StartSunWeather",
proc { |score, move, user, target, skill, ai, battle|
if battle.pbCheckGlobalAbility(:AIRLOCK) ||
battle.pbCheckGlobalAbility(:CLOUDNINE)
next score - 90
elsif battle.field.weather == :Sun
next score - 90
else
user.eachMove do |m|
next if !m.damagingMove? || m.type != :FIRE
score += 20
end
next score
end
}
)
Battle::AI::Handlers::MoveEffectScore.add("StartRainWeather",
proc { |score, move, user, target, skill, ai, battle|
if battle.pbCheckGlobalAbility(:AIRLOCK) ||
battle.pbCheckGlobalAbility(:CLOUDNINE)
next score - 90
elsif battle.field.weather == :Rain
next score - 90
else
user.eachMove do |m|
next if !m.damagingMove? || m.type != :WATER
score += 20
end
next score
end
}
)
Battle::AI::Handlers::MoveEffectScore.add("StartSandstormWeather",
proc { |score, move, user, target, skill, ai, battle|
if battle.pbCheckGlobalAbility(:AIRLOCK) ||
battle.pbCheckGlobalAbility(:CLOUDNINE)
next score - 90
elsif battle.field.weather == :Rain
next score - 90
end
}
)
Battle::AI::Handlers::MoveEffectScore.add("StartHailWeather",
proc { |score, move, user, target, skill, ai, battle|
if battle.pbCheckGlobalAbility(:AIRLOCK) ||
battle.pbCheckGlobalAbility(:CLOUDNINE)
next score - 90
elsif battle.field.weather == :Hail
next score - 90
end
}
)
# StartElectricTerrain
# StartGrassyTerrain
# StartMistyTerrain
# StartPsychicTerrain
Battle::AI::Handlers::MoveEffectScore.add("RemoveTerrain",
proc { |score, move, user, target, skill, ai, battle|
next 0 if battle.field.terrain == :None
}
)
Battle::AI::Handlers::MoveEffectScore.add("AddSpikesToFoeSide",
proc { |score, move, user, target, skill, ai, battle|
if user.pbOpposingSide.effects[PBEffects::Spikes] >= 3
next score - 90
elsif user.allOpposing.none? { |b| battle.pbCanChooseNonActive?(b.index) }
next score - 90 # Opponent can't switch in any Pokemon
else
score += 10 * battle.pbAbleNonActiveCount(user.idxOpposingSide)
score += [40, 26, 13][user.pbOpposingSide.effects[PBEffects::Spikes]]
next score
end
}
)
Battle::AI::Handlers::MoveEffectScore.add("AddToxicSpikesToFoeSide",
proc { |score, move, user, target, skill, ai, battle|
if user.pbOpposingSide.effects[PBEffects::ToxicSpikes] >= 2
next score - 90
elsif user.allOpposing.none? { |b| battle.pbCanChooseNonActive?(b.index) }
next score - 90 # Opponent can't switch in any Pokemon
else
score += 8 * battle.pbAbleNonActiveCount(user.idxOpposingSide)
score += [26, 13][user.pbOpposingSide.effects[PBEffects::ToxicSpikes]]
next score
end
}
)
Battle::AI::Handlers::MoveEffectScore.add("AddStealthRocksToFoeSide",
proc { |score, move, user, target, skill, ai, battle|
if user.pbOpposingSide.effects[PBEffects::StealthRock]
next score - 90
elsif user.allOpposing.none? { |b| battle.pbCanChooseNonActive?(b.index) }
next score - 90 # Opponent can't switch in any Pokemon
else
next score + 10 * battle.pbAbleNonActiveCount(user.idxOpposingSide)
end
}
)
Battle::AI::Handlers::MoveEffectScore.add("AddStickyWebToFoeSide",
proc { |score, move, user, target, skill, ai, battle|
next score - 95 if user.pbOpposingSide.effects[PBEffects::StickyWeb]
}
)
Battle::AI::Handlers::MoveEffectScore.add("SwapSideEffects",
proc { |score, move, user, target, skill, ai, battle|
if ai.skill_check(Battle::AI::AILevel.medium)
good_effects = [:Reflect, :LightScreen, :AuroraVeil, :SeaOfFire,
:Swamp, :Rainbow, :Mist, :Safeguard,
:Tailwind].map! { |e| PBEffects.const_get(e) }
bad_effects = [:Spikes, :StickyWeb, :ToxicSpikes, :StealthRock].map! { |e| PBEffects.const_get(e) }
bad_effects.each do |e|
score += 10 if ![0, false, nil].include?(user.pbOwnSide.effects[e])
score -= 10 if ![0, 1, false, nil].include?(user.pbOpposingSide.effects[e])
end
if ai.skill_check(Battle::AI::AILevel.high)
good_effects.each do |e|
score += 10 if ![0, 1, false, nil].include?(user.pbOpposingSide.effects[e])
score -= 10 if ![0, false, nil].include?(user.pbOwnSide.effects[e])
end
end
next score
end
}
)
Battle::AI::Handlers::MoveEffectScore.add("UserMakeSubstitute",
proc { |score, move, user, target, skill, ai, battle|
if user.effects[PBEffects::Substitute] > 0
next score - 90
elsif user.hp <= user.totalhp / 4
next score - 90
end
}
)
Battle::AI::Handlers::MoveEffectScore.add("RemoveUserBindingAndEntryHazards",
proc { |score, move, user, target, skill, ai, battle|
score += 30 if user.effects[PBEffects::Trapping] > 0
score += 30 if user.effects[PBEffects::LeechSeed] >= 0
if battle.pbAbleNonActiveCount(user.idxOwnSide) > 0
score += 80 if user.pbOwnSide.effects[PBEffects::Spikes] > 0
score += 80 if user.pbOwnSide.effects[PBEffects::ToxicSpikes] > 0
score += 80 if user.pbOwnSide.effects[PBEffects::StealthRock]
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("AttackTwoTurnsLater",
proc { |score, move, user, target, skill, ai, battle|
if battle.positions[target.index].effects[PBEffects::FutureSightCounter] > 0
next 0
elsif battle.pbAbleNonActiveCount(user.idxOwnSide) == 0
# Future Sight tends to be wasteful if down to last Pokemon
next score - 70
end
}
)
# UserSwapsPositionsWithAlly
Battle::AI::Handlers::MoveEffectScore.add("BurnAttackerBeforeUserActs",
proc { |score, move, user, target, skill, ai, battle|
next score + 20 # Because of possible burning
}
)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,683 @@
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveEffectScore.add("SleepTarget",
proc { |score, move, user, target, skill, ai, battle|
if target.pbCanSleep?(user, false)
score += 30
if ai.skill_check(Battle::AI::AILevel.medium)
score -= 30 if target.effects[PBEffects::Yawn] > 0
end
if ai.skill_check(Battle::AI::AILevel.high)
score -= 30 if target.hasActiveAbility?(:MARVELSCALE)
end
if ai.skill_check(Battle::AI::AILevel.best)
if target.pbHasMoveFunction?("FlinchTargetFailsIfUserNotAsleep",
"UseRandomUserMoveIfAsleep") # Snore, Sleep Talk
score -= 50
end
end
else
next 0 if move.statusMove?
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.copy("SleepTarget",
"SleepTargetIfUserDarkrai",
"SleepTargetChangeUserMeloettaForm")
Battle::AI::Handlers::MoveEffectScore.add("SleepTargetNextTurn",
proc { |score, move, user, target, skill, ai, battle|
next 0 if target.effects[PBEffects::Yawn] > 0 || !target.pbCanSleep?(user, false)
score += 30
if ai.skill_check(Battle::AI::AILevel.high)
score -= 30 if target.hasActiveAbility?(:MARVELSCALE)
end
if ai.skill_check(Battle::AI::AILevel.best)
if target.pbHasMoveFunction?("FlinchTargetFailsIfUserNotAsleep",
"UseRandomUserMoveIfAsleep") # Snore, Sleep Talk
score -= 50
end
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("PoisonTarget",
proc { |score, move, user, target, skill, ai, battle|
if target.pbCanPoison?(user, false)
score += 30
if ai.skill_check(Battle::AI::AILevel.medium)
score += 30 if target.hp <= target.totalhp / 4
score += 50 if target.hp <= target.totalhp / 8
score -= 40 if target.effects[PBEffects::Yawn] > 0
end
if ai.skill_check(Battle::AI::AILevel.high)
score += 10 if pbRoughStat(target, :DEFENSE) > 100
score += 10 if pbRoughStat(target, :SPECIAL_DEFENSE) > 100
score -= 40 if target.hasActiveAbility?([:GUTS, :MARVELSCALE, :TOXICBOOST])
end
else
next 0 if move.statusMove?
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("PoisonTargetLowerTargetSpeed1",
proc { |score, move, user, target, skill, ai, battle|
next 0 if !target.pbCanPoison?(user, false) && !target.pbCanLowerStatStage?(:SPEED, user)
if target.pbCanPoison?(user, false)
score += 30
if ai.skill_check(Battle::AI::AILevel.medium)
score += 30 if target.hp <= target.totalhp / 4
score += 50 if target.hp <= target.totalhp / 8
score -= 40 if target.effects[PBEffects::Yawn] > 0
end
if ai.skill_check(Battle::AI::AILevel.high)
score += 10 if pbRoughStat(target, :DEFENSE) > 100
score += 10 if pbRoughStat(target, :SPECIAL_DEFENSE) > 100
score -= 40 if target.hasActiveAbility?([:GUTS, :MARVELSCALE, :TOXICBOOST])
end
end
if target.pbCanLowerStatStage?(:SPEED, user)
score += target.stages[:SPEED] * 10
if ai.skill_check(Battle::AI::AILevel.high)
aspeed = pbRoughStat(user, :SPEED)
ospeed = pbRoughStat(target, :SPEED)
score += 30 if aspeed < ospeed && aspeed * 2 > ospeed
end
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("BadPoisonTarget",
proc { |score, move, user, target, skill, ai, battle|
if target.pbCanPoison?(user, false)
score += 30
if ai.skill_check(Battle::AI::AILevel.medium)
score += 30 if target.hp <= target.totalhp / 4
score += 50 if target.hp <= target.totalhp / 8
score -= 40 if target.effects[PBEffects::Yawn] > 0
end
if ai.skill_check(Battle::AI::AILevel.high)
score += 10 if pbRoughStat(target, :DEFENSE) > 100
score += 10 if pbRoughStat(target, :SPECIAL_DEFENSE) > 100
score -= 40 if target.hasActiveAbility?([:GUTS, :MARVELSCALE, :TOXICBOOST])
end
else
score -= 90 if move.statusMove?
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("ParalyzeTarget",
proc { |score, move, user, target, skill, ai, battle|
if target.pbCanParalyze?(user, false) &&
!(ai.skill_check(Battle::AI::AILevel.medium) &&
move.id == :THUNDERWAVE &&
Effectiveness.ineffective?(pbCalcTypeMod(move.type, user, target)))
score += 30
if ai.skill_check(Battle::AI::AILevel.medium)
aspeed = pbRoughStat(user, :SPEED)
ospeed = pbRoughStat(target, :SPEED)
if aspeed < ospeed
score += 30
elsif aspeed > ospeed
score -= 40
end
end
if ai.skill_check(Battle::AI::AILevel.high)
score -= 40 if target.hasActiveAbility?([:GUTS, :MARVELSCALE, :QUICKFEET])
end
else
score -= 90 if move.statusMove?
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.copy("ParalyzeTarget",
"ParalyzeTargetIfNotTypeImmune",
"ParalyzeTargetAlwaysHitsInRainHitsTargetInSky",
"ParalyzeFlinchTarget")
Battle::AI::Handlers::MoveEffectScore.add("BurnTarget",
proc { |score, move, user, target, skill, ai, battle|
if target.pbCanBurn?(user, false)
score += 30
if ai.skill_check(Battle::AI::AILevel.high)
score -= 40 if target.hasActiveAbility?([:GUTS, :MARVELSCALE, :QUICKFEET, :FLAREBOOST])
end
else
score -= 90 if move.statusMove?
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.copy("BurnTarget",
"BurnTargetIfTargetStatsRaisedThisTurn",
"BurnFlinchTarget")
Battle::AI::Handlers::MoveEffectScore.add("FreezeTarget",
proc { |score, move, user, target, skill, ai, battle|
if target.pbCanFreeze?(user, false)
score += 30
if ai.skill_check(Battle::AI::AILevel.high)
score -= 20 if target.hasActiveAbility?(:MARVELSCALE)
end
else
score -= 90 if move.statusMove?
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("FreezeTargetSuperEffectiveAgainstWater",
proc { |score, move, user, target, skill, ai, battle|
if target.pbCanFreeze?(user, false)
score += 30
if ai.skill_check(Battle::AI::AILevel.high)
score -= 20 if target.hasActiveAbility?(:MARVELSCALE)
end
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("FreezeTargetAlwaysHitsInHail",
proc { |score, move, user, target, skill, ai, battle|
if target.pbCanFreeze?(user, false)
score += 30
if ai.skill_check(Battle::AI::AILevel.high)
score -= 20 if target.hasActiveAbility?(:MARVELSCALE)
end
else
score -= 90 if move.statusMove?
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.copy("FreezeTargetAlwaysHitsInHail",
"FreezeFlinchTarget")
Battle::AI::Handlers::MoveEffectScore.add("ParalyzeBurnOrFreezeTarget",
proc { |score, move, user, target, skill, ai, battle|
next score + 30 if target.status == :NONE
}
)
Battle::AI::Handlers::MoveEffectScore.add("GiveUserStatusToTarget",
proc { |score, move, user, target, skill, ai, battle|
next 0 if user.status == :NONE
next score + 40
}
)
Battle::AI::Handlers::MoveEffectScore.add("CureUserBurnPoisonParalysis",
proc { |score, move, user, target, skill, ai, battle|
case user.status
when :POISON
score += 40
if ai.skill_check(Battle::AI::AILevel.medium)
if user.hp < user.totalhp / 8
score += 60
elsif ai.skill_check(Battle::AI::AILevel.high) &&
user.hp < (user.effects[PBEffects::Toxic] + 1) * user.totalhp / 16
score += 60
end
end
when :BURN, :PARALYSIS
score += 40
else
score -= 90
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("CureUserPartyStatus",
proc { |score, move, user, target, skill, ai, battle|
statuses = 0
battle.pbParty(user.index).each do |pkmn|
statuses += 1 if pkmn && pkmn.status != :NONE
end
if statuses == 0
score -= 80
else
score += 20 * statuses
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("CureTargetBurn",
proc { |score, move, user, target, skill, ai, battle|
if target.opposes?(user)
score -= 40 if target.status == :BURN
elsif target.status == :BURN
score += 40
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("StartUserSideImmunityToInflictedStatus",
proc { |score, move, user, target, skill, ai, battle|
if user.pbOwnSide.effects[PBEffects::Safeguard] > 0
score -= 80
elsif user.status != :NONE
score -= 40
else
score += 30
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("FlinchTarget",
proc { |score, move, user, target, skill, ai, battle|
score += 30
if ai.skill_check(Battle::AI::AILevel.high)
score += 30 if !target.hasActiveAbility?(:INNERFOCUS) &&
target.effects[PBEffects::Substitute] == 0
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("FlinchTargetFailsIfUserNotAsleep",
proc { |score, move, user, target, skill, ai, battle|
next 0 if !user.asleep?
score += 100 # Because it can only be used while asleep
if ai.skill_check(Battle::AI::AILevel.high)
score += 30 if !target.hasActiveAbility?(:INNERFOCUS) &&
target.effects[PBEffects::Substitute] == 0
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("FlinchTargetFailsIfNotUserFirstTurn",
proc { |score, move, user, target, skill, ai, battle|
next 0 if user.turnCount != 0
if ai.skill_check(Battle::AI::AILevel.high)
score += 30 if !target.hasActiveAbility?(:INNERFOCUS) &&
target.effects[PBEffects::Substitute] == 0
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("FlinchTargetDoublePowerIfTargetInSky",
proc { |score, move, user, target, skill, ai, battle|
if ai.skill_check(Battle::AI::AILevel.high)
score += 30 if !target.hasActiveAbility?(:INNERFOCUS) &&
target.effects[PBEffects::Substitute] == 0
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("ConfuseTarget",
proc { |score, move, user, target, skill, ai, battle|
next 0 if !target.pbCanConfuse?(user, false)
next score + 30
}
)
Battle::AI::Handlers::MoveEffectScore.copy("ConfuseTarget",
"ConfuseTargetAlwaysHitsInRainHitsTargetInSky")
Battle::AI::Handlers::MoveEffectScore.add("AttractTarget",
proc { |score, move, user, target, skill, ai, battle|
canattract = true
agender = user.gender
ogender = target.gender
if agender == 2 || ogender == 2 || agender == ogender
score -= 90
canattract = false
elsif target.effects[PBEffects::Attract] >= 0
score -= 80
canattract = false
elsif ai.skill_check(Battle::AI::AILevel.best) && target.hasActiveAbility?(:OBLIVIOUS)
score -= 80
canattract = false
end
if ai.skill_check(Battle::AI::AILevel.high)
if canattract && target.hasActiveItem?(:DESTINYKNOT) &&
user.pbCanAttract?(target, false)
score -= 30
end
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("SetUserTypesBasedOnEnvironment",
proc { |score, move, user, target, skill, ai, battle|
if !user.canChangeType?
score -= 90
elsif ai.skill_check(Battle::AI::AILevel.medium)
new_type = nil
case battle.field.terrain
when :Electric
new_type = :ELECTRIC if GameData::Type.exists?(:ELECTRIC)
when :Grassy
new_type = :GRASS if GameData::Type.exists?(:GRASS)
when :Misty
new_type = :FAIRY if GameData::Type.exists?(:FAIRY)
when :Psychic
new_type = :PSYCHIC if GameData::Type.exists?(:PSYCHIC)
end
if !new_type
envtypes = {
:None => :NORMAL,
:Grass => :GRASS,
:TallGrass => :GRASS,
:MovingWater => :WATER,
:StillWater => :WATER,
:Puddle => :WATER,
:Underwater => :WATER,
:Cave => :ROCK,
:Rock => :GROUND,
:Sand => :GROUND,
:Forest => :BUG,
:ForestGrass => :BUG,
:Snow => :ICE,
:Ice => :ICE,
:Volcano => :FIRE,
:Graveyard => :GHOST,
:Sky => :FLYING,
:Space => :DRAGON,
:UltraSpace => :PSYCHIC
}
new_type = envtypes[battle.environment]
new_type = nil if !GameData::Type.exists?(new_type)
new_type ||= :NORMAL
end
score -= 90 if !user.pbHasOtherType?(new_type)
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("SetUserTypesToResistLastAttack",
proc { |score, move, user, target, skill, ai, battle|
next 0 if !user.canChangeType?
next 0 if !target.lastMoveUsed || !target.lastMoveUsedType ||
GameData::Type.get(target.lastMoveUsedType).pseudo_type
aType = nil
target.eachMove do |m|
next if m.id != target.lastMoveUsed
aType = m.pbCalcType(user)
break
end
next 0 if !aType
has_possible_type = false
GameData::Type.each do |t|
next if t.pseudo_type || user.pbHasType?(t.id) ||
!Effectiveness.resistant_type?(target.lastMoveUsedType, t.id)
has_possible_type = true
break
end
next 0 if !has_possible_type
}
)
Battle::AI::Handlers::MoveEffectScore.add("SetUserTypesToTargetTypes",
proc { |score, move, user, target, skill, ai, battle|
next 0 if !user.canChangeType? || target.pbTypes(true).length == 0
next 0 if user.pbTypes == target.pbTypes &&
user.effects[PBEffects::Type3] == target.effects[PBEffects::Type3]
}
)
Battle::AI::Handlers::MoveEffectScore.add("SetUserTypesToUserMoveType",
proc { |score, move, user, target, skill, ai, battle|
next 0 if !user.canChangeType?
has_possible_type = false
user.eachMoveWithIndex do |m, i|
break if Settings::MECHANICS_GENERATION >= 6 && i > 0
next if GameData::Type.get(m.type).pseudo_type
next if user.pbHasType?(m.type)
has_possible_type = true
break
end
next 0 if !has_possible_type
}
)
Battle::AI::Handlers::MoveEffectScore.add("SetTargetTypesToPsychic",
proc { |score, move, user, target, skill, ai, battle|
if target.pbHasOtherType?(:PSYCHIC)
score -= 90
elsif !target.canChangeType?
score -= 90
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("SetTargetTypesToWater",
proc { |score, move, user, target, skill, ai, battle|
if target.effects[PBEffects::Substitute] > 0 || !target.canChangeType?
score -= 90
elsif !target.pbHasOtherType?(:WATER)
score -= 90
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("AddGhostTypeToTarget",
proc { |score, move, user, target, skill, ai, battle|
next 0 if target.pbHasType?(:GHOST)
}
)
Battle::AI::Handlers::MoveEffectScore.add("AddGrassTypeToTarget",
proc { |score, move, user, target, skill, ai, battle|
next 0 if target.pbHasType?(:GRASS)
}
)
Battle::AI::Handlers::MoveEffectScore.add("UserLosesFireType",
proc { |score, move, user, target, skill, ai, battle|
next 0 if !user.pbHasType?(:FIRE)
}
)
Battle::AI::Handlers::MoveEffectScore.add("SetTargetAbilityToSimple",
proc { |score, move, user, target, skill, ai, battle|
next 0 if target.effects[PBEffects::Substitute] > 0
if ai.skill_check(Battle::AI::AILevel.medium)
next 0 if target.unstoppableAbility? ||
[:TRUANT, :SIMPLE].include?(target.ability)
end
}
)
Battle::AI::Handlers::MoveEffectScore.add("SetTargetAbilityToInsomnia",
proc { |score, move, user, target, skill, ai, battle|
next 0 if target.effects[PBEffects::Substitute] > 0
if ai.skill_check(Battle::AI::AILevel.medium)
next 0 if target.unstoppableAbility? ||
[:TRUANT, :INSOMNIA].include?(target.ability)
end
}
)
Battle::AI::Handlers::MoveEffectScore.add("SetUserAbilityToTargetAbility",
proc { |score, move, user, target, skill, ai, battle|
score -= 40 # don't prefer this move
if ai.skill_check(Battle::AI::AILevel.medium)
if !target.ability || user.ability == target.ability ||
[:MULTITYPE, :RKSSYSTEM].include?(user.ability_id) ||
[:FLOWERGIFT, :FORECAST, :ILLUSION, :IMPOSTER, :MULTITYPE, :RKSSYSTEM,
:TRACE, :WONDERGUARD, :ZENMODE].include?(target.ability_id)
score -= 90
end
end
if ai.skill_check(Battle::AI::AILevel.high)
if target.ability == :TRUANT && user.opposes?(target)
score -= 90
elsif target.ability == :SLOWSTART && user.opposes?(target)
score -= 90
end
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("SetTargetAbilityToUserAbility",
proc { |score, move, user, target, skill, ai, battle|
score -= 40 # don't prefer this move
if target.effects[PBEffects::Substitute] > 0
score -= 90
elsif ai.skill_check(Battle::AI::AILevel.medium)
if !user.ability || user.ability == target.ability ||
[:MULTITYPE, :RKSSYSTEM, :TRUANT].include?(target.ability_id) ||
[:FLOWERGIFT, :FORECAST, :ILLUSION, :IMPOSTER, :MULTITYPE, :RKSSYSTEM,
:TRACE, :ZENMODE].include?(user.ability_id)
score -= 90
end
if ai.skill_check(Battle::AI::AILevel.high)
if user.ability == :TRUANT && user.opposes?(target)
score += 90
elsif user.ability == :SLOWSTART && user.opposes?(target)
score += 90
end
end
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("UserTargetSwapAbilities",
proc { |score, move, user, target, skill, ai, battle|
score -= 40 # don't prefer this move
if ai.skill_check(Battle::AI::AILevel.medium)
if (!user.ability && !target.ability) ||
user.ability == target.ability ||
[:ILLUSION, :MULTITYPE, :RKSSYSTEM, :WONDERGUARD].include?(user.ability_id) ||
[:ILLUSION, :MULTITYPE, :RKSSYSTEM, :WONDERGUARD].include?(target.ability_id)
score -= 90
end
end
if ai.skill_check(Battle::AI::AILevel.high)
if target.ability == :TRUANT && user.opposes?(target)
score -= 90
elsif target.ability == :SLOWSTART && user.opposes?(target)
score -= 90
end
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("NegateTargetAbility",
proc { |score, move, user, target, skill, ai, battle|
if target.effects[PBEffects::Substitute] > 0 ||
target.effects[PBEffects::GastroAcid]
score -= 90
elsif ai.skill_check(Battle::AI::AILevel.high)
score -= 90 if [:MULTITYPE, :RKSSYSTEM, :SLOWSTART, :TRUANT].include?(target.ability_id)
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("NegateTargetAbilityIfTargetActed",
proc { |score, move, user, target, skill, ai, battle|
if ai.skill_check(Battle::AI::AILevel.medium)
userSpeed = pbRoughStat(user, :SPEED)
targetSpeed = pbRoughStat(target, :SPEED)
if userSpeed < targetSpeed
score += 30
end
else
score += 30
end
next score
}
)
# IgnoreTargetAbility
Battle::AI::Handlers::MoveEffectScore.add("StartUserAirborne",
proc { |score, move, user, target, skill, ai, battle|
if user.effects[PBEffects::MagnetRise] > 0 ||
user.effects[PBEffects::Ingrain] ||
user.effects[PBEffects::SmackDown]
score -= 90
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("StartTargetAirborneAndAlwaysHitByMoves",
proc { |score, move, user, target, skill, ai, battle|
if target.effects[PBEffects::Telekinesis] > 0 ||
target.effects[PBEffects::Ingrain] ||
target.effects[PBEffects::SmackDown]
score -= 90
end
next score
}
)
# HitsTargetInSky
Battle::AI::Handlers::MoveEffectScore.add("HitsTargetInSkyGroundsTarget",
proc { |score, move, user, target, skill, ai, battle|
if ai.skill_check(Battle::AI::AILevel.medium)
score += 20 if target.effects[PBEffects::MagnetRise] > 0
score += 20 if target.effects[PBEffects::Telekinesis] > 0
score += 20 if target.inTwoTurnAttack?("TwoTurnAttackInvulnerableInSky",
"TwoTurnAttackInvulnerableInSkyParalyzeTarget")
score += 20 if target.pbHasType?(:FLYING)
score += 20 if target.hasActiveAbility?(:LEVITATE)
score += 20 if target.hasActiveItem?(:AIRBALLOON)
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("StartGravity",
proc { |score, move, user, target, skill, ai, battle|
if battle.field.effects[PBEffects::Gravity] > 0
score -= 90
elsif ai.skill_check(Battle::AI::AILevel.medium)
score -= 30
score -= 20 if user.effects[PBEffects::SkyDrop] >= 0
score -= 20 if user.effects[PBEffects::MagnetRise] > 0
score -= 20 if user.effects[PBEffects::Telekinesis] > 0
score -= 20 if user.pbHasType?(:FLYING)
score -= 20 if user.hasActiveAbility?(:LEVITATE)
score -= 20 if user.hasActiveItem?(:AIRBALLOON)
score += 20 if target.effects[PBEffects::SkyDrop] >= 0
score += 20 if target.effects[PBEffects::MagnetRise] > 0
score += 20 if target.effects[PBEffects::Telekinesis] > 0
score += 20 if target.inTwoTurnAttack?("TwoTurnAttackInvulnerableInSky",
"TwoTurnAttackInvulnerableInSkyParalyzeTarget",
"TwoTurnAttackInvulnerableInSkyTargetCannotAct")
score += 20 if target.pbHasType?(:FLYING)
score += 20 if target.hasActiveAbility?(:LEVITATE)
score += 20 if target.hasActiveItem?(:AIRBALLOON)
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("TransformUserIntoTarget",
proc { |score, move, user, target, skill, ai, battle|
next score - 70
}
)

View File

@@ -0,0 +1,492 @@
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveEffectScore.add("FixedDamage20",
proc { |score, move, user, target, skill, ai, battle|
if target.hp <= 20
score += 80
elsif target.level >= 25
score -= 60 # Not useful against high-level Pokemon
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("FixedDamage40",
proc { |score, move, user, target, skill, ai, battle|
next score + 80 if target.hp <= 40
}
)
Battle::AI::Handlers::MoveEffectScore.add("FixedDamageHalfTargetHP",
proc { |score, move, user, target, skill, ai, battle|
score -= 50
next score + target.hp * 100 / target.totalhp
}
)
Battle::AI::Handlers::MoveEffectScore.add("FixedDamageUserLevel",
proc { |score, move, user, target, skill, ai, battle|
next score + 80 if target.hp <= user.level
}
)
Battle::AI::Handlers::MoveEffectScore.add("FixedDamageUserLevelRandom",
proc { |score, move, user, target, skill, ai, battle|
next score + 30 if target.hp <= user.level
}
)
Battle::AI::Handlers::MoveEffectScore.add("LowerTargetHPToUserHP",
proc { |score, move, user, target, skill, ai, battle|
if user.hp >= target.hp
score -= 90
elsif user.hp < target.hp / 2
score += 50
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("OHKO",
proc { |score, move, user, target, skill, ai, battle|
next 0 if target.hasActiveAbility?(:STURDY)
next 0 if target.level > user.level
}
)
Battle::AI::Handlers::MoveEffectScore.copy("OHKO",
"OHKOIce",
"OHKOHitsUndergroundTarget")
Battle::AI::Handlers::MoveEffectScore.add("DamageTargetAlly",
proc { |score, move, user, target, skill, ai, battle|
target.allAllies.each do |b|
next if !b.near?(target)
score += 10
end
next score
}
)
# PowerHigherWithUserHP
# PowerLowerWithUserHP
# PowerHigherWithTargetHP
# PowerHigherWithUserHappiness
# PowerLowerWithUserHappiness
# PowerHigherWithUserPositiveStatStages
# PowerHigherWithTargetPositiveStatStages
# PowerHigherWithUserFasterThanTarget
# PowerHigherWithTargetFasterThanUser
# PowerHigherWithLessPP
# PowerHigherWithTargetWeight
# PowerHigherWithUserHeavierThanTarget
# PowerHigherWithConsecutiveUse
# PowerHigherWithConsecutiveUseOnUserSide
# RandomPowerDoublePowerIfTargetUnderground
# DoublePowerIfTargetHPLessThanHalf
# DoublePowerIfUserPoisonedBurnedParalyzed
Battle::AI::Handlers::MoveEffectScore.add("DoublePowerIfTargetAsleepCureTarget",
proc { |score, move, user, target, skill, ai, battle|
next score - 20 if target.status == :SLEEP && # Will cure status
target.statusCount > 1
}
)
# DoublePowerIfTargetPoisoned
Battle::AI::Handlers::MoveEffectScore.add("DoublePowerIfTargetParalyzedCureTarget",
proc { |score, move, user, target, skill, ai, battle|
next score - 20 if target.status == :PARALYSIS # Will cure status
}
)
# DoublePowerIfTargetStatusProblem
# DoublePowerIfUserHasNoItem
# DoublePowerIfTargetUnderwater
# DoublePowerIfTargetUnderground
# DoublePowerIfTargetInSky
Battle::AI::Handlers::MoveEffectScore.add("DoublePowerInElectricTerrain",
proc { |score, move, user, target, skill, ai, battle|
next score + 40 if battle.field.terrain == :Electric && target.affectedByTerrain?
}
)
# DoublePowerIfUserLastMoveFailed
# DoublePowerIfAllyFaintedLastTurn
Battle::AI::Handlers::MoveEffectScore.add("DoublePowerIfUserLostHPThisTurn",
proc { |score, move, user, target, skill, ai, battle|
attspeed = pbRoughStat(user, :SPEED)
oppspeed = pbRoughStat(target, :SPEED)
next score + 30 if oppspeed > attspeed
}
)
Battle::AI::Handlers::MoveEffectScore.add("DoublePowerIfTargetLostHPThisTurn",
proc { |score, move, user, target, skill, ai, battle|
next score + 20 if battle.pbOpposingBattlerCount(user) > 1
}
)
# DoublePowerIfUserStatsLoweredThisTurn
Battle::AI::Handlers::MoveEffectScore.add("DoublePowerIfTargetActed",
proc { |score, move, user, target, skill, ai, battle|
attspeed = pbRoughStat(user, :SPEED)
oppspeed = pbRoughStat(target, :SPEED)
next score + 30 if oppspeed > attspeed
}
)
# DoublePowerIfTargetNotActed
# AlwaysCriticalHit
Battle::AI::Handlers::MoveEffectScore.add("EnsureNextCriticalHit",
proc { |score, move, user, target, skill, ai, battle|
if user.effects[PBEffects::LaserFocus] > 0
score -= 90
else
score += 40
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("EnsureNextCriticalHit",
proc { |score, move, user, target, skill, ai, battle|
next 0 if user.pbOwnSide.effects[PBEffects::LuckyChant] > 0
}
)
Battle::AI::Handlers::MoveEffectScore.add("CannotMakeTargetFaint",
proc { |score, move, user, target, skill, ai, battle|
next 0 if target.hp == 1
if target.hp <= target.totalhp / 8
score -= 60
elsif target.hp <= target.totalhp / 4
score -= 30
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("UserEnduresFaintingThisTurn",
proc { |score, move, user, target, skill, ai, battle|
score -= 25 if user.hp > user.totalhp / 2
if ai.skill_check(Battle::AI::AILevel.medium)
score -= 90 if user.effects[PBEffects::ProtectRate] > 1
score -= 90 if target.effects[PBEffects::HyperBeam] > 0
else
score -= user.effects[PBEffects::ProtectRate] * 40
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("StartWeakenElectricMoves",
proc { |score, move, user, target, skill, ai, battle|
next 0 if user.effects[PBEffects::MudSport]
}
)
Battle::AI::Handlers::MoveEffectScore.add("StartWeakenFireMoves",
proc { |score, move, user, target, skill, ai, battle|
next 0 if user.effects[PBEffects::WaterSport]
}
)
Battle::AI::Handlers::MoveEffectScore.add("StartWeakenPhysicalDamageAgainstUserSide",
proc { |score, move, user, target, skill, ai, battle|
next 0 if user.pbOwnSide.effects[PBEffects::Reflect] > 0
}
)
Battle::AI::Handlers::MoveEffectScore.add("StartWeakenSpecialDamageAgainstUserSide",
proc { |score, move, user, target, skill, ai, battle|
next 0 if user.pbOwnSide.effects[PBEffects::LightScreen] > 0
}
)
Battle::AI::Handlers::MoveEffectScore.add("StartWeakenDamageAgainstUserSideIfHail",
proc { |score, move, user, target, skill, ai, battle|
next 0 if user.pbOwnSide.effects[PBEffects::AuroraVeil] > 0 || user.effectiveWeather != :Hail
next score + 40
}
)
Battle::AI::Handlers::MoveEffectScore.add("RemoveScreens",
proc { |score, move, user, target, skill, ai, battle|
score += 20 if user.pbOpposingSide.effects[PBEffects::AuroraVeil] > 0
score += 20 if user.pbOpposingSide.effects[PBEffects::Reflect] > 0
score += 20 if user.pbOpposingSide.effects[PBEffects::LightScreen] > 0
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("ProtectUser",
proc { |score, move, user, target, skill, ai, battle|
if user.effects[PBEffects::ProtectRate] > 1 ||
target.effects[PBEffects::HyperBeam] > 0
score -= 90
else
if ai.skill_check(Battle::AI::AILevel.medium)
score -= user.effects[PBEffects::ProtectRate] * 40
end
score += 50 if user.turnCount == 0
score += 30 if target.effects[PBEffects::TwoTurnAttack]
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("ProtectUserBanefulBunker",
proc { |score, move, user, target, skill, ai, battle|
if user.effects[PBEffects::ProtectRate] > 1 ||
target.effects[PBEffects::HyperBeam] > 0
score -= 90
else
if ai.skill_check(Battle::AI::AILevel.medium)
score -= user.effects[PBEffects::ProtectRate] * 40
end
score += 50 if user.turnCount == 0
score += 30 if target.effects[PBEffects::TwoTurnAttack]
score += 20 # Because of possible poisoning
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("ProtectUserFromDamagingMovesKingsShield",
proc { |score, move, user, target, skill, ai, battle|
if user.effects[PBEffects::ProtectRate] > 1 ||
target.effects[PBEffects::HyperBeam] > 0
score -= 90
else
if ai.skill_check(Battle::AI::AILevel.medium)
score -= user.effects[PBEffects::ProtectRate] * 40
end
score += 50 if user.turnCount == 0
score += 30 if target.effects[PBEffects::TwoTurnAttack]
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("ProtectUserFromDamagingMovesObstruct",
proc { |score, move, user, target, skill, ai, battle|
if user.effects[PBEffects::ProtectRate] > 1 ||
target.effects[PBEffects::HyperBeam] > 0
score -= 90
else
if ai.skill_check(Battle::AI::AILevel.medium)
score -= user.effects[PBEffects::ProtectRate] * 40
end
score += 50 if user.turnCount == 0
score += 30 if target.effects[PBEffects::TwoTurnAttack]
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("ProtectUserFromTargetingMovesSpikyShield",
proc { |score, move, user, target, skill, ai, battle|
if user.effects[PBEffects::ProtectRate] > 1 ||
target.effects[PBEffects::HyperBeam] > 0
score -= 90
else
if ai.skill_check(Battle::AI::AILevel.medium)
score -= user.effects[PBEffects::ProtectRate] * 40
end
score += 50 if user.turnCount == 0
score += 30 if target.effects[PBEffects::TwoTurnAttack]
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("ProtectUserSideFromDamagingMovesIfUserFirstTurn",
proc { |score, move, user, target, skill, ai, battle|
next 0 if user.turnCount != 0
next score + 30
}
)
# ProtectUserSideFromStatusMoves
# ProtectUserSideFromPriorityMoves
# ProtectUserSideFromMultiTargetDamagingMoves
# RemoveProtections
# RemoveProtectionsBypassSubstitute
Battle::AI::Handlers::MoveEffectScore.add("HoopaRemoveProtectionsBypassSubstituteLowerUserDef1",
proc { |score, move, user, target, skill, ai, battle|
next 0 if !user.isSpecies?(:HOOPA) || user.form != 1
next score + 20 if target.stages[:DEFENSE] > 0
}
)
Battle::AI::Handlers::MoveEffectScore.add("RecoilQuarterOfDamageDealt",
proc { |score, move, user, target, skill, ai, battle|
next score - 25
}
)
Battle::AI::Handlers::MoveEffectScore.add("RecoilThirdOfDamageDealtParalyzeTarget",
proc { |score, move, user, target, skill, ai, battle|
score -= 30
if target.pbCanParalyze?(user, false)
score += 30
if ai.skill_check(Battle::AI::AILevel.medium)
aspeed = pbRoughStat(user, :SPEED)
ospeed = pbRoughStat(target, :SPEED)
if aspeed < ospeed
score += 30
elsif aspeed > ospeed
score -= 40
end
end
if ai.skill_check(Battle::AI::AILevel.high)
score -= 40 if target.hasActiveAbility?([:GUTS, :MARVELSCALE, :QUICKFEET])
end
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("RecoilThirdOfDamageDealtBurnTarget",
proc { |score, move, user, target, skill, ai, battle|
score -= 30
if target.pbCanBurn?(user, false)
score += 30
if ai.skill_check(Battle::AI::AILevel.high)
score -= 40 if target.hasActiveAbility?([:GUTS, :MARVELSCALE, :QUICKFEET, :FLAREBOOST])
end
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("RecoilHalfOfDamageDealt",
proc { |score, move, user, target, skill, ai, battle|
next score - 40
}
)
# EffectivenessIncludesFlyingType
Battle::AI::Handlers::MoveEffectScore.add("CategoryDependsOnHigherDamagePoisonTarget",
proc { |score, move, user, target, skill, ai, battle|
next score + 5 if target.pbCanPoison?(user, false)
}
)
# CategoryDependsOnHigherDamageIgnoreTargetAbility
# UseUserBaseDefenseInsteadOfUserBaseAttack
# UseTargetAttackInsteadOfUserAttack
# UseTargetDefenseInsteadOfTargetSpDef
Battle::AI::Handlers::MoveEffectScore.add("EnsureNextMoveAlwaysHits",
proc { |score, move, user, target, skill, ai, battle|
next 0 if target.effects[PBEffects::Substitute] > 0
next 0 if user.effects[PBEffects::LockOn] > 0
}
)
Battle::AI::Handlers::MoveEffectScore.add("StartNegateTargetEvasionStatStageAndGhostImmunity",
proc { |score, move, user, target, skill, ai, battle|
if target.effects[PBEffects::Foresight]
score -= 90
elsif target.pbHasType?(:GHOST)
score += 70
elsif target.stages[:EVASION] <= 0
score -= 60
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("StartNegateTargetEvasionStatStageAndDarkImmunity",
proc { |score, move, user, target, skill, ai, battle|
if target.effects[PBEffects::MiracleEye]
score -= 90
elsif target.pbHasType?(:DARK)
score += 70
elsif target.stages[:EVASION] <= 0
score -= 60
end
next score
}
)
# IgnoreTargetDefSpDefEvaStatStages
# TypeIsUserFirstType
# TypeDependsOnUserIVs
Battle::AI::Handlers::MoveEffectScore.add("TypeAndPowerDependOnUserBerry",
proc { |score, move, user, target, skill, ai, battle|
next 0 if !user.item || !user.item.is_berry? || !user.itemActive?
}
)
# TypeDependsOnUserPlate
# TypeDependsOnUserMemory
# TypeDependsOnUserDrive
Battle::AI::Handlers::MoveEffectScore.add("TypeDependsOnUserMorpekoFormRaiseUserSpeed1",
proc { |score, move, user, target, skill, ai, battle|
next score + 20 if user.stages[:SPEED] <= 0
}
)
# TypeAndPowerDependOnWeather
Battle::AI::Handlers::MoveEffectScore.add("TypeAndPowerDependOnTerrain",
proc { |score, move, user, target, skill, ai, battle|
next score + 40 if battle.field.terrain != :None
}
)
Battle::AI::Handlers::MoveEffectScore.add("TargetMovesBecomeElectric",
proc { |score, move, user, target, skill, ai, battle|
aspeed = pbRoughStat(user, :SPEED)
ospeed = pbRoughStat(target, :SPEED)
next 0 if aspeed > ospeed
}
)
# NormalMovesBecomeElectric

View File

@@ -0,0 +1,207 @@
#===============================================================================
#
#===============================================================================
# HitTwoTimes
Battle::AI::Handlers::MoveEffectScore.add("HitTwoTimesPoisonTarget",
proc { |score, move, user, target, skill, ai, battle|
next 0 if !target.pbCanPoison?(user, false)
score += 30
if ai.skill_check(Battle::AI::AILevel.medium)
score += 30 if target.hp <= target.totalhp / 4
score += 50 if target.hp <= target.totalhp / 8
score -= 40 if target.effects[PBEffects::Yawn] > 0
end
if ai.skill_check(Battle::AI::AILevel.high)
score += 10 if pbRoughStat(target, :DEFENSE) > 100
score += 10 if pbRoughStat(target, :SPECIAL_DEFENSE) > 100
score -= 40 if target.hasActiveAbility?([:GUTS, :MARVELSCALE, :TOXICBOOST])
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("HitTwoTimesFlinchTarget",
proc { |score, move, user, target, skill, ai, battle|
next score + 30 if target.effects[PBEffects::Minimize]
}
)
# HitTwoTimesTargetThenTargetAlly
# HitThreeTimesPowersUpWithEachHit
Battle::AI::Handlers::MoveEffectScore.add("HitThreeTimesAlwaysCriticalHit",
proc { |score, move, user, target, skill, ai, battle|
if ai.skill_check(Battle::AI::AILevel.high)
stat = (move.physicalMove?) ? :DEFENSE : :SPECIAL_DEFENSE
next score + 50 if targets.stages[stat] > 1
end
}
)
# HitTwoToFiveTimes
# HitTwoToFiveTimesOrThreeForAshGreninja
Battle::AI::Handlers::MoveEffectScore.add("HitTwoToFiveTimesRaiseUserSpd1LowerUserDef1",
proc { |score, move, user, target, skill, ai, battle|
aspeed = pbRoughStat(user, :SPEED)
ospeed = pbRoughStat(target, :SPEED)
if aspeed > ospeed && aspeed * 2 / 3 < ospeed
score -= 50
elsif aspeed < ospeed && aspeed * 1.5 > ospeed
score += 50
end
score += user.stages[:DEFENSE] * 30
next score
}
)
# HitOncePerUserTeamMember
# AttackAndSkipNextTurn
# TwoTurnAttack
# TwoTurnAttackOneTurnInSun
Battle::AI::Handlers::MoveEffectScore.add("TwoTurnAttackParalyzeTarget",
proc { |score, move, user, target, skill, ai, battle|
if target.pbCanParalyze?(user, false) &&
!(ai.skill_check(Battle::AI::AILevel.medium) &&
move.id == :THUNDERWAVE &&
Effectiveness.ineffective?(pbCalcTypeMod(move.type, user, target)))
score += 30
if ai.skill_check(Battle::AI::AILevel.medium)
aspeed = pbRoughStat(user, :SPEED)
ospeed = pbRoughStat(target, :SPEED)
if aspeed < ospeed
score += 30
elsif aspeed > ospeed
score -= 40
end
end
if ai.skill_check(Battle::AI::AILevel.high)
score -= 40 if target.hasActiveAbility?([:GUTS, :MARVELSCALE, :QUICKFEET])
end
elsif ai.skill_check(Battle::AI::AILevel.medium)
score -= 90 if move.statusMove?
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("TwoTurnAttackBurnTarget",
proc { |score, move, user, target, skill, ai, battle|
next 0 if !target.pbCanBurn?(user, false)
score += 30
if ai.skill_check(Battle::AI::AILevel.high)
score -= 40 if target.hasActiveAbility?([:GUTS, :MARVELSCALE, :QUICKFEET, :FLAREBOOST])
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("TwoTurnAttackFlinchTarget",
proc { |score, move, user, target, skill, ai, battle|
score += 20 if user.effects[PBEffects::FocusEnergy] > 0
if ai.skill_check(Battle::AI::AILevel.high)
score += 20 if !target.hasActiveAbility?(:INNERFOCUS) &&
target.effects[PBEffects::Substitute] == 0
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("TwoTurnAttackRaiseUserSpAtkSpDefSpd2",
proc { |score, move, user, target, skill, ai, battle|
if user.statStageAtMax?(:SPECIAL_ATTACK) &&
user.statStageAtMax?(:SPECIAL_DEFENSE) &&
user.statStageAtMax?(:SPEED)
score -= 90
else
score -= user.stages[:SPECIAL_ATTACK] * 10 # Only *10 instead of *20
score -= user.stages[:SPECIAL_DEFENSE] * 10 # because two-turn attack
score -= user.stages[:SPEED] * 10
if ai.skill_check(Battle::AI::AILevel.medium)
hasSpecialAttack = false
user.eachMove do |m|
next if !m.specialMove?(m.type)
hasSpecialAttack = true
break
end
if hasSpecialAttack
score += 20
elsif ai.skill_check(Battle::AI::AILevel.high)
score -= 90
end
end
if ai.skill_check(Battle::AI::AILevel.high)
aspeed = pbRoughStat(user, :SPEED)
ospeed = pbRoughStat(target, :SPEED)
score += 30 if aspeed < ospeed && aspeed * 2 > ospeed
end
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("TwoTurnAttackChargeRaiseUserDefense1",
proc { |score, move, user, target, skill, ai, battle|
if move.statusMove?
if user.statStageAtMax?(:DEFENSE)
score -= 90
else
score -= user.stages[:DEFENSE] * 20
end
elsif user.stages[:DEFENSE] < 0
score += 20
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("TwoTurnAttackChargeRaiseUserSpAtk1",
proc { |score, move, user, target, skill, ai, battle|
aspeed = pbRoughStat(user, :SPEED)
ospeed = pbRoughStat(target, :SPEED)
if (aspeed > ospeed && user.hp > user.totalhp / 3) || user.hp > user.totalhp / 2
score += 60
else
score -= 90
end
score += user.stages[:SPECIAL_ATTACK] * 20
next score
}
)
# TwoTurnAttackInvulnerableUnderground
# TwoTurnAttackInvulnerableUnderwater
# TwoTurnAttackInvulnerableInSky
# TwoTurnAttackInvulnerableInSkyParalyzeTarget
# TwoTurnAttackInvulnerableInSkyTargetCannotAct
# TwoTurnAttackInvulnerableRemoveProtections
# MultiTurnAttackPreventSleeping
# MultiTurnAttackConfuseUserAtEnd
# MultiTurnAttackPowersUpEachTurn
Battle::AI::Handlers::MoveEffectScore.add("MultiTurnAttackBideThenReturnDoubleDamage",
proc { |score, move, user, target, skill, ai, battle|
if user.hp <= user.totalhp / 4
score -= 90
elsif user.hp <= user.totalhp / 2
score -= 50
end
next score
}
)

View File

@@ -0,0 +1,343 @@
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveEffectScore.add("HealUserFullyAndFallAsleep",
proc { |score, move, user, target, skill, ai, battle|
next 0 if user.hp == user.totalhp || !user.pbCanSleep?(user, false, nil, true)
score += 70
score -= user.hp * 140 / user.totalhp
score += 30 if user.status != :NONE
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("HealUserHalfOfTotalHP",
proc { |score, move, user, target, skill, ai, battle|
next 0 if user.hp == user.totalhp || (ai.skill_check(Battle::AI::AILevel.medium) && !user.canHeal?)
score += 50
score -= user.hp * 100 / user.totalhp
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("HealUserDependingOnWeather",
proc { |score, move, user, target, skill, ai, battle|
next 0 if user.hp == user.totalhp || (ai.skill_check(Battle::AI::AILevel.medium) && !user.canHeal?)
case user.effectiveWeather
when :Sun, :HarshSun
score += 30
when :None
else
score -= 30
end
score += 50
score -= user.hp * 100 / user.totalhp
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("HealUserDependingOnSandstorm",
proc { |score, move, user, target, skill, ai, battle|
next 0 if user.hp == user.totalhp || (ai.skill_check(Battle::AI::AILevel.medium) && !user.canHeal?)
score += 50
score -= user.hp * 100 / user.totalhp
score += 30 if user.effectiveWeather == :Sandstorm
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("HealUserHalfOfTotalHPLoseFlyingTypeThisTurn",
proc { |score, move, user, target, skill, ai, battle|
next 0 if user.hp == user.totalhp || (ai.skill_check(Battle::AI::AILevel.medium) && !user.canHeal?)
score += 50
score -= user.hp * 100 / user.totalhp
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("CureTargetStatusHealUserHalfOfTotalHP",
proc { |score, move, user, target, skill, ai, battle|
if target.status == :NONE
score -= 90
elsif user.hp == user.totalhp && target.opposes?(user)
score -= 90
else
score += (user.totalhp - user.hp) * 50 / user.totalhp
score -= 30 if target.opposes?(user)
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("HealUserByTargetAttackLowerTargetAttack1",
proc { |score, move, user, target, skill, ai, battle|
if target.statStageAtMin?(:ATTACK)
score -= 90
else
if target.pbCanLowerStatStage?(:ATTACK, user)
score += target.stages[:ATTACK] * 20
if ai.skill_check(Battle::AI::AILevel.medium)
hasPhysicalAttack = false
target.eachMove do |m|
next if !m.physicalMove?(m.type)
hasPhysicalAttack = true
break
end
if hasPhysicalAttack
score += 20
elsif ai.skill_check(Battle::AI::AILevel.high)
score -= 90
end
end
end
score += (user.totalhp - user.hp) * 50 / user.totalhp
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("HealUserByHalfOfDamageDone",
proc { |score, move, user, target, skill, ai, battle|
if ai.skill_check(Battle::AI::AILevel.high) && target.hasActiveAbility?(:LIQUIDOOZE)
score -= 70
elsif user.hp <= user.totalhp / 2
score += 20
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("HealUserByHalfOfDamageDoneIfTargetAsleep",
proc { |score, move, user, target, skill, ai, battle|
next 0 if !target.asleep?
if ai.skill_check(Battle::AI::AILevel.high) && target.hasActiveAbility?(:LIQUIDOOZE)
score -= 70
elsif user.hp <= user.totalhp / 2
score += 20
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("HealUserByThreeQuartersOfDamageDone",
proc { |score, move, user, target, skill, ai, battle|
if ai.skill_check(Battle::AI::AILevel.high) && target.hasActiveAbility?(:LIQUIDOOZE)
score -= 80
elsif user.hp <= user.totalhp / 2
score += 40
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("HealUserAndAlliesQuarterOfTotalHP",
proc { |score, move, user, target, skill, ai, battle|
ally_amt = 30
battle.allSameSideBattlers(user.index).each do |b|
if b.hp == b.totalhp || (ai.skill_check(Battle::AI::AILevel.medium) && !b.canHeal?)
score -= ally_amt / 2
elsif b.hp < b.totalhp * 3 / 4
score += ally_amt
end
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("HealUserAndAlliesQuarterOfTotalHPCureStatus",
proc { |score, move, user, target, skill, ai, battle|
ally_amt = 80 / battle.pbSideSize(user.index)
battle.allSameSideBattlers(user.index).each do |b|
if b.hp == b.totalhp || (ai.skill_check(Battle::AI::AILevel.medium) && !b.canHeal?)
score -= ally_amt
elsif b.hp < b.totalhp * 3 / 4
score += ally_amt
end
score += ally_amt / 2 if b.pbHasAnyStatus?
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("HealTargetHalfOfTotalHP",
proc { |score, move, user, target, skill, ai, battle|
next 0 if user.opposes?(target)
if target.hp < target.totalhp / 2 && target.effects[PBEffects::Substitute] == 0
score += 20
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("HealTargetDependingOnGrassyTerrain",
proc { |score, move, user, target, skill, ai, battle|
next 0 if user.hp == user.totalhp || (ai.skill_check(Battle::AI::AILevel.medium) && !user.canHeal?)
score += 50
score -= user.hp * 100 / user.totalhp
if ai.skill_check(Battle::AI::AILevel.medium)
score += 30 if battle.field.terrain == :Grassy
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("HealUserPositionNextTurn",
proc { |score, move, user, target, skill, ai, battle|
next 0 if battle.positions[user.index].effects[PBEffects::Wish] > 0
}
)
Battle::AI::Handlers::MoveEffectScore.add("StartHealUserEachTurn",
proc { |score, move, user, target, skill, ai, battle|
next 0 if user.effects[PBEffects::AquaRing]
}
)
Battle::AI::Handlers::MoveEffectScore.add("StartHealUserEachTurnTrapUserInBattle",
proc { |score, move, user, target, skill, ai, battle|
next 0 if user.effects[PBEffects::Ingrain]
}
)
Battle::AI::Handlers::MoveEffectScore.add("StartDamageTargetEachTurnIfTargetAsleep",
proc { |score, move, user, target, skill, ai, battle|
if target.effects[PBEffects::Nightmare] ||
target.effects[PBEffects::Substitute] > 0
score -= 90
elsif !target.asleep?
score -= 90
else
score -= 90 if target.statusCount <= 1
score += 50 if target.statusCount > 3
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("StartLeechSeedTarget",
proc { |score, move, user, target, skill, ai, battle|
if target.effects[PBEffects::LeechSeed] >= 0
score -= 90
elsif ai.skill_check(Battle::AI::AILevel.medium) && target.pbHasType?(:GRASS)
score -= 90
elsif user.turnCount == 0
score += 60
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("UserLosesHalfOfTotalHP",
proc { |score, move, user, target, skill, ai, battle|
next 0 if user.hp <= user.totalhp / 2
}
)
Battle::AI::Handlers::MoveEffectScore.add("UserLosesHalfOfTotalHPExplosive",
proc { |score, move, user, target, skill, ai, battle|
reserves = battle.pbAbleNonActiveCount(user.idxOwnSide)
foes = battle.pbAbleNonActiveCount(user.idxOpposingSide)
if battle.pbCheckGlobalAbility(:DAMP)
score -= 100
elsif ai.skill_check(Battle::AI::AILevel.medium) && reserves == 0 && foes > 0
score -= 100 # don't want to lose
elsif ai.skill_check(Battle::AI::AILevel.high) && reserves == 0 && foes == 0
score += 80 # want to draw
else
score -= (user.totalhp - user.hp) * 75 / user.totalhp
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("UserFaintsExplosive",
proc { |score, move, user, target, skill, ai, battle|
reserves = battle.pbAbleNonActiveCount(user.idxOwnSide)
foes = battle.pbAbleNonActiveCount(user.idxOpposingSide)
if battle.pbCheckGlobalAbility(:DAMP)
score -= 100
elsif ai.skill_check(Battle::AI::AILevel.medium) && reserves == 0 && foes > 0
score -= 100 # don't want to lose
elsif ai.skill_check(Battle::AI::AILevel.high) && reserves == 0 && foes == 0
score += 80 # want to draw
else
score -= user.hp * 100 / user.totalhp
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("UserFaintsPowersUpInMistyTerrainExplosive",
proc { |score, move, user, target, skill, ai, battle|
reserves = battle.pbAbleNonActiveCount(user.idxOwnSide)
foes = battle.pbAbleNonActiveCount(user.idxOpposingSide)
if battle.pbCheckGlobalAbility(:DAMP)
score -= 100
elsif ai.skill_check(Battle::AI::AILevel.medium) && reserves == 0 && foes > 0
score -= 100 # don't want to lose
elsif ai.skill_check(Battle::AI::AILevel.high) && reserves == 0 && foes == 0
score += 40 # want to draw
score += 40 if battle.field.terrain == :Misty
else
score -= user.hp * 100 / user.totalhp
score += 20 if battle.field.terrain == :Misty
end
next score
}
)
# UserFaintsFixedDamageUserHP
Battle::AI::Handlers::MoveEffectScore.add("UserFaintsLowerTargetAtkSpAtk2",
proc { |score, move, user, target, skill, ai, battle|
if !target.pbCanLowerStatStage?(:ATTACK, user) &&
!target.pbCanLowerStatStage?(:SPECIAL_ATTACK, user)
score -= 100
elsif battle.pbAbleNonActiveCount(user.idxOwnSide) == 0
score -= 100
else
score += target.stages[:ATTACK] * 10
score += target.stages[:SPECIAL_ATTACK] * 10
score -= user.hp * 100 / user.totalhp
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("UserFaintsHealAndCureReplacement",
proc { |score, move, user, target, skill, ai, battle|
next score - 70
}
)
Battle::AI::Handlers::MoveEffectScore.copy("UserFaintsHealAndCureReplacement",
"UserFaintsHealAndCureReplacementRestorePP")
Battle::AI::Handlers::MoveEffectScore.add("StartPerishCountsForAllBattlers",
proc { |score, move, user, target, skill, ai, battle|
if battle.pbAbleNonActiveCount(user.idxOwnSide) == 0
score -= 90
elsif target.effects[PBEffects::PerishSong] > 0
score -= 90
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("AttackerFaintsIfUserFaints",
proc { |score, move, user, target, skill, ai, battle|
score += 50
score -= user.hp * 100 / user.totalhp
score += 30 if user.hp <= user.totalhp / 10
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("SetAttackerMovePPTo0IfUserFaints",
proc { |score, move, user, target, skill, ai, battle|
score += 50
score -= user.hp * 100 / user.totalhp
score += 30 if user.hp <= user.totalhp / 10
next score
}
)

View File

@@ -0,0 +1,193 @@
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveEffectScore.add("UserTakesTargetItem",
proc { |score, move, user, target, skill, ai, battle|
if ai.skill_check(Battle::AI::AILevel.high)
if !user.item && target.item
score += 40
else
score -= 90
end
else
score -= 80
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("TargetTakesUserItem",
proc { |score, move, user, target, skill, ai, battle|
if !user.item || target.item
score -= 90
elsif user.hasActiveItem?([:FLAMEORB, :TOXICORB, :STICKYBARB, :IRONBALL,
:CHOICEBAND, :CHOICESCARF, :CHOICESPECS])
score += 50
else
score -= 80
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("UserTargetSwapItems",
proc { |score, move, user, target, skill, ai, battle|
if !user.item && !target.item
score -= 90
elsif ai.skill_check(Battle::AI::AILevel.high) && target.hasActiveAbility?(:STICKYHOLD)
score -= 90
elsif user.hasActiveItem?([:FLAMEORB, :TOXICORB, :STICKYBARB, :IRONBALL,
:CHOICEBAND, :CHOICESCARF, :CHOICESPECS])
score += 50
elsif !user.item && target.item
score -= 30 if user.lastMoveUsed &&
GameData::Move.get(user.lastMoveUsed).function_code == "UserTargetSwapItems"
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("RestoreUserConsumedItem",
proc { |score, move, user, target, skill, ai, battle|
if !user.recycleItem || user.item
score -= 80
elsif user.recycleItem
score += 30
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("RemoveTargetItem",
proc { |score, move, user, target, skill, ai, battle|
if ai.skill_check(Battle::AI::AILevel.high)
score += 20 if target.item
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("DestroyTargetBerryOrGem",
proc { |score, move, user, target, skill, ai, battle|
if target.effects[PBEffects::Substitute] == 0
if ai.skill_check(Battle::AI::AILevel.high) && target.item && target.item.is_berry?
score += 30
end
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("CorrodeTargetItem",
proc { |score, move, user, target, skill, ai, battle|
if battle.corrosiveGas[target.index % 2][target.pokemonIndex]
score -= 100
elsif !target.item || !target.itemActive? || target.unlosableItem?(target.item) ||
target.hasActiveAbility?(:STICKYHOLD)
score -= 90
elsif target.effects[PBEffects::Substitute] > 0
score -= 90
else
score += 50
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("StartTargetCannotUseItem",
proc { |score, move, user, target, skill, ai, battle|
next 0 if target.effects[PBEffects::Embargo] > 0
}
)
Battle::AI::Handlers::MoveEffectScore.add("StartNegateHeldItems",
proc { |score, move, user, target, skill, ai, battle|
next 0 if battle.field.effects[PBEffects::MagicRoom] > 0
next score + 30 if !user.item && target.item
}
)
Battle::AI::Handlers::MoveEffectScore.add("UserConsumeBerryRaiseDefense2",
proc { |score, move, user, target, skill, ai, battle|
if !user.item || !user.item.is_berry? || !user.itemActive?
score -= 100
else
if ai.skill_check(Battle::AI::AILevel.high)
useful_berries = [
:ORANBERRY, :SITRUSBERRY, :AGUAVBERRY, :APICOTBERRY, :CHERIBERRY,
:CHESTOBERRY, :FIGYBERRY, :GANLONBERRY, :IAPAPABERRY, :KEEBERRY,
:LANSATBERRY, :LEPPABERRY, :LIECHIBERRY, :LUMBERRY, :MAGOBERRY,
:MARANGABERRY, :PECHABERRY, :PERSIMBERRY, :PETAYABERRY, :RAWSTBERRY,
:SALACBERRY, :STARFBERRY, :WIKIBERRY
]
score += 30 if useful_berries.include?(user.item_id)
end
if ai.skill_check(Battle::AI::AILevel.medium)
score += 20 if user.canHeal? && user.hp < user.totalhp / 3 && user.hasActiveAbility?(:CHEEKPOUCH)
score += 20 if user.hasActiveAbility?([:HARVEST, :RIPEN]) ||
user.pbHasMoveFunction?("RestoreUserConsumedItem") # Recycle
score += 20 if !user.canConsumeBerry?
end
score -= user.stages[:DEFENSE] * 20
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("AllBattlersConsumeBerry",
proc { |score, move, user, target, skill, ai, battle|
useful_berries = [
:ORANBERRY, :SITRUSBERRY, :AGUAVBERRY, :APICOTBERRY, :CHERIBERRY,
:CHESTOBERRY, :FIGYBERRY, :GANLONBERRY, :IAPAPABERRY, :KEEBERRY,
:LANSATBERRY, :LEPPABERRY, :LIECHIBERRY, :LUMBERRY, :MAGOBERRY,
:MARANGABERRY, :PECHABERRY, :PERSIMBERRY, :PETAYABERRY,
:RAWSTBERRY, :SALACBERRY, :STARFBERRY, :WIKIBERRY
]
battle.allSameSideBattlers(user.index).each do |b|
if !b.item || !b.item.is_berry? || !b.itemActive?
score -= 100 / battle.pbSideSize(user.index)
else
if ai.skill_check(Battle::AI::AILevel.high)
amt = 30 / battle.pbSideSize(user.index)
score += amt if useful_berries.include?(b.item_id)
end
if ai.skill_check(Battle::AI::AILevel.medium)
amt = 20 / battle.pbSideSize(user.index)
score += amt if b.canHeal? && b.hp < b.totalhp / 3 && b.hasActiveAbility?(:CHEEKPOUCH)
score += amt if b.hasActiveAbility?([:HARVEST, :RIPEN]) ||
b.pbHasMoveFunction?("RestoreUserConsumedItem") # Recycle
score += amt if !b.canConsumeBerry?
end
end
end
if ai.skill_check(Battle::AI::AILevel.high)
battle.allOtherSideBattlers(user.index).each do |b|
amt = 10 / battle.pbSideSize(target.index)
score -= amt if b.hasActiveItem?(useful_berries)
score -= amt if b.canHeal? && b.hp < b.totalhp / 3 && b.hasActiveAbility?(:CHEEKPOUCH)
score -= amt if b.hasActiveAbility?([:HARVEST, :RIPEN]) ||
b.pbHasMoveFunction?("RestoreUserConsumedItem") # Recycle
score -= amt if !b.canConsumeBerry?
end
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("UserConsumeTargetBerry",
proc { |score, move, user, target, skill, ai, battle|
if target.effects[PBEffects::Substitute] == 0
if ai.skill_check(Battle::AI::AILevel.high) && target.item && target.item.is_berry?
score += 30
end
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("ThrowUserItemAtTarget",
proc { |score, move, user, target, skill, ai, battle|
next 0 if !user.item || !user.itemActive? ||
user.unlosableItem?(user.item) || user.item.is_poke_ball?
}
)

View File

@@ -0,0 +1,271 @@
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveEffectScore.add("RedirectAllMovesToUser",
proc { |score, move, user, target, skill, ai, battle|
next 0 if user.allAllies.length == 0
}
)
Battle::AI::Handlers::MoveEffectScore.add("RedirectAllMovesToTarget",
proc { |score, move, user, target, skill, ai, battle|
next 0 if user.allAllies.length == 0
}
)
Battle::AI::Handlers::MoveEffectScore.add("CannotBeRedirected",
proc { |score, move, user, target, skill, ai, battle|
redirection = false
user.allOpposing.each do |b|
next if b.index == target.index
if b.effects[PBEffects::RagePowder] ||
b.effects[PBEffects::Spotlight] > 0 ||
b.effects[PBEffects::FollowMe] > 0 ||
(b.hasActiveAbility?(:LIGHTNINGROD) && move.pbCalcType == :ELECTRIC) ||
(b.hasActiveAbility?(:STORMDRAIN) && move.pbCalcType == :WATER)
redirection = true
break
end
end
score += 50 if redirection && ai.skill_check(Battle::AI::AILevel.medium)
next score
}
)
# RandomlyDamageOrHealTarget
Battle::AI::Handlers::MoveEffectScore.add("HealAllyOrDamageFoe",
proc { |score, move, user, target, skill, ai, battle|
if !target.opposes?(user)
if target.hp == target.totalhp || (ai.skill_check(Battle::AI::AILevel.medium) && !target.canHeal?)
score -= 90
else
score += 50
score -= target.hp * 100 / target.totalhp
end
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("CurseTargetOrLowerUserSpd1RaiseUserAtkDef1",
proc { |score, move, user, target, skill, ai, battle|
if user.pbHasType?(:GHOST)
if target.effects[PBEffects::Curse]
score -= 90
elsif user.hp <= user.totalhp / 2
if battle.pbAbleNonActiveCount(user.idxOwnSide) == 0
score -= 90
else
score -= 50
score -= 30 if battle.switchStyle
end
end
else
avg = user.stages[:SPEED] * 10
avg -= user.stages[:ATTACK] * 10
avg -= user.stages[:DEFENSE] * 10
score += avg / 3
end
next score
}
)
# EffectDependsOnEnvironment
Battle::AI::Handlers::MoveEffectScore.add("HitsAllFoesAndPowersUpInPsychicTerrain",
proc { |score, move, user, target, skill, ai, battle|
next score + 40 if battle.field.terrain == :Psychic && user.affectedByTerrain?
}
)
Battle::AI::Handlers::MoveEffectScore.add("TargetNextFireMoveDamagesTarget",
proc { |score, move, user, target, skill, ai, battle|
aspeed = pbRoughStat(user, :SPEED)
ospeed = pbRoughStat(target, :SPEED)
if aspeed > ospeed
score -= 90
elsif target.pbHasMoveType?(:FIRE)
score += 30
end
next score
}
)
# DoublePowerAfterFusionFlare
# DoublePowerAfterFusionBolt
Battle::AI::Handlers::MoveEffectScore.add("PowerUpAllyMove",
proc { |score, move, user, target, skill, ai, battle|
next 0 if user.allAllies.empty?
next score + 30
}
)
Battle::AI::Handlers::MoveEffectScore.add("CounterPhysicalDamage",
proc { |score, move, user, target, skill, ai, battle|
if target.effects[PBEffects::HyperBeam] > 0
score -= 90
else
attack = pbRoughStat(user, :ATTACK)
spatk = pbRoughStat(user, :SPECIAL_ATTACK)
if attack * 1.5 < spatk
score -= 60
elsif ai.skill_check(Battle::AI::AILevel.medium) && target.lastMoveUsed
moveData = GameData::Move.get(target.lastMoveUsed)
score += 60 if moveData.physical?
end
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("CounterSpecialDamage",
proc { |score, move, user, target, skill, ai, battle|
if target.effects[PBEffects::HyperBeam] > 0
score -= 90
else
attack = pbRoughStat(user, :ATTACK)
spatk = pbRoughStat(user, :SPECIAL_ATTACK)
if attack > spatk * 1.5
score -= 60
elsif ai.skill_check(Battle::AI::AILevel.medium) && target.lastMoveUsed
moveData = GameData::Move.get(target.lastMoveUsed)
score += 60 if moveData.special?
end
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("CounterDamagePlusHalf",
proc { |score, move, user, target, skill, ai, battle|
next score - 90 if target.effects[PBEffects::HyperBeam] > 0
}
)
Battle::AI::Handlers::MoveEffectScore.add("UserAddStockpileRaiseDefSpDef1",
proc { |score, move, user, target, skill, ai, battle|
avg = 0
avg -= user.stages[:DEFENSE] * 10
avg -= user.stages[:SPECIAL_DEFENSE] * 10
score += avg / 2
if user.effects[PBEffects::Stockpile] >= 3
score -= 80
elsif user.pbHasMoveFunction?("PowerDependsOnUserStockpile",
"HealUserDependingOnUserStockpile") # Spit Up, Swallow
score += 20 # More preferable if user also has Spit Up/Swallow
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("PowerDependsOnUserStockpile",
proc { |score, move, user, target, skill, ai, battle|
next 0 if user.effects[PBEffects::Stockpile] == 0
}
)
Battle::AI::Handlers::MoveEffectScore.add("HealUserDependingOnUserStockpile",
proc { |score, move, user, target, skill, ai, battle|
next 0 if user.effects[PBEffects::Stockpile] == 0
next 0 if user.hp == user.totalhp
mult = [0, 25, 50, 100][user.effects[PBEffects::Stockpile]]
score += mult
score -= user.hp * mult * 2 / user.totalhp
next score
}
)
# GrassPledge
# FirePledge
# WaterPledge
# UseLastMoveUsed
Battle::AI::Handlers::MoveEffectScore.add("UseLastMoveUsedByTarget",
proc { |score, move, user, target, skill, ai, battle|
score -= 40
if ai.skill_check(Battle::AI::AILevel.high)
score -= 100 if !target.lastRegularMoveUsed ||
GameData::Move.get(target.lastRegularMoveUsed).flags.none? { |f| f[/^CanMirrorMove$/i] }
end
next score
}
)
# UseMoveTargetIsAboutToUse
# UseMoveDependingOnEnvironment
# UseRandomMove
# UseRandomMoveFromUserParty
Battle::AI::Handlers::MoveEffectScore.add("UseRandomUserMoveIfAsleep",
proc { |score, move, user, target, skill, ai, battle|
if user.asleep?
score += 100 # Because it can only be used while asleep
else
score -= 90
end
next score
}
)
# BounceBackProblemCausingStatusMoves
# StealAndUseBeneficialStatusMove
Battle::AI::Handlers::MoveEffectScore.add("ReplaceMoveThisBattleWithTargetLastMoveUsed",
proc { |score, move, user, target, skill, ai, battle|
moveBlacklist = [
"Struggle", # Struggle
"ReplaceMoveThisBattleWithTargetLastMoveUsed", # Mimic
"ReplaceMoveWithTargetLastMoveUsed", # Sketch
"UseRandomMove" # Metronome
]
if user.effects[PBEffects::Transform] || !target.lastRegularMoveUsed
score -= 90
else
lastMoveData = GameData::Move.get(target.lastRegularMoveUsed)
if moveBlacklist.include?(lastMoveData.function_code) ||
lastMoveData.type == :SHADOW
score -= 90
end
user.eachMove do |m|
next if m != target.lastRegularMoveUsed
score -= 90
break
end
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("ReplaceMoveWithTargetLastMoveUsed",
proc { |score, move, user, target, skill, ai, battle|
moveBlacklist = [
"Struggle", # Struggle
"ReplaceMoveWithTargetLastMoveUsed" # Sketch
]
if user.effects[PBEffects::Transform] || !target.lastRegularMoveUsed
score -= 90
else
lastMoveData = GameData::Move.get(target.lastRegularMoveUsed)
if moveBlacklist.include?(lastMoveData.function_code) ||
lastMoveData.type == :SHADOW
score -= 90
end
user.eachMove do |m|
next if m != target.lastRegularMoveUsed
score -= 90 # User already knows the move that will be Sketched
break
end
end
next score
}
)

View File

@@ -0,0 +1,347 @@
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveEffectScore.add("FleeFromBattle",
proc { |score, move, user, target, skill, ai, battle|
next 0 if battle.trainerBattle?
}
)
Battle::AI::Handlers::MoveEffectScore.add("SwitchOutUserStatusMove",
proc { |score, move, user, target, skill, ai, battle|
if !battle.pbCanChooseNonActive?(user.index) ||
battle.pbTeamAbleNonActiveCount(user.index) > 1 # Don't switch in ace
score -= 100
else
score += 40 if user.effects[PBEffects::Confusion] > 0
total = 0
GameData::Stat.each_battle { |s| total += user.stages[s.id] }
if total <= 0 || user.turnCount == 0
score += 60
else
score -= total * 10
# special case: user has no damaging moves
hasDamagingMove = false
user.eachMove do |m|
next if !m.damagingMove?
hasDamagingMove = true
break
end
score += 75 if !hasDamagingMove
end
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("SwitchOutUserDamagingMove",
proc { |score, move, user, target, skill, ai, battle|
next 0 if !battle.pbCanChooseNonActive?(user.index) ||
battle.pbTeamAbleNonActiveCount(user.index) > 1 # Don't switch in ace
}
)
Battle::AI::Handlers::MoveEffectScore.add("LowerTargetAtkSpAtk1SwitchOutUser",
proc { |score, move, user, target, skill, ai, battle|
avg = target.stages[:ATTACK] * 10
avg += target.stages[:SPECIAL_ATTACK] * 10
score += avg / 2
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("SwitchOutUserPassOnEffects",
proc { |score, move, user, target, skill, ai, battle|
if battle.pbCanChooseNonActive?(user.index)
score -= 40 if user.effects[PBEffects::Confusion] > 0
total = 0
GameData::Stat.each_battle { |s| total += user.stages[s.id] }
if total <= 0 || user.turnCount == 0
score -= 60
else
score += total * 10
# special case: user has no damaging moves
hasDamagingMove = false
user.eachMove do |m|
next if !m.damagingMove?
hasDamagingMove = true
break
end
score += 75 if !hasDamagingMove
end
else
score -= 100
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("SwitchOutTargetStatusMove",
proc { |score, move, user, target, skill, ai, battle|
if target.effects[PBEffects::Ingrain] ||
(ai.skill_check(Battle::AI::AILevel.high) && target.hasActiveAbility?(:SUCTIONCUPS))
score -= 90
else
ch = 0
battle.pbParty(target.index).each_with_index do |pkmn, i|
ch += 1 if battle.pbCanSwitchLax?(target.index, i)
end
score -= 90 if ch == 0
end
if score > 20
score += 50 if target.pbOwnSide.effects[PBEffects::Spikes] > 0
score += 50 if target.pbOwnSide.effects[PBEffects::ToxicSpikes] > 0
score += 50 if target.pbOwnSide.effects[PBEffects::StealthRock]
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("SwitchOutTargetDamagingMove",
proc { |score, move, user, target, skill, ai, battle|
if !target.effects[PBEffects::Ingrain] &&
!(ai.skill_check(Battle::AI::AILevel.high) && target.hasActiveAbility?(:SUCTIONCUPS))
score += 40 if target.pbOwnSide.effects[PBEffects::Spikes] > 0
score += 40 if target.pbOwnSide.effects[PBEffects::ToxicSpikes] > 0
score += 40 if target.pbOwnSide.effects[PBEffects::StealthRock]
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("BindTarget",
proc { |score, move, user, target, skill, ai, battle|
next score + 40 if target.effects[PBEffects::Trapping] == 0
}
)
Battle::AI::Handlers::MoveEffectScore.add("BindTargetDoublePowerIfTargetUnderwater",
proc { |score, move, user, target, skill, ai, battle|
next score + 40 if target.effects[PBEffects::Trapping] == 0
}
)
Battle::AI::Handlers::MoveEffectScore.add("TrapTargetInBattle",
proc { |score, move, user, target, skill, ai, battle|
next 0 if target.effects[PBEffects::MeanLook] >= 0
}
)
Battle::AI::Handlers::MoveEffectScore.add("TrapTargetInBattleLowerTargetDefSpDef1EachTurn",
proc { |score, move, user, target, skill, ai, battle|
next 0 if target.effects[PBEffects::Octolock] >= 0
score += 30 if !target.trappedInBattle?
score -= 100 if !target.pbCanLowerStatStage?(:DEFENSE, user, move) &&
!target.pbCanLowerStatStage?(:SPECIAL_DEFENSE, user, move)
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("TrapUserAndTargetInBattle",
proc { |score, move, user, target, skill, ai, battle|
if target.effects[PBEffects::JawLock] < 0
score += 40 if !user.trappedInBattle? && !target.trappedInBattle?
end
next score
}
)
# TrapAllBattlersInBattleForOneTurn
# PursueSwitchingFoe
Battle::AI::Handlers::MoveEffectScore.add("UsedAfterUserTakesPhysicalDamage",
proc { |score, move, user, target, skill, ai, battle|
if ai.skill_check(Battle::AI::AILevel.medium)
hasPhysicalAttack = false
target.eachMove do |m|
next if !m.physicalMove?(m.type)
hasPhysicalAttack = true
break
end
score -= 80 if !hasPhysicalAttack
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("UsedAfterAllyRoundWithDoublePower",
proc { |score, move, user, target, skill, ai, battle|
if ai.skill_check(Battle::AI::AILevel.medium)
user.allAllies.each do |b|
next if !b.pbHasMove?(move.id)
score += 20
end
end
next score
}
)
# TargetActsNext
# TargetActsLast
Battle::AI::Handlers::MoveEffectScore.add("TargetUsesItsLastUsedMoveAgain",
proc { |score, move, user, target, skill, ai, battle|
if ai.skill_check(Battle::AI::AILevel.medium)
if !target.lastRegularMoveUsed ||
!target.pbHasMove?(target.lastRegularMoveUsed) ||
target.usingMultiTurnAttack?
score -= 90
else
# Without lots of code here to determine good/bad moves and relative
# speeds, using this move is likely to just be a waste of a turn
score -= 50
end
end
next score
}
)
# StartSlowerBattlersActFirst
Battle::AI::Handlers::MoveEffectScore.add("HigherPriorityInGrassyTerrain",
proc { |score, move, user, target, skill, ai, battle|
if ai.skill_check(Battle::AI::AILevel.medium) && @battle.field.terrain == :Grassy
aspeed = pbRoughStat(user, :SPEED)
ospeed = pbRoughStat(target, :SPEED)
score += 40 if aspeed < ospeed
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("LowerPPOfTargetLastMoveBy3",
proc { |score, move, user, target, skill, ai, battle|
last_move = target.pbGetMoveWithID(target.lastRegularMoveUsed)
if last_move && last_move.total_pp > 0 && last_move.pp <= 3
score += 50
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("LowerPPOfTargetLastMoveBy4",
proc { |score, move, user, target, skill, ai, battle|
next score - 40
}
)
Battle::AI::Handlers::MoveEffectScore.add("DisableTargetLastMoveUsed",
proc { |score, move, user, target, skill, ai, battle|
next 0 if target.effects[PBEffects::Disable] > 0
}
)
Battle::AI::Handlers::MoveEffectScore.add("DisableTargetUsingSameMoveConsecutively",
proc { |score, move, user, target, skill, ai, battle|
next 0 if target.effects[PBEffects::Torment]
}
)
Battle::AI::Handlers::MoveEffectScore.add("DisableTargetUsingDifferentMove",
proc { |score, move, user, target, skill, ai, battle|
aspeed = pbRoughStat(user, :SPEED)
ospeed = pbRoughStat(target, :SPEED)
if target.effects[PBEffects::Encore] > 0
score -= 90
elsif aspeed > ospeed
if target.lastRegularMoveUsed
moveData = GameData::Move.get(target.lastRegularMoveUsed)
if moveData.category == 2 && # Status move
[:User, :BothSides].include?(moveData.target)
score += 60
elsif moveData.category != 2 && # Damaging move
moveData.target == :NearOther &&
Effectiveness.ineffective?(pbCalcTypeMod(moveData.type, target, user))
score += 60
end
else
score -= 90
end
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("DisableTargetStatusMoves",
proc { |score, move, user, target, skill, ai, battle|
next 0 if target.effects[PBEffects::Taunt] > 0
}
)
Battle::AI::Handlers::MoveEffectScore.add("DisableTargetHealingMoves",
proc { |score, move, user, target, skill, ai, battle|
next 0 if target.effects[PBEffects::HealBlock] > 0
}
)
Battle::AI::Handlers::MoveEffectScore.add("DisableTargetSoundMoves",
proc { |score, move, user, target, skill, ai, battle|
if target.effects[PBEffects::ThroatChop] == 0 && ai.skill_check(Battle::AI::AILevel.high)
hasSoundMove = false
user.eachMove do |m|
next if !m.soundMove?
hasSoundMove = true
break
end
score += 40 if hasSoundMove
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("DisableTargetMovesKnownByUser",
proc { |score, move, user, target, skill, ai, battle|
next 0 if target.effects[PBEffects::Imprison]
}
)
Battle::AI::Handlers::MoveEffectScore.add("AllBattlersLoseHalfHPUserSkipsNextTurn",
proc { |score, move, user, target, skill, ai, battle|
score += 20 # Shadow moves are more preferable
score += 20 if target.hp >= target.totalhp / 2
score -= 20 if user.hp < user.hp / 2
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("AllBattlersLoseHalfHPUserSkipsNextTurn",
proc { |score, move, user, target, skill, ai, battle|
score += 20 # Shadow moves are more preferable
score -= 40
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("StartShadowSkyWeather",
proc { |score, move, user, target, skill, ai, battle|
score += 20 # Shadow moves are more preferable
if battle.pbCheckGlobalAbility(:AIRLOCK) ||
battle.pbCheckGlobalAbility(:CLOUDNINE)
score -= 90
elsif battle.field.weather == :ShadowSky
score -= 90
end
next score
}
)
Battle::AI::Handlers::MoveEffectScore.add("RemoveAllScreens",
proc { |score, move, user, target, skill, ai, battle|
score += 20 # Shadow moves are more preferable
if target.pbOwnSide.effects[PBEffects::AuroraVeil] > 0 ||
target.pbOwnSide.effects[PBEffects::Reflect] > 0 ||
target.pbOwnSide.effects[PBEffects::LightScreen] > 0 ||
target.pbOwnSide.effects[PBEffects::Safeguard] > 0
score += 30
score -= 90 if user.pbOwnSide.effects[PBEffects::AuroraVeil] > 0 ||
user.pbOwnSide.effects[PBEffects::Reflect] > 0 ||
user.pbOwnSide.effects[PBEffects::LightScreen] > 0 ||
user.pbOwnSide.effects[PBEffects::Safeguard] > 0
else
next 0
end
next score
}
)

View File

@@ -183,9 +183,10 @@ class Battle::AI
alias _battlePalace_pbEnemyShouldWithdraw? pbEnemyShouldWithdraw? alias _battlePalace_pbEnemyShouldWithdraw? pbEnemyShouldWithdraw?
end end
def pbEnemyShouldWithdraw?(idxBattler) def pbEnemyShouldWithdraw?
return _battlePalace_pbEnemyShouldWithdraw?(idxBattler) if !@battlePalace return _battlePalace_pbEnemyShouldWithdraw? if !@battlePalace
thispkmn = @battle.battlers[idxBattler] thispkmn = @user
idxBattler = @user.index
shouldswitch = false shouldswitch = false
if thispkmn.effects[PBEffects::PerishSong] == 1 if thispkmn.effects[PBEffects::PerishSong] == 1
shouldswitch = true shouldswitch = true

View File

@@ -215,8 +215,8 @@ class Battle::AI
alias _battleArena_pbEnemyShouldWithdraw? pbEnemyShouldWithdraw? alias _battleArena_pbEnemyShouldWithdraw? pbEnemyShouldWithdraw?
end end
def pbEnemyShouldWithdraw?(idxBattler) def pbEnemyShouldWithdraw?
return _battleArena_pbEnemyShouldWithdraw?(idxBattler) if !@battleArena return _battleArena_pbEnemyShouldWithdraw? if !@battleArena
return false return false
end end
end end

View File

@@ -19,6 +19,10 @@ class Trainer
return _INTL("{1} {2}", trainer_type_name, @name) return _INTL("{1} {2}", trainer_type_name, @name)
end end
def skill_level
return GameData::TrainerType.try_get(:trainer_type)&.skill_level || 0
end
#============================================================================= #=============================================================================
# Portion of the ID which is visible on the Trainer Card # Portion of the ID which is visible on the Trainer Card