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
#===============================================================================
# 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
def initialize(mod)
@mod = mod
@hash = {}
@addIfs = []
@symbolCache = {}
def initialize
@hash = {}
@add_ifs = []
end
def fromSymbol(sym)
return sym unless sym.is_a?(Symbol) || sym.is_a?(String)
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
def [](id)
return @hash[id] if id && @hash[id]
@add_ifs.each do |add_if|
return add_if[2] if add_if[1].call(id)
end
return ret
return nil
end
def addIf(conditionProc, handler = nil, &handlerBlock)
def add(id, handler = nil, &handlerBlock)
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
@addIfs.push([conditionProc, handler || handlerBlock])
@hash[id] = handler || handlerBlock if id && !id.empty?
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?
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
id = fromSymbol(sym)
@hash[id] = handler || handlerBlock if id
symbol = toSymbol(sym)
@hash[symbol] = handler || handlerBlock if symbol
@add_ifs.push([id, conditionProc, handler || handlerBlock])
end
def copy(src, *dests)
handler = self[src]
if handler
dests.each do |dest|
self.add(dest, handler)
end
end
return if !handler
dests.each { |dest| add(dest, handler) }
end
def [](sym) # 'sym' can be an ID or symbol
id = fromSymbol(sym)
ret = nil
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
@addIfs.each do |addif|
return addif[1] if addif[0].call(id)
end
def remove(key)
if @hash.keys.include?(key)
@hash.delete(key)
else
@add_ifs.delete_if { |add_if| add_if[0] == key }
end
return ret
end
def trigger(sym, *args)
handler = self[sym]
return (handler) ? handler.call(fromSymbol(sym), *args) : nil
end
def 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
#===============================================================================
# A stripped-down version of class HandlerHash which only deals with symbols and
# doesn't care about whether those symbols are defined as constants in a class
# or module.
# A stripped-down version of class HandlerHash which only deals with IDs that
# are symbols.
#===============================================================================
class HandlerHash2
class HandlerHashSymbol
def initialize
@hash = {}
@add_ifs = []
@@ -219,6 +201,7 @@ class HandlerHash2
def clear
@hash.clear
@add_ifs.clear
end
def trigger(sym, *args)
@@ -229,32 +212,63 @@ class HandlerHash2
end
#===============================================================================
# An even more stripped down version of class HandlerHash which just takes
# hashes with keys, no matter what the keys are.
# A specialised version of class HandlerHash which only deals with IDs that 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
def initialize
@hash = {}
@addIfs = []
class HandlerHashEnum
def initialize(mod)
@mod = mod
@hash = {}
@addIfs = []
@symbolCache = {}
end
def [](entry)
def [](sym) # 'sym' can be an ID or symbol
id = fromSymbol(sym)
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
@addIfs.each do |addif|
return addif[1] if addif[0].call(entry)
return addif[1] if addif[0].call(id)
end
end
return ret
end
def add(entry, handler = nil, &handlerBlock)
if ![Proc, Hash].include?(handler.class) && !block_given?
raise ArgumentError, "#{self.class.name} for #{entry.inspect} has no valid handler (#{handler.inspect} was given)"
def fromSymbol(sym)
return sym unless sym.is_a?(Symbol) || sym.is_a?(String)
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
return if !entry || entry.empty?
@hash[entry] = handler || handlerBlock
return ret
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
def addIf(conditionProc, handler = nil, &handlerBlock)
@@ -267,42 +281,31 @@ class HandlerHashBasic
def copy(src, *dests)
handler = self[src]
return if !handler
dests.each { |dest| add(dest, handler) }
end
def remove(key)
@hash.delete(key)
dests.each { |dest| self.add(dest, handler) }
end
def clear
@hash.clear
@addIfs.clear
end
def each
@hash.each_pair { |key, value| yield key, value }
end
def keys
return @hash.keys.clone
end
def trigger(entry, *args)
handler = self[entry]
return handler&.call(*args)
def trigger(sym, *args)
handler = self[sym]
return (handler) ? handler.call(fromSymbol(sym), *args) : nil
end
end
#===============================================================================
#
#===============================================================================
class SpeciesHandlerHash < HandlerHash2
class SpeciesHandlerHash < HandlerHashSymbol
end
class AbilityHandlerHash < HandlerHash2
class AbilityHandlerHash < HandlerHashSymbol
end
class ItemHandlerHash < HandlerHash2
class ItemHandlerHash < HandlerHashSymbol
end
class MoveHandlerHash < HandlerHash2
class MoveHandlerHash < HandlerHashSymbol
end

View File

@@ -81,7 +81,7 @@ module MenuHandlers
@@handlers = {}
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)
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
# 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)
@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
def pbAIRandom(x); return rand(x); end
@@ -44,26 +53,66 @@ class Battle::AI
return Math.sqrt(varianceTimesN / n)
end
#=============================================================================
# Decide whether the opponent should Mega Evolve their Pokémon
#=============================================================================
def pbEnemyShouldMegaEvolve?(idxBattler)
battler = @battle.battlers[idxBattler]
if @battle.pbCanMegaEvolve?(idxBattler) # Simple "always should if possible"
PBDebug.log("[AI] #{battler.pbThis} (#{idxBattler}) will Mega Evolve")
# Decide whether the opponent should Mega Evolve their Pokémon.
def pbEnemyShouldMegaEvolve?
if @battle.pbCanMegaEvolve?(@user.index) # Simple "always should if possible"
PBDebug.log("[AI] #{@user.pbThis} (#{@user.index}) will Mega Evolve")
return true
end
return false
end
#=============================================================================
# Choose an action
#=============================================================================
# Choose an action.
def pbDefaultChooseEnemyCommand(idxBattler)
return if pbEnemyShouldUseItem?(idxBattler)
return if pbEnemyShouldWithdraw?(idxBattler)
set_up(idxBattler)
choices = pbGetMoveScores
return if pbEnemyShouldUseItem?
return if pbEnemyShouldWithdraw?
return if @battle.pbAutoFightMenu(idxBattler)
@battle.pbRegisterMegaEvolution(idxBattler) if pbEnemyShouldMegaEvolve?(idxBattler)
pbChooseMoves(idxBattler)
@battle.pbRegisterMegaEvolution(idxBattler) if pbEnemyShouldMegaEvolve?
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

View File

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

View File

@@ -2,26 +2,24 @@ class Battle::AI
#=============================================================================
# Decide whether the opponent should switch Pokémon
#=============================================================================
def pbEnemyShouldWithdraw?(idxBattler)
return pbEnemyShouldWithdrawEx?(idxBattler, false)
def pbEnemyShouldWithdraw?
return pbEnemyShouldWithdrawEx?(false)
end
def pbEnemyShouldWithdrawEx?(idxBattler, forceSwitch)
return false if @battle.wildBattle?
def pbEnemyShouldWithdrawEx?(forceSwitch)
return false if @wildBattler
shouldSwitch = forceSwitch
batonPass = -1
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
# super-effective and powerful
if !shouldSwitch && battler.turnCount > 0 && skill >= PBTrainerAI.highSkill
target = battler.pbDirectOpposing(true)
if !shouldSwitch && @user.turnCount > 0 && skill_check(AILevel.high)
target = @user.pbDirectOpposing(true)
if !target.fainted? && target.lastMoveUsed &&
(target.level - battler.level).abs <= 6
(target.level - @user.level).abs <= 6
moveData = GameData::Move.get(target.lastMoveUsed)
moveType = moveData.type
typeMod = pbCalcTypeMod(moveType, target, battler)
typeMod = pbCalcTypeMod(moveType, target, @user)
if Effectiveness.super_effective?(typeMod) && moveData.base_damage > 50
switchChance = (moveData.base_damage > 70) ? 30 : 20
shouldSwitch = (pbAIRandom(100) < switchChance)
@@ -29,77 +27,76 @@ class Battle::AI
end
end
# Pokémon can't do anything (must have been in battle for at least 5 rounds)
if !@battle.pbCanChooseAnyMove?(idxBattler) &&
battler.turnCount && battler.turnCount >= 5
if !@battle.pbCanChooseAnyMove?(@user.index) &&
@user.turnCount && @user.turnCount >= 5
shouldSwitch = true
end
# Pokémon is Perish Songed and has Baton Pass
if skill >= PBTrainerAI.highSkill && battler.effects[PBEffects::PerishSong] == 1
battler.eachMoveWithIndex do |m, i|
if skill_check(AILevel.high) && @user.effects[PBEffects::PerishSong] == 1
@user.eachMoveWithIndex do |m, i|
next if m.function != "SwitchOutUserPassOnEffects" # Baton Pass
next if !@battle.pbCanChooseMove?(idxBattler, i, false)
next if !@battle.pbCanChooseMove?(@user.index, i, false)
batonPass = i
break
end
end
# 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
if battler.status == :POISON && battler.statusCount > 0 &&
skill >= PBTrainerAI.highSkill
toxicHP = battler.totalhp / 16
nextToxicHP = toxicHP * (battler.effects[PBEffects::Toxic] + 1)
if battler.hp <= nextToxicHP && battler.hp > toxicHP * 2 && pbAIRandom(100) < 80
shouldSwitch = true
if @user.status == :POISON && @user.statusCount > 0 && skill_check(AILevel.high)
toxicHP = @user.totalhp / 16
nextToxicHP = toxicHP * (@user.effects[PBEffects::Toxic] + 1)
if @user.hp <= nextToxicHP && @user.hp > toxicHP * 2
shouldSwitch = true if pbAIRandom(100) < 80
end
end
# Pokémon is Encored into an unfavourable move
if battler.effects[PBEffects::Encore] > 0 && skill >= PBTrainerAI.mediumSkill
idxEncoredMove = battler.pbEncoredMoveIndex
if @user.effects[PBEffects::Encore] > 0 && skill_check(AILevel.medium)
idxEncoredMove = @user.pbEncoredMoveIndex
if idxEncoredMove >= 0
scoreSum = 0
scoreCount = 0
battler.allOpposing.each do |b|
scoreSum += pbGetMoveScore(battler.moves[idxEncoredMove], battler, b, skill)
@user.allOpposing.each do |b|
scoreSum += pbGetMoveScore(@user.moves[idxEncoredMove], b)
scoreCount += 1
end
if scoreCount > 0 && scoreSum / scoreCount <= 20 && pbAIRandom(100) < 80
shouldSwitch = true
if scoreCount > 0 && scoreSum / scoreCount <= 20
shouldSwitch = true if pbAIRandom(100) < 80
end
end
end
# If there is a single foe and it is resting after Hyper Beam or is
# Truanting (i.e. free turn)
if @battle.pbSideSize(battler.index + 1) == 1 &&
!battler.pbDirectOpposing.fainted? && skill >= PBTrainerAI.highSkill
opp = battler.pbDirectOpposing
if @battle.pbSideSize(@user.index + 1) == 1 &&
!@user.pbDirectOpposing.fainted? && skill_check(AILevel.high)
opp = @user.pbDirectOpposing
if (opp.effects[PBEffects::HyperBeam] > 0 ||
(opp.hasActiveAbility?(:TRUANT) && opp.effects[PBEffects::Truant])) && pbAIRandom(100) < 80
shouldSwitch = false
(opp.hasActiveAbility?(:TRUANT) && opp.effects[PBEffects::Truant]))
shouldSwitch = false if pbAIRandom(100) < 80
end
end
# Sudden Death rule - I'm not sure what this means
if @battle.rules["suddendeath"] && battler.turnCount > 0
if battler.hp <= battler.totalhp / 4 && pbAIRandom(100) < 30
if @battle.rules["suddendeath"] && @user.turnCount > 0
if @user.hp <= @user.totalhp / 4 && pbAIRandom(100) < 30
shouldSwitch = true
elsif battler.hp <= battler.totalhp / 2 && pbAIRandom(100) < 80
elsif @user.hp <= @user.totalhp / 2 && pbAIRandom(100) < 80
shouldSwitch = true
end
end
# Pokémon is about to faint because of Perish Song
if battler.effects[PBEffects::PerishSong] == 1
if @user.effects[PBEffects::PerishSong] == 1
shouldSwitch = true
end
if shouldSwitch
list = []
idxPartyStart, idxPartyEnd = @battle.pbTeamIndexRangeFromBattlerIndex(idxBattler)
@battle.pbParty(idxBattler).each_with_index do |pkmn, i|
idxPartyStart, idxPartyEnd = @battle.pbTeamIndexRangeFromBattlerIndex(@user.index)
@battle.pbParty(@user.index).each_with_index do |pkmn, i|
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
# 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
spikes = battler.pbOwnSide.effects[PBEffects::Spikes]
spikes = @user.pbOwnSide.effects[PBEffects::Spikes]
# Don't switch to this if too little HP
if spikes > 0
spikesDmg = [8, 6, 4][spikes - 1]
@@ -108,17 +105,17 @@ class Battle::AI
end
end
# 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
typeMod = pbCalcTypeModPokemon(pkmn, battler.pbDirectOpposing(true))
typeMod = pbCalcTypeModPokemon(pkmn, @user.pbDirectOpposing(true))
if Effectiveness.super_effective?(typeMod)
# Greater weight if new Pokemon's type is effective against target
weight = 85
end
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
typeMod = pbCalcTypeModPokemon(pkmn, battler.pbDirectOpposing(true))
typeMod = pbCalcTypeModPokemon(pkmn, @user.pbDirectOpposing(true))
if Effectiveness.super_effective?(typeMod)
# Greater weight if new Pokemon's type is effective against target
weight = 60
@@ -129,13 +126,13 @@ class Battle::AI
end
end
if list.length > 0
if batonPass >= 0 && @battle.pbRegisterMove(idxBattler, batonPass, false)
PBDebug.log("[AI] #{battler.pbThis} (#{idxBattler}) will use Baton Pass to avoid Perish Song")
if batonPass >= 0 && @battle.pbRegisterMove(@user.index, batonPass, false)
PBDebug.log("[AI] #{@user.pbThis} (#{@user.index}) will use Baton Pass to avoid Perish Song")
return true
end
if @battle.pbRegisterSwitch(idxBattler, list[0])
PBDebug.log("[AI] #{battler.pbThis} (#{idxBattler}) will switch with " +
@battle.pbParty(idxBattler)[list[0]].name)
if @battle.pbRegisterSwitch(@user.index, list[0])
PBDebug.log("[AI] #{@user.pbThis} (#{@user.index}) will switch with " +
@battle.pbParty(@user.index)[list[0]].name)
return true
end
end
@@ -143,10 +140,10 @@ class Battle::AI
return false
end
#=============================================================================
# Choose a replacement Pokémon
#=============================================================================
# Choose a replacement Pokémon (called directly from @battle, not part of
# action choosing).
def pbDefaultChooseNewEnemy(idxBattler, party)
set_up(idxBattler)
enemies = []
idxPartyStart, idxPartyEnd = @battle.pbTeamIndexRangeFromBattlerIndex(idxBattler)
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
# chosen)
#=============================================================================
def pbChooseMoves(idxBattler)
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
def pbChooseMove(choices)
# Figure out useful information about the choices
totalScore = 0
maxScore = 0
@@ -29,18 +11,9 @@ class Battle::AI
totalScore += c[1]
maxScore = c[1] if maxScore < c[1]
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
if !wildBattler && skill >= PBTrainerAI.highSkill && maxScore > 100
if skill_check(AILevel.high) && maxScore > 100
stDev = pbStdDev(choices)
if stDev >= 40 && pbAIRandom(100) < 90
preferredMoves = []
@@ -51,97 +24,141 @@ class Battle::AI
end
if preferredMoves.length > 0
m = preferredMoves[pbAIRandom(preferredMoves.length)]
PBDebug.log("[AI] #{user.pbThis} (#{user.index}) prefers #{user.moves[m[0]].name}")
@battle.pbRegisterMove(idxBattler, m[0], false)
@battle.pbRegisterTarget(idxBattler, m[2]) if m[2] >= 0
PBDebug.log("[AI] #{@user.pbThis} (#{@user.index}) prefers #{@user.moves[m[0]].name}")
@battle.pbRegisterMove(@user.index, m[0], false)
@battle.pbRegisterTarget(@user.index, m[2]) if m[2] >= 0
return
end
end
end
# 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
if ((maxScore <= 20 && user.turnCount > 2) ||
(maxScore <= 40 && user.turnCount > 5)) && pbAIRandom(100) < 80
badMoves = true
if (maxScore <= 20 && @user.turnCount > 2) ||
(maxScore <= 40 && @user.turnCount > 5)
badMoves = true if pbAIRandom(100) < 80
end
if !badMoves && totalScore < 100 && user.turnCount > 1
if !badMoves && totalScore < 100 && @user.turnCount > 1
badMoves = true
choices.each do |c|
next if !user.moves[c[0]].damagingMove?
next if !@user.moves[c[0]].damagingMove?
badMoves = false
break
end
badMoves = false if badMoves && pbAIRandom(100) < 10
end
if badMoves && pbEnemyShouldWithdrawEx?(idxBattler, true)
if badMoves && pbEnemyShouldWithdrawEx?(true)
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
return
end
end
# If there are no calculated choices, pick one at random
if choices.length == 0
PBDebug.log("[AI] #{user.pbThis} (#{user.index}) doesn't want to use any moves; picking one at random")
user.eachMoveWithIndex do |_m, i|
next if !@battle.pbCanChooseMove?(idxBattler, i, false)
PBDebug.log("[AI] #{@user.pbThis} (#{@user.index}) doesn't want to use any moves; picking one at random")
@user.eachMoveWithIndex do |_m, i|
next if !@battle.pbCanChooseMove?(@user.index, i, false)
choices.push([i, 100, -1]) # Move index, score, target
end
if choices.length == 0 # No moves are physically possible to use; use Struggle
@battle.pbAutoChooseMove(user.index)
@battle.pbAutoChooseMove(@user.index)
end
end
# Randomly choose a move from the choices and register it
randNum = pbAIRandom(totalScore)
choices.each do |c|
randNum -= c[1]
next if randNum >= 0
@battle.pbRegisterMove(idxBattler, c[0], false)
@battle.pbRegisterTarget(idxBattler, c[2]) if c[2] >= 0
@battle.pbRegisterMove(@user.index, c[0], false)
@battle.pbRegisterTarget(@user.index, c[2]) if c[2] >= 0
break
end
# Log the result
if @battle.choices[idxBattler][2]
PBDebug.log("[AI] #{user.pbThis} (#{user.index}) will use #{@battle.choices[idxBattler][2].name}")
if @battle.choices[@user.index][2]
PBDebug.log("[AI] #{@user.pbThis} (#{@user.index}) will use #{@battle.choices[@user.index][2].name}")
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
#=============================================================================
# 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
end
# Trainer Pokémon calculate how much they want to use each of their moves.
def pbRegisterMoveTrainer(user, idxMove, choices, skill)
move = user.moves[idxMove]
target_data = move.pbTarget(user)
def pbRegisterMoveTrainer(idxMove, choices)
move = @user.moves[idxMove]
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) ||
target_data.num_targets == 0
# 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
# 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
elsif target_data.num_targets > 1
# If move affects multiple battlers and you don't choose a particular one
totalScore = 0
@battle.allBattlers.each do |b|
next if !@battle.pbMoveCanTarget?(user.index, b.index, target_data)
score = pbGetMoveScore(move, user, b, skill)
totalScore += ((user.opposes?(b)) ? score : -score)
next if !@battle.pbMoveCanTarget?(@user.index, b.index, target_data)
score = pbGetMoveScore(move, b)
totalScore += ((@user.opposes?(b)) ? score : -score)
end
choices.push([idxMove, totalScore, -1]) if totalScore > 0
else
# If move affects one battler and you have to choose which one
scoresAndTargets = []
@battle.allBattlers.each do |b|
next if !@battle.pbMoveCanTarget?(user.index, b.index, target_data)
next if target_data.targets_foe && !user.opposes?(b)
score = pbGetMoveScore(move, user, b, skill)
next if !@battle.pbMoveCanTarget?(@user.index, b.index, target_data)
next if target_data.targets_foe && !@user.opposes?(b)
score = pbGetMoveScore(move, b)
scoresAndTargets.push([score, b.index]) if score > 0
end
if scoresAndTargets.length > 0
@@ -152,34 +169,134 @@ class Battle::AI
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
#=============================================================================
def pbGetMoveScore(move, user, target, skill = 100)
skill = PBTrainerAI.minimumSkill if skill < PBTrainerAI.minimumSkill
score = 100
score = pbGetMoveScoreFunctionCode(score, move, user, target, skill)
def pbGetMoveScore(move, target = nil)
set_up_move_check(move, target)
# 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
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
if @battle.pbAbleNonActiveCount(user.idxOwnSide) == 0 &&
!(skill >= PBTrainerAI.highSkill && @battle.pbAbleNonActiveCount(target.idxOwnSide) > 0)
if move.statusMove?
score /= 1.5
elsif target.hp <= target.totalhp / 2
score *= 1.5
if @battle.pbAbleNonActiveCount(@user.idxOwnSide) == 0 &&
!(skill_check(AILevel.high) && @battle.pbAbleNonActiveCount(@target.idxOwnSide) > 0)
if @move.statusMove?
score *= 0.9
elsif @target.hp <= @target.totalhp / 2
score *= 1.1
end
end
# Don't prefer attacking the target if they'd be semi-invulnerable
if skill >= PBTrainerAI.highSkill && move.accuracy > 0 &&
(target.semiInvulnerable? || target.effects[PBEffects::SkyDrop] >= 0)
if skill_check(AILevel.high) && @move.accuracy > 0 && @user_faster &&
(@target.semiInvulnerable? || @target.effects[PBEffects::SkyDrop] >= 0)
miss = true
miss = false if user.hasActiveAbility?(:NOGUARD) || target.hasActiveAbility?(:NOGUARD)
if miss && pbRoughStat(user, :SPEED, skill) > pbRoughStat(target, :SPEED, skill)
miss = false if @user.hasActiveAbility?(:NOGUARD)
miss = false if skill_check(AILevel.best) && @target.hasActiveAbility?(:NOGUARD)
if skill_check(AILevel.best) && miss
# Knows what can get past semi-invulnerability
if target.effects[PBEffects::SkyDrop] >= 0 ||
target.inTwoTurnAttack?("TwoTurnAttackInvulnerableInSky",
if @target.effects[PBEffects::SkyDrop] >= 0 ||
@target.inTwoTurnAttack?("TwoTurnAttackInvulnerableInSky",
"TwoTurnAttackInvulnerableInSkyParalyzeTarget",
"TwoTurnAttackInvulnerableInSkyTargetCannotAct")
miss = false if move.hitsFlyingTargets?
@@ -189,107 +306,388 @@ class Battle::AI
miss = false if move.hitsDivingTargets?
end
end
score -= 80 if miss
score = 0 if miss
end
# Pick a good move for the Choice items
if user.hasActiveItem?([:CHOICEBAND, :CHOICESPECS, :CHOICESCARF]) ||
user.hasActiveAbility?(:GORILLATACTICS)
if move.baseDamage >= 60
score += 60
elsif move.damagingMove?
score += 30
elsif move.function == "UserTargetSwapItems"
score += 70 # Trick
else
score -= 60
end
if @user.hasActiveItem?([:CHOICEBAND, :CHOICESPECS, :CHOICESCARF]) ||
@user.hasActiveAbility?(:GORILLATACTICS)
# Really don't prefer status moves (except Trick)
score *= 0.1 if @move.statusMove? && @move.function != "UserTargetSwapItems"
# Don't prefer moves of certain types
move_type = pbRoughType(@move)
# Most unpreferred types are 0x effective against another type, except
# Fire/Water/Grass
# TODO: Actually check through the types for 0x instead of hardcoding
# them.
# 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
# If user is asleep, prefer moves that are usable while asleep
if user.status == :SLEEP && !move.usableWhenAsleep?
user.eachMove do |m|
next unless m.usableWhenAsleep?
score -= 60
break
end
# If user is asleep, don't prefer moves that can't be used while asleep
if skill_check(AILevel.medium) && @user.asleep? && @user.statusCount > 1 &&
!@move.usableWhenAsleep?
score *= 0.2
end
# If user is frozen, prefer a move that can thaw the user
if user.status == :FROZEN
if move.thawsUser?
score += 40
if skill_check(AILevel.medium) && @user.status == :FROZEN
if @move.thawsUser?
score += 30
else
user.eachMove do |m|
@user.eachMove do |m|
next unless m.thawsUser?
score -= 60
score = 0 # Discard this move if user knows another move that thaws
break
end
end
end
# If target is frozen, don't prefer moves that could thaw them
if target.status == :FROZEN
user.eachMove do |m|
next if m.thawsUser?
score -= 60
break
if @target.status == :FROZEN
if pbRoughType(@move) == :FIRE || (Settings::MECHANICS_GENERATION >= 6 && @move.thawsUser?)
score *= 0.1
end
end
end
# Don't prefer moves that are ineffective because of abilities or effects
return 0 if pbCheckMoveImmunity(score, move, user, target, skill)
# 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, user, target, skill)
score *= accuracy / 100.0
score = 0 if score <= 10 && skill >= PBTrainerAI.highSkill
# Don't prefer hitting a wild shiny Pokémon
if @battle.wildBattle? && @target.opposes? && @target.shiny?
score *= 0.15
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 = 0 if score < 0
return score
end
#=============================================================================
# Add to a move's score based on how much damage it will deal (as a percentage
# of the target's current HP)
# Calculate how much damage a move is likely to do to a given target (as a
# percentage of the target's current HP)
#=============================================================================
def pbGetMoveScoreDamage(score, move, user, target, skill)
return 0 if score <= 0
def pbGetDamagingMoveBaseScore
# 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)
baseDmg = pbMoveBaseDamage(move, user, target, skill)
realDamage = pbRoughDamage(move, user, target, skill, baseDmg)
# Account for accuracy of move
accuracy = pbRoughAccuracy(move, user, target, skill)
realDamage *= accuracy / 100.0
base_damage = pbMoveBaseDamage(@move, @target)
calc_damage = pbRoughDamage(@move, @target, base_damage)
# TODO: Maybe move this check elsewhere? Note that Reborn's base score does
# not include this halving, but the predicted damage does.
# Two-turn attacks waste 2 turns to deal one lot of damage
if move.chargingTurnMove? || move.function == "AttackAndSkipNextTurn" # Hyper Beam
realDamage *= 2 / 3 # Not halved because semi-invulnerable during use or hits first turn
end
# Prefer flinching external effects (note that move effects which cause
# flinching are dealt with in the function code part of score calculation)
if skill >= PBTrainerAI.mediumSkill && !move.flinchingMove? &&
!target.hasActiveAbility?(:INNERFOCUS) &&
!target.hasActiveAbility?(:SHIELDDUST) &&
target.effects[PBEffects::Substitute] == 0
canFlinch = false
if user.hasActiveItem?([:KINGSROCK, :RAZORFANG]) ||
user.hasActiveAbility?(:STENCH)
canFlinch = true
calc_damage /= 2 if @move.chargingTurnMove?
# TODO: Maybe move this check elsewhere?
# Increased critical hit rate
if skill_check(AILevel.medium)
crit_stage = pbRoughCriticalHitStage(@move, @target)
if crit_stage >= 0
crit_fraction = (crit_stage > 50) ? 1 : Battle::Move::CRITICAL_HIT_RATIOS[crit_stage]
crit_mult = (Settings::NEW_CRITICAL_HIT_RATE_MECHANICS) ? 0.5 : 1
calc_damage *= (1 + crit_mult / crit_fraction)
end
realDamage *= 1.3 if canFlinch
end
# 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
# damagePercentage /= 2 if damagePercentage<20
# damage_percentage /= 2 if damage_percentage < 20
# 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
damagePercentage = 120 if damagePercentage > 120 # Treat all lethal moves the same
damagePercentage += 40 if damagePercentage > 100 # Prefer moves likely to be lethal
score += damagePercentage.to_i
return score
damage_percentage = 110 if damage_percentage > 110 # Treat all lethal moves the same
damage_percentage += 40 if damage_percentage > 100 # Prefer moves likely to be lethal
return damage_percentage.to_i
end
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

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

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?
end
def pbEnemyShouldWithdraw?(idxBattler)
return _battlePalace_pbEnemyShouldWithdraw?(idxBattler) if !@battlePalace
thispkmn = @battle.battlers[idxBattler]
def pbEnemyShouldWithdraw?
return _battlePalace_pbEnemyShouldWithdraw? if !@battlePalace
thispkmn = @user
idxBattler = @user.index
shouldswitch = false
if thispkmn.effects[PBEffects::PerishSong] == 1
shouldswitch = true

View File

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

View File

@@ -19,6 +19,10 @@ class Trainer
return _INTL("{1} {2}", trainer_type_name, @name)
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