Merge branch 'ai' into dev

This commit is contained in:
Maruno17
2023-05-14 18:36:25 +01:00
160 changed files with 75932 additions and 5730 deletions

View File

@@ -7,7 +7,9 @@ module PBDebug
rescue
PBDebug.log("")
PBDebug.log("**Exception: #{$!.message}")
PBDebug.log($!.backtrace.inspect.to_s)
backtrace = ""
$!.backtrace.each { |line| backtrace += line + "\r\n" }
PBDebug.log(backtrace)
PBDebug.log("")
pbPrintException($!) # if $INTERNAL
PBDebug.flush
@@ -16,14 +18,53 @@ module PBDebug
def self.flush
if $DEBUG && $INTERNAL && @@log.length > 0
File.open("Data/debuglog.txt", "a+b") { |f| f.write(@@log.to_s) }
File.open("Data/debuglog.txt", "a+b") { |f| f.write(@@log.join) }
end
@@log.clear
end
def self.log(msg)
if $DEBUG && $INTERNAL
@@log.push("#{msg}\r\n")
echoln msg.gsub("%", "%%")
@@log.push(msg + "\r\n")
PBDebug.flush # if @@log.length > 1024
end
end
def self.log_header(msg)
if $DEBUG && $INTERNAL
echoln Console.markup_style(msg.gsub("%", "%%"), text: :light_purple)
@@log.push(msg + "\r\n")
PBDebug.flush # if @@log.length > 1024
end
end
def self.log_message(msg)
if $DEBUG && $INTERNAL
msg = "\"" + msg + "\""
echoln Console.markup_style(msg.gsub("%", "%%"), text: :dark_gray)
@@log.push(msg + "\r\n")
PBDebug.flush # if @@log.length > 1024
end
end
def self.log_ai(msg)
if $DEBUG && $INTERNAL
msg = "[AI] " + msg
echoln msg.gsub("%", "%%")
@@log.push(msg + "\r\n")
PBDebug.flush # if @@log.length > 1024
end
end
def self.log_score_change(amt, msg)
return if amt == 0
if $DEBUG && $INTERNAL
sign = (amt > 0) ? "+" : "-"
amt_text = sprintf("%3d", amt.abs)
msg = " #{sign}#{amt_text}: #{msg}"
echoln msg.gsub("%", "%%")
@@log.push(msg + "\r\n")
PBDebug.flush # if @@log.length > 1024
end
end

View File

@@ -104,4 +104,11 @@ module Settings
CHECK_EVOLUTION_AFTER_ALL_BATTLES = (MECHANICS_GENERATION >= 6)
# Whether fainted Pokémon can try to evolve after a battle.
CHECK_EVOLUTION_FOR_FAINTED_POKEMON = true
#=============================================================================
# Whether wild Pokémon with the "Legendary", "Mythical" or "UltraBeast" flag
# (as defined in pokemon.txt) have a smarter AI. Their skill level is set to
# 32, which is a medium skill level.
SMARTER_WILD_LEGENDARY_POKEMON = true
end

View File

@@ -91,94 +91,61 @@ 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 = {}
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
def [](id)
return @hash[id] if id && @hash[id]
return 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 ret
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])
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
@hash[id] = handler || handlerBlock if id && !id.empty?
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
end
return ret
end
def trigger(sym, *args)
handler = self[sym]
return (handler) ? handler.call(fromSymbol(sym), *args) : nil
def remove(key)
@hash.delete(key)
end
def clear
@hash.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. Also contains an add_ifs hash for code that applies to multiple
# IDs (determined by its condition proc).
#===============================================================================
class HandlerHash2
class HandlerHashSymbol
def initialize
@hash = {}
@add_ifs = {}
@@ -219,6 +186,7 @@ class HandlerHash2
def clear
@hash.clear
@add_ifs.clear
end
def trigger(sym, *args)
@@ -229,66 +197,100 @@ 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 = {}
class HandlerHashEnum
def initialize(mod)
@mod = mod
@hash = {}
@addIfs = []
@symbolCache = {}
end
def [](entry)
return @hash[entry] if entry && @hash[entry]
return nil
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 [](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
end
return if !entry || entry.empty?
@hash[entry] = handler || handlerBlock
return ret
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
end
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)
if ![Proc, Hash].include?(handler.class) && !block_given?
raise ArgumentError, "addIf call for #{self.class.name} has no valid handler (#{handler.inspect} was given)"
end
@addIfs.push([conditionProc, handler || handlerBlock])
end
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

@@ -88,6 +88,14 @@ module GameData
return GameData::Type.get(@type).special?
end
def damaging?
return @category != 2
end
def status?
return @category == 2
end
def hidden_move?
GameData::Item.each do |i|
return true if i.is_HM? && i.move == @id

View File

@@ -84,6 +84,7 @@ class Battle
attr_accessor :poke_ball_failed # Set after first_poke_ball to prevent it being set again
attr_reader :switching # True if during the switching phase of the round
attr_reader :futureSight # True if Future Sight is hitting
attr_reader :command_phase
attr_reader :endOfRound # True during the end of round
attr_accessor :moldBreaker # True if Mold Breaker applies
attr_reader :struggle # The Struggle move
@@ -159,15 +160,12 @@ class Battle
@lastMoveUser = -1
@switching = false
@futureSight = false
@command_phase = false
@endOfRound = false
@moldBreaker = false
@runCommand = 0
@nextPickupUse = 0
if GameData::Move.exists?(:STRUGGLE)
@struggle = Move.from_pokemon_move(self, Pokemon::Move.new(:STRUGGLE))
else
@struggle = Move::Struggle.new(self, nil)
end
@struggle = Move::Struggle.new(self, nil)
@mega_rings = []
GameData::Item.each { |item| @mega_rings.push(item.id) if item.has_flag?("MegaRing") }
@battleAI = AI.new(self)

View File

@@ -246,7 +246,8 @@ class Battle
#=============================================================================
def pbStartBattle
PBDebug.log("")
PBDebug.log("******************************************")
PBDebug.log("================================================================")
PBDebug.log("")
logMsg = "[Started battle] "
if @sideSizes[0] == 1 && @sideSizes[1] == 1
logMsg += "Single "
@@ -278,6 +279,7 @@ class Battle
def pbStartBattleCore
# Set up the battlers on each side
sendOuts = pbSetUpSides
@battleAI.create_ai_objects
# Create all the sprites and play the battle intro animation
@scene.pbStartBattle(self)
# Show trainers on both sides sending out Pokémon
@@ -321,7 +323,7 @@ class Battle
@turnCount = 0
loop do # Now begin the battle loop
PBDebug.log("")
PBDebug.log("***Round #{@turnCount + 1}***")
PBDebug.log_header("===== Round #{@turnCount + 1} =====")
if @debug && @turnCount >= 100
@decision = pbDecisionOnTime
PBDebug.log("")
@@ -407,7 +409,8 @@ class Battle
##### WIN #####
when 1
PBDebug.log("")
PBDebug.log("***Player won***")
PBDebug.log_header("===== Player won =====")
PBDebug.log("")
if trainerBattle?
@scene.pbTrainerBattleSuccess
case @opponent.length
@@ -426,6 +429,7 @@ class Battle
msg = "..." if !msg || msg.empty?
pbDisplayPaused(msg.gsub(/\\[Pp][Nn]/, pbPlayer.name))
end
PBDebug.log("")
end
# Gain money from winning a trainer battle, and from Pay Day
pbGainMoney if @decision != 4
@@ -434,8 +438,9 @@ class Battle
##### LOSE, DRAW #####
when 2, 5
PBDebug.log("")
PBDebug.log("***Player lost***") if @decision == 2
PBDebug.log("***Player drew with opponent***") if @decision == 5
PBDebug.log_header("===== Player lost =====") if @decision == 2
PBDebug.log_header("===== Player drew with opponent =====") if @decision == 5
PBDebug.log("")
if @internalBattle
pbDisplayPaused(_INTL("You have no more Pokémon that can fight!"))
if trainerBattle?
@@ -461,10 +466,14 @@ class Battle
msg = "..." if !msg || msg.empty?
pbDisplayPaused(msg.gsub(/\\[Pp][Nn]/, pbPlayer.name))
end
PBDebug.log("")
end
end
##### CAUGHT WILD POKÉMON #####
when 4
PBDebug.log("")
PBDebug.log_header("===== Pokémon caught =====")
PBDebug.log("")
@scene.pbWildBattleSuccess if !Settings::GAIN_EXP_FOR_CAPTURE
end
# Register captured Pokémon in the Pokédex, and store them

View File

@@ -243,14 +243,14 @@ class Battle
end
end
# Write the priority order to the debug log
logMsg = (fullCalc) ? "[Round order] " : "[Round order recalculated] "
comma = false
@priority.each do |entry|
logMsg += ", " if comma
logMsg += "#{entry[0].pbThis(comma)} (#{entry[0].index})"
comma = true
if fullCalc && $INTERNAL
logMsg = "[Round order] "
@priority.each_with_index do |entry, i|
logMsg += ", " if i > 0
logMsg += "#{entry[0].pbThis(i > 0)} (#{entry[0].index})"
end
PBDebug.log(logMsg)
end
PBDebug.log(logMsg)
end
end

View File

@@ -6,7 +6,7 @@ class Battle
# battle.
# NOTE: Messages are only shown while in the party screen when choosing a
# command for the next round.
def pbCanSwitchLax?(idxBattler, idxParty, partyScene = nil)
def pbCanSwitchIn?(idxBattler, idxParty, partyScene = nil)
return true if idxParty < 0
party = pbParty(idxBattler)
return false if idxParty >= party.length
@@ -18,8 +18,7 @@ class Battle
if !pbIsOwner?(idxBattler, idxParty)
if partyScene
owner = pbGetOwnerFromPartyIndex(idxBattler, idxParty)
partyScene.pbDisplay(_INTL("You can't switch {1}'s Pokémon with one of yours!",
owner.name))
partyScene.pbDisplay(_INTL("You can't switch {1}'s Pokémon with one of yours!", owner.name))
end
return false
end
@@ -35,30 +34,15 @@ class Battle
end
# Check whether the currently active Pokémon (at battler index idxBattler) can
# switch out (and that its replacement at party index idxParty can switch in).
# NOTE: Messages are only shown while in the party screen when choosing a
# command for the next round.
def pbCanSwitch?(idxBattler, idxParty = -1, partyScene = nil)
# Check whether party Pokémon can switch in
return false if !pbCanSwitchLax?(idxBattler, idxParty, partyScene)
# Make sure another battler isn't already choosing to switch to the party
# Pokémon
allSameSideBattlers(idxBattler).each do |b|
next if choices[b.index][0] != :SwitchOut || choices[b.index][1] != idxParty
partyScene&.pbDisplay(_INTL("{1} has already been selected.",
pbParty(idxBattler)[idxParty].name))
return false
end
# Check whether battler can switch out
# switch out.
def pbCanSwitchOut?(idxBattler, partyScene = nil)
battler = @battlers[idxBattler]
return true if battler.fainted?
# Ability/item effects that allow switching no matter what
if battler.abilityActive? &&
Battle::AbilityEffects.triggerCertainSwitching(battler.ability, battler, self)
if battler.abilityActive? && Battle::AbilityEffects.triggerCertainSwitching(battler.ability, battler, self)
return true
end
if battler.itemActive? &&
Battle::ItemEffects.triggerCertainSwitching(battler.item, battler, self)
if battler.itemActive? && Battle::ItemEffects.triggerCertainSwitching(battler.item, battler, self)
return true
end
# Other certain switching effects
@@ -72,25 +56,42 @@ class Battle
allOtherSideBattlers(idxBattler).each do |b|
next if !b.abilityActive?
if Battle::AbilityEffects.triggerTrappingByTarget(b.ability, battler, b, self)
partyScene&.pbDisplay(_INTL("{1}'s {2} prevents switching!",
b.pbThis, b.abilityName))
partyScene&.pbDisplay(_INTL("{1}'s {2} prevents switching!", b.pbThis, b.abilityName))
return false
end
end
allOtherSideBattlers(idxBattler).each do |b|
next if !b.itemActive?
if Battle::ItemEffects.triggerTrappingByTarget(b.item, battler, b, self)
partyScene&.pbDisplay(_INTL("{1}'s {2} prevents switching!",
b.pbThis, b.itemName))
partyScene&.pbDisplay(_INTL("{1}'s {2} prevents switching!", b.pbThis, b.itemName))
return false
end
end
return true
end
# Check whether the currently active Pokémon (at battler index idxBattler) can
# switch out (and that its replacement at party index idxParty can switch in).
# NOTE: Messages are only shown while in the party screen when choosing a
# command for the next round.
def pbCanSwitch?(idxBattler, idxParty = -1, partyScene = nil)
# Check whether party Pokémon can switch in
return false if !pbCanSwitchIn?(idxBattler, idxParty, partyScene)
# Make sure another battler isn't already choosing to switch to the party
# Pokémon
allSameSideBattlers(idxBattler).each do |b|
next if choices[b.index][0] != :SwitchOut || choices[b.index][1] != idxParty
partyScene&.pbDisplay(_INTL("{1} has already been selected.",
pbParty(idxBattler)[idxParty].name))
return false
end
# Check whether battler can switch out
return pbCanSwitchOut?(idxBattler, partyScene)
end
def pbCanChooseNonActive?(idxBattler)
pbParty(idxBattler).each_with_index do |_pkmn, i|
return true if pbCanSwitchLax?(idxBattler, i)
return true if pbCanSwitchIn?(idxBattler, i)
end
return false
end
@@ -113,7 +114,7 @@ class Battle
ret = -1
@scene.pbPartyScreen(idxBattler, canCancel) do |idxParty, partyScene|
if checkLaxOnly
next false if !pbCanSwitchLax?(idxBattler, idxParty, partyScene)
next false if !pbCanSwitchIn?(idxBattler, idxParty, partyScene)
elsif !pbCanSwitch?(idxBattler, idxParty, partyScene)
next false
end
@@ -129,8 +130,8 @@ class Battle
# For choosing a replacement Pokémon when prompted in the middle of other
# things happening (U-turn, Baton Pass, in def pbEORSwitch).
def pbSwitchInBetween(idxBattler, checkLaxOnly = false, canCancel = false)
return pbPartyScreen(idxBattler, checkLaxOnly, canCancel) if pbOwnedByPlayer?(idxBattler)
return @battleAI.pbDefaultChooseNewEnemy(idxBattler, pbParty(idxBattler))
return pbPartyScreen(idxBattler, checkLaxOnly, canCancel) if !@controlPlayer && pbOwnedByPlayer?(idxBattler)
return @battleAI.pbDefaultChooseNewEnemy(idxBattler)
end
#=============================================================================
@@ -206,7 +207,7 @@ class Battle
if random
choices = [] # Find all Pokémon that can switch in
eachInTeamFromBattlerIndex(idxBattler) do |_pkmn, i|
choices.push(i) if pbCanSwitchLax?(idxBattler, i)
choices.push(i) if pbCanSwitchIn?(idxBattler, i)
end
return -1 if choices.length == 0
return choices[pbRandom(choices.length)]

View File

@@ -140,7 +140,7 @@ class Battle
def pbPartyMenu(idxBattler)
ret = -1
if @debug
ret = @battleAI.pbDefaultChooseNewEnemy(idxBattler, pbParty(idxBattler))
ret = @battleAI.pbDefaultChooseNewEnemy(idxBattler)
else
ret = pbPartyScreen(idxBattler, false, true, true)
end
@@ -172,6 +172,7 @@ class Battle
# Command phase
#=============================================================================
def pbCommandPhase
@command_phase = true
@scene.pbBeginCommandPhase
# Reset choices if commands can be shown
@battlers.each_with_index do |b, i|
@@ -186,8 +187,12 @@ class Battle
end
# Choose actions for the round (player first, then AI)
pbCommandPhaseLoop(true) # Player chooses their actions
return if @decision != 0 # Battle ended, stop choosing actions
if @decision != 0 # Battle ended, stop choosing actions
@command_phase = false
return
end
pbCommandPhaseLoop(false) # AI chooses their actions
@command_phase = false
end
def pbCommandPhaseLoop(isPlayer)
@@ -200,8 +205,11 @@ class Battle
idxBattler += 1
break if idxBattler >= @battlers.length
next if !@battlers[idxBattler] || pbOwnedByPlayer?(idxBattler) != isPlayer
next if @choices[idxBattler][0] != :None # Action is forced, can't choose one
next if !pbCanShowCommands?(idxBattler) # Action is forced, can't choose one
if @choices[idxBattler][0] != :None || !pbCanShowCommands?(idxBattler)
# Action is forced, can't choose one
PBDebug.log_ai("#{@battlers[idxBattler].pbThis} (#{idxBattler}) is forced to use a multi-turn move")
next
end
# AI controls this battler
if @controlPlayer || !pbOwnedByPlayer?(idxBattler)
@battleAI.pbDefaultChooseEnemyCommand(idxBattler)

View File

@@ -186,9 +186,9 @@ class Battle
end
b.effects[PBEffects::Rage] = false if !pbChoseMoveFunctionCode?(i, "StartRaiseUserAtk1WhenDamaged")
end
PBDebug.log("")
# Calculate move order for this round
pbCalculatePriority(true)
PBDebug.log("")
# Perform actions
pbAttackPhasePriorityChangeMessages
pbAttackPhaseCall

View File

@@ -134,6 +134,8 @@ class Battle
#=============================================================================
def pbEORSeaOfFireDamage(priority)
2.times do |side|
next if sides[side].effects[PBEffects::SeaOfFire] == 0
sides[side].effects[PBEffects::SeaOfFire] -= 1
next if sides[side].effects[PBEffects::SeaOfFire] == 0
pbCommonAnimation("SeaOfFire") if side == 0
pbCommonAnimation("SeaOfFireOpp") if side == 1
@@ -594,7 +596,7 @@ class Battle
#=============================================================================
def pbEndOfRoundPhase
PBDebug.log("")
PBDebug.log("[End of round]")
PBDebug.log("[End of round #{@turnCount + 1}]")
@endOfRound = true
@scene.pbBeginEndOfRoundPhase
pbCalculatePriority # recalculate speeds
@@ -747,6 +749,7 @@ class Battle
battler.lastHPLostFromFoe = 0
battler.droppedBelowHalfHP = false
battler.statsDropped = false
battler.tookMoveDamageThisRound = false
battler.tookDamageThisRound = false
battler.tookPhysicalHit = false
battler.statsRaisedThisRound = false

View File

@@ -37,6 +37,7 @@ class Battle::Battler
attr_accessor :currentMove # ID of multi-turn move currently being used
attr_accessor :droppedBelowHalfHP # Used for Emergency Exit/Wimp Out
attr_accessor :statsDropped # Used for Eject Pack
attr_accessor :tookMoveDamageThisRound # Boolean for Focus Punch
attr_accessor :tookDamageThisRound # Boolean for whether self took damage this round
attr_accessor :tookPhysicalHit
attr_accessor :statsRaisedThisRound # Boolean for whether self's stat(s) raised this round
@@ -44,6 +45,13 @@ class Battle::Battler
attr_accessor :canRestoreIceFace # Whether Hail started in the round
attr_accessor :damageState
# These arrays should all have the same number of values in them
STAT_STAGE_MULTIPLIERS = [2, 2, 2, 2, 2, 2, 2, 3, 4, 5, 6, 7, 8]
STAT_STAGE_DIVISORS = [8, 7, 6, 5, 4, 3, 2, 2, 2, 2, 2, 2, 2]
ACC_EVA_STAGE_MULTIPLIERS = [3, 3, 3, 3, 3, 3, 3, 4, 5, 6, 7, 8, 9]
ACC_EVA_STAGE_DIVISORS = [9, 8, 7, 6, 5, 4, 3, 3, 3, 3, 3, 3, 3]
STAT_STAGE_MAXIMUM = 6 # Is also the minimum (-6)
#=============================================================================
# Complex accessors
#=============================================================================
@@ -239,10 +247,8 @@ class Battle::Battler
#=============================================================================
def pbSpeed
return 1 if fainted?
stageMul = [2, 2, 2, 2, 2, 2, 2, 3, 4, 5, 6, 7, 8]
stageDiv = [8, 7, 6, 5, 4, 3, 2, 2, 2, 2, 2, 2, 2]
stage = @stages[:SPEED] + 6
speed = @speed * stageMul[stage] / stageDiv[stage]
stage = @stages[:SPEED] + STAT_STAGE_MAXIMUM
speed = @speed * STAT_STAGE_MULTIPLIERS[stage] / STAT_STAGE_DIVISORS[stage]
speedMult = 1.0
# Ability effects that alter calculated Speed
if abilityActive?

View File

@@ -125,27 +125,28 @@ class Battle::Battler
@effects[PBEffects::Substitute] = 0
@effects[PBEffects::Telekinesis] = 0
end
@fainted = (@hp == 0)
@lastAttacker = []
@lastFoeAttacker = []
@lastHPLost = 0
@lastHPLostFromFoe = 0
@droppedBelowHalfHP = false
@statsDropped = false
@tookDamageThisRound = false
@tookPhysicalHit = false
@statsRaisedThisRound = false
@statsLoweredThisRound = false
@canRestoreIceFace = false
@lastMoveUsed = nil
@lastMoveUsedType = nil
@lastRegularMoveUsed = nil
@lastRegularMoveTarget = -1
@lastRoundMoved = -1
@lastMoveFailed = false
@lastRoundMoveFailed = false
@movesUsed = []
@turnCount = 0
@fainted = (@hp == 0)
@lastAttacker = []
@lastFoeAttacker = []
@lastHPLost = 0
@lastHPLostFromFoe = 0
@droppedBelowHalfHP = false
@statsDropped = false
@tookMoveDamageThisRound = false
@tookDamageThisRound = false
@tookPhysicalHit = false
@statsRaisedThisRound = false
@statsLoweredThisRound = false
@canRestoreIceFace = false
@lastMoveUsed = nil
@lastMoveUsedType = nil
@lastRegularMoveUsed = nil
@lastRegularMoveTarget = -1
@lastRoundMoved = -1
@lastMoveFailed = false
@lastRoundMoveFailed = false
@movesUsed = []
@turnCount = 0
@effects[PBEffects::Attract] = -1
@battle.allBattlers.each do |b| # Other battlers no longer attracted to self
b.effects[PBEffects::Attract] = -1 if b.effects[PBEffects::Attract] == @index

View File

@@ -8,13 +8,14 @@ class Battle::Battler
amt = 1 if amt < 1 && !fainted?
oldHP = @hp
self.hp -= amt
PBDebug.log("[HP change] #{pbThis} lost #{amt} HP (#{oldHP}=>#{@hp})") if amt > 0
PBDebug.log("[HP change] #{pbThis} lost #{amt} HP (#{oldHP} -> #{@hp})") if amt > 0
raise _INTL("HP less than 0") if @hp < 0
raise _INTL("HP greater than total HP") if @hp > @totalhp
@battle.scene.pbHPChanged(self, oldHP, anim) if anyAnim && amt > 0
if amt > 0 && registerDamage
@droppedBelowHalfHP = true if @hp < @totalhp / 2 && @hp + amt >= @totalhp / 2
@tookDamageThisRound = true
@tookMoveDamageThisRound = true
end
return amt
end
@@ -25,7 +26,7 @@ class Battle::Battler
amt = 1 if amt < 1 && @hp < @totalhp
oldHP = @hp
self.hp += amt
PBDebug.log("[HP change] #{pbThis} gained #{amt} HP (#{oldHP}=>#{@hp})") if amt > 0
PBDebug.log("[HP change] #{pbThis} gained #{amt} HP (#{oldHP} -> #{@hp})") if amt > 0
raise _INTL("HP less than 0") if @hp < 0
raise _INTL("HP greater than total HP") if @hp > @totalhp
@battle.scene.pbHPChanged(self, oldHP, anim) if anyAnim && amt > 0

View File

@@ -171,7 +171,7 @@ class Battle::Battler
return true
end
def pbCanSynchronizeStatus?(newStatus, target)
def pbCanSynchronizeStatus?(newStatus, user)
return false if fainted?
# Trying to replace a status problem with another one
return false if self.status != :NONE
@@ -181,8 +181,8 @@ class Battle::Battler
hasImmuneType = false
case newStatus
when :POISON
# NOTE: target will have Synchronize, so it can't have Corrosion.
if !(target && target.hasActiveAbility?(:CORROSION))
# NOTE: user will have Synchronize, so it can't have Corrosion.
if !(user && user.hasActiveAbility?(:CORROSION))
hasImmuneType |= pbHasType?(:POISON)
hasImmuneType |= pbHasType?(:STEEL)
end
@@ -205,6 +205,7 @@ class Battle::Battler
return false
end
# Safeguard immunity
# NOTE: user will have Synchronize, so it can't have Infiltrator.
if pbOwnSide.effects[PBEffects::Safeguard] > 0 &&
!(user && user.hasActiveAbility?(:INFILTRATOR))
return false

View File

@@ -3,7 +3,7 @@ class Battle::Battler
# Increase stat stages
#=============================================================================
def statStageAtMax?(stat)
return @stages[stat] >= 6
return @stages[stat] >= STAT_STAGE_MAXIMUM
end
def pbCanRaiseStatStage?(stat, user = nil, move = nil, showFailMsg = false, ignoreContrary = false)
@@ -33,11 +33,11 @@ class Battle::Battler
increment *= 2 if hasActiveAbility?(:SIMPLE)
end
# Change the stat stage
increment = [increment, 6 - @stages[stat]].min
increment = [increment, STAT_STAGE_MAXIMUM - @stages[stat]].min
if increment > 0
stat_name = GameData::Stat.get(stat).name
new = @stages[stat] + increment
PBDebug.log("[Stat change] #{pbThis}'s #{stat_name}: #{@stages[stat]} -> #{new} (+#{increment})")
PBDebug.log("[Stat change] #{pbThis}'s #{stat_name} changed by +#{increment} (#{@stages[stat]} -> #{new})")
@stages[stat] += increment
@statsRaisedThisRound = true
end
@@ -117,7 +117,7 @@ class Battle::Battler
# Decrease stat stages
#=============================================================================
def statStageAtMin?(stat)
return @stages[stat] <= -6
return @stages[stat] <= -STAT_STAGE_MAXIMUM
end
def pbCanLowerStatStage?(stat, user = nil, move = nil, showFailMsg = false,
@@ -183,11 +183,11 @@ class Battle::Battler
increment *= 2 if hasActiveAbility?(:SIMPLE)
end
# Change the stat stage
increment = [increment, 6 + @stages[stat]].min
increment = [increment, STAT_STAGE_MAXIMUM + @stages[stat]].min
if increment > 0
stat_name = GameData::Stat.get(stat).name
new = @stages[stat] - increment
PBDebug.log("[Stat change] #{pbThis}'s #{stat_name}: #{@stages[stat]} -> #{new} (-#{increment})")
PBDebug.log("[Stat change] #{pbThis}'s #{stat_name} changed by -#{increment} (#{@stages[stat]} -> #{new})")
@stages[stat] -= increment
@statsLoweredThisRound = true
@statsDropped = true

View File

@@ -282,7 +282,8 @@ class Battle::Battler
# NOTE: A Pokémon using Bug Bite/Pluck, and a Pokémon having an item thrown at
# it via Fling, will gain the effect of the item even if the Pokémon is
# affected by item-negating effects.
# item_to_use is an item ID for Bug Bite/Pluck and Fling, and nil otherwise.
# item_to_use is an item ID for Stuff Cheeks, Teatime, Bug Bite/Pluck and
# Fling, and nil otherwise.
# fling is for Fling only.
def pbHeldItemTriggerCheck(item_to_use = nil, fling = false)
return if fainted?
@@ -386,7 +387,7 @@ class Battle::Battler
#=============================================================================
# Item effects
#=============================================================================
def pbConfusionBerry(item_to_use, forced, flavor, confuse_msg)
def pbConfusionBerry(item_to_use, forced, confuse_stat, confuse_msg)
return false if !forced && !canHeal?
return false if !forced && !canConsumePinchBerry?(Settings::MECHANICS_GENERATION >= 7)
used_item_name = GameData::Item.get(item_to_use).name
@@ -414,12 +415,9 @@ class Battle::Battler
@battle.pbDisplay(_INTL("{1} restored its health using its {2}!", pbThis, used_item_name))
end
end
flavor_stat = [:ATTACK, :DEFENSE, :SPEED, :SPECIAL_ATTACK, :SPECIAL_DEFENSE][flavor]
self.nature.stat_changes.each do |change|
next if change[1] > 0 || change[0] != flavor_stat
if self.nature.stat_changes.any? { |val| val[0] == confuse_stat && val[1] < 0 }
@battle.pbDisplay(confuse_msg)
pbConfuse if pbCanConfuseSelf?(false)
break
end
return true
end

View File

@@ -47,7 +47,7 @@ class Battle::Battler
return false
end
# Use the move
PBDebug.log("[Move usage] #{pbThis} started using #{choice[2].name}")
PBDebug.log("[Use move] #{pbThis} (#{@index}) used #{choice[2].name}")
PBDebug.logonerr { pbUseMove(choice, choice[2] == @battle.struggle) }
@battle.pbJudge
# Update priority order
@@ -144,7 +144,7 @@ class Battle::Battler
choice[2].pp = -1
end
choice[3] = target # Target (-1 means no target yet)
PBDebug.log("[Move usage] #{pbThis} started using the called/simple move #{choice[2].name}")
PBDebug.log("[Use move] #{pbThis} used the called/simple move #{choice[2].name}")
pbUseMove(choice, specialUsage)
end

View File

@@ -43,19 +43,21 @@ class Battle::Battler
# Choice Band/Gorilla Tactics
@effects[PBEffects::ChoiceBand] = nil if !pbHasMove?(@effects[PBEffects::ChoiceBand])
if @effects[PBEffects::ChoiceBand] && move.id != @effects[PBEffects::ChoiceBand]
choiced_move_name = GameData::Move.get(@effects[PBEffects::ChoiceBand]).name
if hasActiveItem?([:CHOICEBAND, :CHOICESPECS, :CHOICESCARF])
if showMessages
msg = _INTL("The {1} only allows the use of {2}!", itemName, choiced_move_name)
(commandPhase) ? @battle.pbDisplayPaused(msg) : @battle.pbDisplay(msg)
choiced_move = GameData::Move.try_get(@effects[PBEffects::ChoiceBand])
if choiced_move
if hasActiveItem?([:CHOICEBAND, :CHOICESPECS, :CHOICESCARF])
if showMessages
msg = _INTL("The {1} only allows the use of {2}!", itemName, choiced_move.name)
(commandPhase) ? @battle.pbDisplayPaused(msg) : @battle.pbDisplay(msg)
end
return false
elsif hasActiveAbility?(:GORILLATACTICS)
if showMessages
msg = _INTL("{1} can only use {2}!", pbThis, choiced_move.name)
(commandPhase) ? @battle.pbDisplayPaused(msg) : @battle.pbDisplay(msg)
end
return false
end
return false
elsif hasActiveAbility?(:GORILLATACTICS)
if showMessages
msg = _INTL("{1} can only use {2}!", pbThis, choiced_move_name)
(commandPhase) ? @battle.pbDisplayPaused(msg) : @battle.pbDisplay(msg)
end
return false
end
end
# Taunt
@@ -85,7 +87,7 @@ class Battle::Battler
end
# Assault Vest (prevents choosing status moves but doesn't prevent
# executing them)
if hasActiveItem?(:ASSAULTVEST) && move.statusMove? && move.id != :MEFIRST && commandPhase
if hasActiveItem?(:ASSAULTVEST) && move.statusMove? && move.function != "UseMoveTargetIsAboutToUse" && commandPhase
if showMessages
msg = _INTL("The effects of the {1} prevent status moves from being used!", itemName)
(commandPhase) ? @battle.pbDisplayPaused(msg) : @battle.pbDisplay(msg)
@@ -192,10 +194,13 @@ class Battle::Battler
return false
end
if @effects[PBEffects::HyperBeam] > 0 # Intentionally before Truant
PBDebug.log("[Move failed] #{pbThis} is recharging after using #{move.name}")
@battle.pbDisplay(_INTL("{1} must recharge!", pbThis))
@effects[PBEffects::Truant] = !@effects[PBEffects::Truant] if hasActiveAbility?(:TRUANT)
return false
end
if choice[1] == -2 # Battle Palace
PBDebug.log("[Move failed] #{pbThis} can't act in the Battle Palace somehow")
@battle.pbDisplay(_INTL("{1} appears incapable of using its power!", pbThis))
return false
end
@@ -210,6 +215,7 @@ class Battle::Battler
else
pbContinueStatus
if !move.usableWhenAsleep? # Snore/Sleep Talk
PBDebug.log("[Move failed] #{pbThis} is asleep")
@lastMoveFailed = true
return false
end
@@ -220,6 +226,7 @@ class Battle::Battler
pbCureStatus
else
pbContinueStatus
PBDebug.log("[Move failed] #{pbThis} is frozen")
@lastMoveFailed = true
return false
end
@@ -235,12 +242,14 @@ class Battle::Battler
@battle.pbDisplay(_INTL("{1} is loafing around!", pbThis))
@lastMoveFailed = true
@battle.pbHideAbilitySplash(self)
PBDebug.log("[Move failed] #{pbThis} can't act because of #{abilityName}")
return false
end
end
# Flinching
if @effects[PBEffects::Flinch]
@battle.pbDisplay(_INTL("{1} flinched and couldn't move!", pbThis))
PBDebug.log("[Move failed] #{pbThis} flinched")
if abilityActive?
Battle::AbilityEffects.triggerOnFlinch(self.ability, self, @battle)
end
@@ -259,6 +268,7 @@ class Battle::Battler
threshold = (Settings::MECHANICS_GENERATION >= 7) ? 33 : 50 # % chance
if @battle.pbRandom(100) < threshold
pbConfusionDamage(_INTL("It hurt itself in its confusion!"))
PBDebug.log("[Move failed] #{pbThis} hurt itself in its confusion")
@lastMoveFailed = true
return false
end
@@ -267,6 +277,7 @@ class Battle::Battler
# Paralysis
if @status == :PARALYSIS && @battle.pbRandom(100) < 25
pbContinueStatus
PBDebug.log("[Move failed] #{pbThis} is paralyzed")
@lastMoveFailed = true
return false
end
@@ -277,6 +288,7 @@ class Battle::Battler
@battle.battlers[@effects[PBEffects::Attract]].pbThis(true)))
if @battle.pbRandom(100) < 50
@battle.pbDisplay(_INTL("{1} is immobilized by love!", pbThis))
PBDebug.log("[Move failed] #{pbThis} is immobilized by love")
@lastMoveFailed = true
return false
end
@@ -295,7 +307,10 @@ class Battle::Battler
# Two-turn attacks can't fail here in the charging turn
return true if user.effects[PBEffects::TwoTurnAttack]
# Move-specific failures
return false if move.pbFailsAgainstTarget?(user, target, show_message)
if move.pbFailsAgainstTarget?(user, target, show_message)
PBDebug.log(sprintf("[Move failed] In function code %s's def pbFailsAgainstTarget?", move.function))
return false
end
# Immunity to priority moves because of Psychic Terrain
if @battle.field.terrain == :Psychic && target.affectedByTerrain? && target.opposes?(user) &&
@battle.choices[user.index][4] > 0 # Move priority saved from pbCalculatePriority
@@ -551,15 +566,17 @@ class Battle::Battler
miss = true
end
end
target.damageState.invulnerable = true if miss
if !miss
if miss
target.damageState.invulnerable = true
PBDebug.log("[Move failed] Target is semi-invulnerable")
else
# Called by another move
return true if skipAccuracyCheck
# Accuracy check
return true if move.pbAccuracyCheck(user, target) # Includes Counter/Mirror Coat
PBDebug.log("[Move failed] Failed pbAccuracyCheck")
end
# Missed
PBDebug.log("[Move failed] Failed pbAccuracyCheck or target is semi-invulnerable")
return false
end

View File

@@ -43,11 +43,10 @@ class Battle::Move
@category = move.category
@accuracy = move.accuracy
@pp = move.pp # Can be changed with Mimic/Transform
@addlEffect = move.effect_chance
@target = move.target
@priority = move.priority
@flags = move.flags.clone
@calcType = nil
@addlEffect = move.effect_chance
@powerBoost = false # For Aerilate, Pixilate, Refrigerate, Galvanize
@snatched = false
end

View File

@@ -251,10 +251,9 @@ class Battle::Move
oldHP = b.hp
if b.damageState.substitute
old_sub_hp = b.effects[PBEffects::Substitute] + b.damageState.hpLost
PBDebug.log("[Move damage] #{b.pbThis}'s substitute lost #{b.damageState.hpLost} HP (#{old_sub_hp}=>#{b.effects[PBEffects::Substitute]})")
PBDebug.log("[Substitute HP change] #{b.pbThis}'s substitute lost #{b.damageState.hpLost} HP (#{old_sub_hp} -> #{b.effects[PBEffects::Substitute]})")
else
oldHP += b.damageState.hpLost
PBDebug.log("[Move damage] #{b.pbThis} lost #{b.damageState.hpLost} HP (#{oldHP}=>#{b.hp})")
end
effectiveness = 0
if Effectiveness.resistant?(b.damageState.typeMod)
@@ -375,19 +374,22 @@ class Battle::Move
# code.
moveType = nil
moveType = :NORMAL if @function == "TypeDependsOnUserIVs" # Hidden Power
if physicalMove?(moveType)
target.effects[PBEffects::Counter] = damage
target.effects[PBEffects::CounterTarget] = user.index
elsif specialMove?(moveType)
target.effects[PBEffects::MirrorCoat] = damage
target.effects[PBEffects::MirrorCoatTarget] = user.index
if !target.damageState.substitute
if physicalMove?(moveType)
target.effects[PBEffects::Counter] = damage
target.effects[PBEffects::CounterTarget] = user.index
elsif specialMove?(moveType)
target.effects[PBEffects::MirrorCoat] = damage
target.effects[PBEffects::MirrorCoatTarget] = user.index
end
end
if target.effects[PBEffects::Bide] > 0
target.effects[PBEffects::BideDamage] += damage
target.effects[PBEffects::BideTarget] = user.index
end
target.damageState.fainted = true if target.fainted?
target.lastHPLost = damage # For Focus Punch
target.lastHPLost = damage
target.tookMoveDamageThisRound = true if damage > 0 && !target.damageState.substitute # For Focus Punch
target.tookDamageThisRound = true if damage > 0 # For Assurance
target.lastAttacker.push(user.index) # For Revenge
if target.opposes?(user)

View File

@@ -101,10 +101,11 @@ class Battle::Move
# Check if move can't miss
return true if modifiers[:base_accuracy] == 0
# Calculation
accStage = [[modifiers[:accuracy_stage], -6].max, 6].min + 6
evaStage = [[modifiers[:evasion_stage], -6].max, 6].min + 6
stageMul = [3, 3, 3, 3, 3, 3, 3, 4, 5, 6, 7, 8, 9]
stageDiv = [9, 8, 7, 6, 5, 4, 3, 3, 3, 3, 3, 3, 3]
max_stage = Battle::Battler::STAT_STAGE_MAXIMUM
accStage = [[modifiers[:accuracy_stage], -max_stage].max, max_stage].min + max_stage
evaStage = [[modifiers[:evasion_stage], -max_stage].max, max_stage].min + max_stage
stageMul = Battle::Battler::ACC_EVA_STAGE_MULTIPLIERS
stageDiv = Battle::Battler::ACC_EVA_STAGE_DIVISORS
accuracy = 100.0 * stageMul[accStage] / stageDiv[accStage]
evasion = 100.0 * stageMul[evaStage] / stageDiv[evaStage]
accuracy = (accuracy * modifiers[:accuracy_multiplier]).round
@@ -226,13 +227,13 @@ class Battle::Move
def pbModifyDamage(damageMult, user, target); return damageMult; end
def pbGetAttackStats(user, target)
return user.spatk, user.stages[:SPECIAL_ATTACK] + 6 if specialMove?
return user.attack, user.stages[:ATTACK] + 6
return user.spatk, user.stages[:SPECIAL_ATTACK] + Battle::Battler::STAT_STAGE_MAXIMUM if specialMove?
return user.attack, user.stages[:ATTACK] + Battle::Battler::STAT_STAGE_MAXIMUM
end
def pbGetDefenseStats(user, target)
return target.spdef, target.stages[:SPECIAL_DEFENSE] + 6 if specialMove?
return target.defense, target.stages[:DEFENSE] + 6
return target.spdef, target.stages[:SPECIAL_DEFENSE] + Battle::Battler::STAT_STAGE_MAXIMUM if specialMove?
return target.defense, target.stages[:DEFENSE] + Battle::Battler::STAT_STAGE_MAXIMUM
end
def pbCalcDamage(user, target, numTargets = 1)
@@ -241,8 +242,9 @@ class Battle::Move
target.damageState.calcDamage = 1
return
end
stageMul = [2, 2, 2, 2, 2, 2, 2, 3, 4, 5, 6, 7, 8]
stageDiv = [8, 7, 6, 5, 4, 3, 2, 2, 2, 2, 2, 2, 2]
max_stage = Battle::Battler::STAT_STAGE_MAXIMUM
stageMul = Battle::Battler::STAT_STAGE_MULTIPLIERS
stageDiv = Battle::Battler::STAT_STAGE_DIVISORS
# Get the move's type
type = @calcType # nil is treated as physical
# Calculate whether this hit deals critical damage
@@ -252,13 +254,13 @@ class Battle::Move
# Calculate user's attack stat
atk, atkStage = pbGetAttackStats(user, target)
if !target.hasActiveAbility?(:UNAWARE) || @battle.moldBreaker
atkStage = 6 if target.damageState.critical && atkStage < 6
atkStage = max_stage if target.damageState.critical && atkStage < max_stage
atk = (atk.to_f * stageMul[atkStage] / stageDiv[atkStage]).floor
end
# Calculate target's defense stat
defense, defStage = pbGetDefenseStats(user, target)
if !user.hasActiveAbility?(:UNAWARE)
defStage = 6 if target.damageState.critical && defStage > 6
defStage = max_stage if target.damageState.critical && defStage > max_stage
defense = (defense.to_f * stageMul[defStage] / stageDiv[defStage]).floor
end
# Calculate all multiplier effects
@@ -283,7 +285,7 @@ class Battle::Move
if (@battle.pbCheckGlobalAbility(:DARKAURA) && type == :DARK) ||
(@battle.pbCheckGlobalAbility(:FAIRYAURA) && type == :FAIRY)
if @battle.pbCheckGlobalAbility(:AURABREAK)
multipliers[:power_multiplier] *= 2 / 3.0
multipliers[:power_multiplier] *= 3 / 4.0
else
multipliers[:power_multiplier] *= 4 / 3.0
end
@@ -308,10 +310,14 @@ class Battle::Move
Battle::AbilityEffects.triggerDamageCalcFromTarget(
target.ability, user, target, self, multipliers, baseDmg, type
)
Battle::AbilityEffects.triggerDamageCalcFromTargetNonIgnorable(
target.ability, user, target, self, multipliers, baseDmg, type
)
end
end
if target.abilityActive?
Battle::AbilityEffects.triggerDamageCalcFromTargetNonIgnorable(
target.ability, user, target, self, multipliers, baseDmg, type
)
end
if !@battle.moldBreaker
target.allAllies.each do |b|
next if !b.abilityActive?
Battle::AbilityEffects.triggerDamageCalcFromTargetAlly(
@@ -413,6 +419,8 @@ class Battle::Move
if target.pbHasType?(:ROCK) && specialMove? && @function != "UseTargetDefenseInsteadOfTargetSpDef"
multipliers[:defense_multiplier] *= 1.5
end
when :ShadowSky
multipliers[:final_damage_multiplier] *= 1.5 if type == :SHADOW
end
# Critical hits
if target.damageState.critical

View File

@@ -36,7 +36,6 @@ class Battle::Move::Confusion < Battle::Move
@priority = 0
@flags = []
@addlEffect = 0
@calcType = nil
@powerBoost = false
@snatched = false
end
@@ -53,19 +52,18 @@ class Battle::Move::Struggle < Battle::Move
def initialize(battle, move)
@battle = battle
@realMove = nil # Not associated with a move
@id = (move) ? move.id : :STRUGGLE
@name = (move) ? move.name : _INTL("Struggle")
@id = :STRUGGLE
@name = _INTL("Struggle")
@function = "Struggle"
@power = 50
@type = nil
@category = 0
@accuracy = 0
@pp = -1
@target = :NearOther
@target = :RandomNearFoe
@priority = 0
@flags = ["Contact", "CanProtect"]
@addlEffect = 0
@calcType = nil
@powerBoost = false
@snatched = false
end
@@ -85,6 +83,8 @@ end
# Raise one of user's stats.
#===============================================================================
class Battle::Move::StatUpMove < Battle::Move
attr_reader :statUp
def canSnatch?; return true; end
def pbMoveFailed?(user, targets)
@@ -108,6 +108,8 @@ end
# Raise multiple of user's stats.
#===============================================================================
class Battle::Move::MultiStatUpMove < Battle::Move
attr_reader :statUp
def canSnatch?; return true; end
def pbMoveFailed?(user, targets)
@@ -151,6 +153,8 @@ end
# Lower multiple of user's stats.
#===============================================================================
class Battle::Move::StatDownMove < Battle::Move
attr_reader :statDown
def pbEffectWhenDealingDamage(user, target)
return if @battle.pbAllFainted?(target.idxOwnSide)
showAnim = true
@@ -167,6 +171,8 @@ end
# Lower one of target's stats.
#===============================================================================
class Battle::Move::TargetStatDownMove < Battle::Move
attr_reader :statDown
def canMagicCoat?; return true; end
def pbFailsAgainstTarget?(user, target, show_message)
@@ -190,6 +196,8 @@ end
# Lower multiple of target's stats.
#===============================================================================
class Battle::Move::TargetMultiStatDownMove < Battle::Move
attr_reader :statDown
def canMagicCoat?; return true; end
def pbFailsAgainstTarget?(user, target, show_message)
@@ -485,6 +493,8 @@ end
# Weather-inducing move.
#===============================================================================
class Battle::Move::WeatherMove < Battle::Move
attr_reader :weatherType
def initialize(battle, move)
super
@weatherType = :None

View File

@@ -174,11 +174,11 @@ class Battle::Move::FailsIfUserDamagedThisTurn < Battle::Move
end
def pbDisplayUseMessage(user)
super if !user.effects[PBEffects::FocusPunch] || user.lastHPLost == 0
super if !user.effects[PBEffects::FocusPunch] || !user.tookMoveDamageThisRound
end
def pbMoveFailed?(user, targets)
if user.effects[PBEffects::FocusPunch] && user.lastHPLost > 0
if user.effects[PBEffects::FocusPunch] && user.tookMoveDamageThisRound
@battle.pbDisplay(_INTL("{1} lost its focus and couldn't move!", user.pbThis))
return true
end
@@ -187,7 +187,7 @@ class Battle::Move::FailsIfUserDamagedThisTurn < Battle::Move
end
#===============================================================================
# Fails if the target didn't chose a damaging move to use this round, or has
# Fails if the target didn't choose a damaging move to use this round, or has
# already moved. (Sucker Punch)
#===============================================================================
class Battle::Move::FailsIfTargetActed < Battle::Move
@@ -459,6 +459,8 @@ end
# side. (Court Change)
#===============================================================================
class Battle::Move::SwapSideEffects < Battle::Move
attr_reader :number_effects, :boolean_effects
def initialize(battle, move)
super
@number_effects = [

View File

@@ -23,10 +23,17 @@ end
# (Fell Stinger (Gen 6-))
#===============================================================================
class Battle::Move::RaiseUserAttack2IfTargetFaints < Battle::Move
attr_reader :statUp
def initialize(battle, move)
super
@statUp = [:ATTACK, 2]
end
def pbEffectAfterAllHits(user, target)
return if !target.damageState.fainted
return if !user.pbCanRaiseStatStage?(:ATTACK, user, self)
user.pbRaiseStatStage(:ATTACK, 2, user)
return if !user.pbCanRaiseStatStage?(@statUp[0], user, self)
user.pbRaiseStatStage(@statUp[0], @statUp[1], user)
end
end
@@ -45,10 +52,17 @@ end
# (Fell Stinger (Gen 7+))
#===============================================================================
class Battle::Move::RaiseUserAttack3IfTargetFaints < Battle::Move
attr_reader :statUp
def initialize(battle, move)
super
@statUp = [:ATTACK, 3]
end
def pbEffectAfterAllHits(user, target)
return if !target.damageState.fainted
return if !user.pbCanRaiseStatStage?(:ATTACK, user, self)
user.pbRaiseStatStage(:ATTACK, 3, user)
return if !user.pbCanRaiseStatStage?(@statUp[0], user, self)
user.pbRaiseStatStage(@statUp[0], @statUp[1], user)
end
end
@@ -57,15 +71,22 @@ end
# (Belly Drum)
#===============================================================================
class Battle::Move::MaxUserAttackLoseHalfOfTotalHP < Battle::Move
attr_reader :statUp
def canSnatch?; return true; end
def initialize(battle, move)
super
@statUp = [:ATTACK, 12]
end
def pbMoveFailed?(user, targets)
hpLoss = [user.totalhp / 2, 1].max
if user.hp <= hpLoss
@battle.pbDisplay(_INTL("But it failed!"))
return true
end
return true if !user.pbCanRaiseStatStage?(:ATTACK, user, self, true)
return true if !user.pbCanRaiseStatStage?(@statUp[0], user, self, true)
return false
end
@@ -73,16 +94,18 @@ class Battle::Move::MaxUserAttackLoseHalfOfTotalHP < Battle::Move
hpLoss = [user.totalhp / 2, 1].max
user.pbReduceHP(hpLoss, false, false)
if user.hasActiveAbility?(:CONTRARY)
user.stages[:ATTACK] = -6
user.stages[@statUp[0]] = -Battle::Battler::STAT_STAGE_MAXIMUM
user.statsLoweredThisRound = true
user.statsDropped = true
@battle.pbCommonAnimation("StatDown", user)
@battle.pbDisplay(_INTL("{1} cut its own HP and minimized its Attack!", user.pbThis))
@battle.pbDisplay(_INTL("{1} cut its own HP and minimized its {2}!",
user.pbThis, GameData::Stat.get(@statUp[0]).name))
else
user.stages[:ATTACK] = 6
user.stages[@statUp[0]] = Battle::Battler::STAT_STAGE_MAXIMUM
user.statsRaisedThisRound = true
@battle.pbCommonAnimation("StatUp", user)
@battle.pbDisplay(_INTL("{1} cut its own HP and maximized its Attack!", user.pbThis))
@battle.pbDisplay(_INTL("{1} cut its own HP and maximized its {2}!",
user.pbThis, GameData::Stat.get(@statUp[0]).name))
end
user.pbItemHPHealCheck
end
@@ -407,6 +430,8 @@ end
# (Shell Smash)
#===============================================================================
class Battle::Move::LowerUserDefSpDef1RaiseUserAtkSpAtkSpd2 < Battle::Move
attr_reader :statUp, :statDown
def canSnatch?; return true; end
def initialize(battle, move)
@@ -882,11 +907,12 @@ class Battle::Move::RaiseTargetAtkSpAtk2 < Battle::Move
end
def pbEffectAgainstTarget(user, target)
showAnim = true
if target.pbCanRaiseStatStage?(:ATTACK, user, self)
target.pbRaiseStatStage(:ATTACK, 2, user)
showAnim = false if target.pbRaiseStatStage(:ATTACK, 2, user, showAnim)
end
if target.pbCanRaiseStatStage?(:SPECIAL_ATTACK, user, self)
target.pbRaiseStatStage(:SPECIAL_ATTACK, 2, user)
target.pbRaiseStatStage(:SPECIAL_ATTACK, 2, user, showAnim)
end
end
end
@@ -1330,6 +1356,8 @@ end
# stage each. (Venom Drench)
#===============================================================================
class Battle::Move::LowerPoisonedTargetAtkSpAtkSpd1 < Battle::Move
attr_reader :statDown
def canMagicCoat?; return true; end
def initialize(battle, move)
@@ -1342,10 +1370,13 @@ class Battle::Move::LowerPoisonedTargetAtkSpAtkSpd1 < Battle::Move
targets.each do |b|
next if !b || b.fainted?
next if !b.poisoned?
next if !b.pbCanLowerStatStage?(:ATTACK, user, self) &&
!b.pbCanLowerStatStage?(:SPECIAL_ATTACK, user, self) &&
!b.pbCanLowerStatStage?(:SPEED, user, self)
@validTargets.push(b.index)
failed = true
(@statDown.length / 2).times do |i|
next if !b.pbCanLowerStatStage?(@statDown[i * 2], user, self)
failed = false
break
end
@validTargets.push(b.index) if !failed
end
if @validTargets.length == 0
@battle.pbDisplay(_INTL("But it failed!"))
@@ -1397,7 +1428,7 @@ end
# Raises the Attack and Defense of all user's allies by 1 stage each. Bypasses
# protections, including Crafty Shield. Fails if there is no ally. (Coaching)
#===============================================================================
class Battle::Move::RaiseUserAndAlliesAtkDef1 < Battle::Move
class Battle::Move::RaiseAlliesAtkDef1 < Battle::Move
def ignoresSubstitute?(user); return true; end
def canSnatch?; return true; end

View File

@@ -100,11 +100,18 @@ end
# Poisons the target and decreases its Speed by 1 stage. (Toxic Thread)
#===============================================================================
class Battle::Move::PoisonTargetLowerTargetSpeed1 < Battle::Move
attr_reader :statDown
def initialize(battle, move)
super
@statDown = [:SPEED, 1]
end
def canMagicCoat?; return true; end
def pbFailsAgainstTarget?(user, target, show_message)
if !target.pbCanPoison?(user, false, self) &&
!target.pbCanLowerStatStage?(:SPEED, user, self)
!target.pbCanLowerStatStage?(@statDown[0], user, self)
@battle.pbDisplay(_INTL("But it failed!")) if show_message
return true
end
@@ -113,8 +120,8 @@ class Battle::Move::PoisonTargetLowerTargetSpeed1 < Battle::Move
def pbEffectAgainstTarget(user, target)
target.pbPoison(user) if target.pbCanPoison?(user, false, self)
if target.pbCanLowerStatStage?(:SPEED, user, self)
target.pbLowerStatStage(:SPEED, 1, user)
if target.pbCanLowerStatStage?(@statDown[0], user, self)
target.pbLowerStatStage(@statDown[0], @statDown[1], user)
end
end
end
@@ -651,6 +658,34 @@ end
# Changes user's type depending on the environment. (Camouflage)
#===============================================================================
class Battle::Move::SetUserTypesBasedOnEnvironment < Battle::Move
TERRAIN_TYPES = {
:Electric => :ELECTRIC,
:Grassy => :GRASS,
:Misty => :FAIRY,
:Psychic => :PSYCHIC
}
ENVIRONMENT_TYPES = {
: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
}
def canSnatch?; return true; end
def pbMoveFailed?(user, targets)
@@ -659,56 +694,13 @@ class Battle::Move::SetUserTypesBasedOnEnvironment < Battle::Move
return true
end
@newType = :NORMAL
checkedTerrain = false
case @battle.field.terrain
when :Electric
if GameData::Type.exists?(:ELECTRIC)
@newType = :ELECTRIC
checkedTerrain = true
end
when :Grassy
if GameData::Type.exists?(:GRASS)
@newType = :GRASS
checkedTerrain = true
end
when :Misty
if GameData::Type.exists?(:FAIRY)
@newType = :FAIRY
checkedTerrain = true
end
when :Psychic
if GameData::Type.exists?(:PSYCHIC)
@newType = :PSYCHIC
checkedTerrain = true
end
terr_type = TERRAIN_TYPES[@battle.field.terrain]
if terr_type && GameData::Type.exists?(terr_type)
@newType = terr_type
else
@newType = ENVIRONMENT_TYPES[@battle.environment] || :NORMAL
@newType = :NORMAL if !GameData::Type.exists?(@newType)
end
if !checkedTerrain
case @battle.environment
when :Grass, :TallGrass
@newType = :GRASS
when :MovingWater, :StillWater, :Puddle, :Underwater
@newType = :WATER
when :Cave
@newType = :ROCK
when :Rock, :Sand
@newType = :GROUND
when :Forest, :ForestGrass
@newType = :BUG
when :Snow, :Ice
@newType = :ICE
when :Volcano
@newType = :FIRE
when :Graveyard
@newType = :GHOST
when :Sky
@newType = :FLYING
when :Space
@newType = :DRAGON
when :UltraSpace
@newType = :PSYCHIC
end
end
@newType = :NORMAL if !GameData::Type.exists?(@newType)
if !GameData::Type.exists?(@newType) || !user.pbHasOtherType?(@newType)
@battle.pbDisplay(_INTL("But it failed!"))
return true
@@ -887,7 +879,7 @@ class Battle::Move::AddGhostTypeToTarget < Battle::Move
def canMagicCoat?; return true; end
def pbFailsAgainstTarget?(user, target, show_message)
if !GameData::Type.exists?(:GHOST) || target.pbHasType?(:GHOST) || !target.canChangeType?
if !target.canChangeType? || !GameData::Type.exists?(:GHOST) || target.pbHasType?(:GHOST)
@battle.pbDisplay(_INTL("But it failed!")) if show_message
return true
end
@@ -908,7 +900,7 @@ class Battle::Move::AddGrassTypeToTarget < Battle::Move
def canMagicCoat?; return true; end
def pbFailsAgainstTarget?(user, target, show_message)
if !GameData::Type.exists?(:GRASS) || target.pbHasType?(:GRASS) || !target.canChangeType?
if !target.canChangeType? || !GameData::Type.exists?(:GRASS) || target.pbHasType?(:GRASS)
@battle.pbDisplay(_INTL("But it failed!")) if show_message
return true
end
@@ -957,7 +949,7 @@ class Battle::Move::SetTargetAbilityToSimple < Battle::Move
end
def pbFailsAgainstTarget?(user, target, show_message)
if target.unstoppableAbility? || [:TRUANT, :SIMPLE].include?(target.ability)
if target.unstoppableAbility? || [:TRUANT, :SIMPLE].include?(target.ability_id)
@battle.pbDisplay(_INTL("But it failed!")) if show_message
return true
end
@@ -1165,7 +1157,7 @@ class Battle::Move::NegateTargetAbility < Battle::Move
def canMagicCoat?; return true; end
def pbFailsAgainstTarget?(user, target, show_message)
if target.unstoppableAbility?
if target.unstoppableAbility? || target.effects[PBEffects::GastroAcid]
@battle.pbDisplay(_INTL("But it failed!")) if show_message
return true
end

View File

@@ -837,9 +837,9 @@ class Battle::Move::RemoveScreens < Battle::Move
end
def pbShowAnimation(id, user, targets, hitNum = 0, showAnimation = true)
if user.pbOpposingSide.effects[PBEffects::LightScreen] > 0 ||
user.pbOpposingSide.effects[PBEffects::Reflect] > 0 ||
user.pbOpposingSide.effects[PBEffects::AuroraVeil] > 0
if user.pbOpposingSide.effects[PBEffects::AuroraVeil] > 0 ||
user.pbOpposingSide.effects[PBEffects::LightScreen] > 0 ||
user.pbOpposingSide.effects[PBEffects::Reflect] > 0
hitNum = 1 # Wall-breaking anim
end
super
@@ -991,20 +991,8 @@ end
#===============================================================================
# Ends target's protections immediately. (Hyperspace Hole)
#===============================================================================
class Battle::Move::RemoveProtectionsBypassSubstitute < Battle::Move
class Battle::Move::RemoveProtectionsBypassSubstitute < Battle::Move::RemoveProtections
def ignoresSubstitute?(user); return true; end
def pbEffectAgainstTarget(user, target)
target.effects[PBEffects::BanefulBunker] = false
target.effects[PBEffects::KingsShield] = false
target.effects[PBEffects::Obstruct] = false
target.effects[PBEffects::Protect] = false
target.effects[PBEffects::SpikyShield] = false
target.pbOwnSide.effects[PBEffects::CraftyShield] = false
target.pbOwnSide.effects[PBEffects::MatBlock] = false
target.pbOwnSide.effects[PBEffects::QuickGuard] = false
target.pbOwnSide.effects[PBEffects::WideGuard] = false
end
end
#===============================================================================
@@ -1133,24 +1121,25 @@ class Battle::Move::CategoryDependsOnHigherDamagePoisonTarget < Battle::Move::Po
def pbOnStartUse(user, targets)
target = targets[0]
stageMul = [2, 2, 2, 2, 2, 2, 2, 3, 4, 5, 6, 7, 8]
stageDiv = [8, 7, 6, 5, 4, 3, 2, 2, 2, 2, 2, 2, 2]
max_stage = Battle::Battler::STAT_STAGE_MAXIMUM
stageMul = Battle::Battler::STAT_STAGE_MULTIPLIERS
stageDiv = Battle::Battler::STAT_STAGE_DIVISORS
# Calculate user's effective attacking values
attack_stage = user.stages[:ATTACK] + 6
attack_stage = user.stages[:ATTACK] + max_stage
real_attack = (user.attack.to_f * stageMul[attack_stage] / stageDiv[attack_stage]).floor
special_attack_stage = user.stages[:SPECIAL_ATTACK] + 6
special_attack_stage = user.stages[:SPECIAL_ATTACK] + max_stage
real_special_attack = (user.spatk.to_f * stageMul[special_attack_stage] / stageDiv[special_attack_stage]).floor
# Calculate target's effective defending values
defense_stage = target.stages[:DEFENSE] + 6
defense_stage = target.stages[:DEFENSE] + max_stage
real_defense = (target.defense.to_f * stageMul[defense_stage] / stageDiv[defense_stage]).floor
special_defense_stage = target.stages[:SPECIAL_DEFENSE] + 6
special_defense_stage = target.stages[:SPECIAL_DEFENSE] + max_stage
real_special_defense = (target.spdef.to_f * stageMul[special_defense_stage] / stageDiv[special_defense_stage]).floor
# Perform simple damage calculation
physical_damage = real_attack.to_f / real_defense
special_damage = real_special_attack.to_f / real_special_defense
# Determine move's category
if physical_damage == special_damage
@calcCategry = @battle.pbRandom(2)
@calcCategory = (@battle.command_phase) ? rand(2) : @battle.pbRandom(2)
else
@calcCategory = (physical_damage > special_damage) ? 0 : 1
end
@@ -1178,13 +1167,14 @@ class Battle::Move::CategoryDependsOnHigherDamageIgnoreTargetAbility < Battle::M
def pbOnStartUse(user, targets)
# Calculate user's effective attacking value
stageMul = [2, 2, 2, 2, 2, 2, 2, 3, 4, 5, 6, 7, 8]
stageDiv = [8, 7, 6, 5, 4, 3, 2, 2, 2, 2, 2, 2, 2]
max_stage = Battle::Battler::STAT_STAGE_MAXIMUM
stageMul = Battle::Battler::STAT_STAGE_MULTIPLIERS
stageDiv = Battle::Battler::STAT_STAGE_DIVISORS
atk = user.attack
atkStage = user.stages[:ATTACK] + 6
atkStage = user.stages[:ATTACK] + max_stage
realAtk = (atk.to_f * stageMul[atkStage] / stageDiv[atkStage]).floor
spAtk = user.spatk
spAtkStage = user.stages[:SPECIAL_ATTACK] + 6
spAtkStage = user.stages[:SPECIAL_ATTACK] + max_stage
realSpAtk = (spAtk.to_f * stageMul[spAtkStage] / stageDiv[spAtkStage]).floor
# Determine move's category
@calcCategory = (realAtk > realSpAtk) ? 0 : 1
@@ -1197,9 +1187,9 @@ end
# are applied normally, applying the user's Attack modifiers and not the user's
# Defence modifiers. (Body Press)
#===============================================================================
class Battle::Move::UseUserBaseDefenseInsteadOfUserBaseAttack < Battle::Move
class Battle::Move::UseUserDefenseInsteadOfUserAttack < Battle::Move
def pbGetAttackStats(user, target)
return user.defense, user.stages[:DEFENSE] + 6
return user.defense, user.stages[:DEFENSE] + Battle::Battler::STAT_STAGE_MAXIMUM
end
end
@@ -1209,8 +1199,8 @@ end
#===============================================================================
class Battle::Move::UseTargetAttackInsteadOfUserAttack < Battle::Move
def pbGetAttackStats(user, target)
return target.spatk, target.stages[:SPECIAL_ATTACK] + 6 if specialMove?
return target.attack, target.stages[:ATTACK] + 6
return target.spatk, target.stages[:SPECIAL_ATTACK] + Battle::Battler::STAT_STAGE_MAXIMUM if specialMove?
return target.attack, target.stages[:ATTACK] + Battle::Battler::STAT_STAGE_MAXIMUM
end
end
@@ -1220,7 +1210,7 @@ end
#===============================================================================
class Battle::Move::UseTargetDefenseInsteadOfTargetSpDef < Battle::Move
def pbGetDefenseStats(user, target)
return target.defense, target.stages[:DEFENSE] + 6
return target.defense, target.stages[:DEFENSE] + Battle::Battler::STAT_STAGE_MAXIMUM
end
end
@@ -1269,14 +1259,14 @@ end
# (Chip Away, Darkest Lariat, Sacred Sword)
#===============================================================================
class Battle::Move::IgnoreTargetDefSpDefEvaStatStages < Battle::Move
def pbCalcAccuracyMultipliers(user, target, multipliers)
def pbCalcAccuracyModifiers(user, target, modifiers)
super
modifiers[:evasion_stage] = 0
end
def pbGetDefenseStats(user, target)
ret1, _ret2 = super
return ret1, 6 # Def/SpDef stat stage
return ret1, Battle::Battler::STAT_STAGE_MAXIMUM # Def/SpDef stat stage
end
end
@@ -1356,13 +1346,9 @@ class Battle::Move::TypeAndPowerDependOnUserBerry < Battle::Move
return false
end
# NOTE: The AI calls this method via pbCalcType, and this method returns a
# type assuming user has an item even though it might not. Since the AI
# won't want to use this move if the user has no item, though, perhaps
# this is good enough.
def pbBaseType(user)
item = user.item
ret = :NORMAL
item = user.item
if item
item.flags.each do |flag|
next if !flag[/^NaturalGift_(\w+)_(?:\d+)$/i]
@@ -1374,20 +1360,15 @@ class Battle::Move::TypeAndPowerDependOnUserBerry < Battle::Move
return ret
end
# This is a separate method so that the AI can use it as well
def pbNaturalGiftBaseDamage(heldItem)
if heldItem
GameData::Item.get(heldItem).flags.each do |flag|
def pbBaseDamage(baseDmg, user, target)
if user.item.id
GameData::Item.get(user.item.id).flags.each do |flag|
return [$~[1].to_i, 10].max if flag[/^NaturalGift_(?:\w+)_(\d+)$/i]
end
end
return 1
end
def pbBaseDamage(baseDmg, user, target)
return pbNaturalGiftBaseDamage(user.item.id)
end
def pbEndOfMoveUsageEffect(user, targets, numHits, switchedBattlers)
# NOTE: The item is consumed even if this move was Protected against or it
# missed. The item is not consumed if the target was switched out by
@@ -1426,12 +1407,9 @@ class Battle::Move::TypeDependsOnUserPlate < Battle::Move
def pbBaseType(user)
ret = :NORMAL
if user.itemActive?
@itemTypes.each do |item, itemType|
next if user.item != item
ret = itemType if GameData::Type.exists?(itemType)
break
end
if user.item_id && user.itemActive?
typ = @itemTypes[user.item_id]
ret = typ if typ && GameData::Type.exists?(typ)
end
return ret
end
@@ -1466,12 +1444,9 @@ class Battle::Move::TypeDependsOnUserMemory < Battle::Move
def pbBaseType(user)
ret = :NORMAL
if user.itemActive?
@itemTypes.each do |item, itemType|
next if user.item != item
ret = itemType if GameData::Type.exists?(itemType)
break
end
if user.item_id && user.itemActive?
typ = @itemTypes[user.item_id]
ret = typ if typ && GameData::Type.exists?(typ)
end
return ret
end
@@ -1493,12 +1468,9 @@ class Battle::Move::TypeDependsOnUserDrive < Battle::Move
def pbBaseType(user)
ret = :NORMAL
if user.itemActive?
@itemTypes.each do |item, itemType|
next if user.item != item
ret = itemType if GameData::Type.exists?(itemType)
break
end
if user.item_id && user.itemActive?
typ = @itemTypes[user.item_id]
ret = typ if typ && GameData::Type.exists?(typ)
end
return ret
end
@@ -1554,6 +1526,8 @@ class Battle::Move::TypeAndPowerDependOnWeather < Battle::Move
ret = :ROCK if GameData::Type.exists?(:ROCK)
when :Hail
ret = :ICE if GameData::Type.exists?(:ICE)
when :ShadowSky
ret = :NONE
end
return ret
end

View File

@@ -134,21 +134,7 @@ end
# Hits 2-5 times in a row. If the move does not fail, increases the user's Speed
# by 1 stage and decreases the user's Defense by 1 stage. (Scale Shot)
#===============================================================================
class Battle::Move::HitTwoToFiveTimesRaiseUserSpd1LowerUserDef1 < Battle::Move
def multiHitMove?; return true; end
def pbNumHits(user, targets)
hitChances = [
2, 2, 2, 2, 2, 2, 2,
3, 3, 3, 3, 3, 3, 3,
4, 4, 4,
5, 5, 5
]
r = @battle.pbRandom(hitChances.length)
r = hitChances.length - 1 if user.hasActiveAbility?(:SKILLLINK)
return hitChances[r]
end
class Battle::Move::HitTwoToFiveTimesRaiseUserSpd1LowerUserDef1 < Battle::Move::HitTwoToFiveTimes
def pbEffectAfterAllHits(user, target)
return if target.damageState.unaffected
if user.pbCanLowerStatStage?(:DEFENSE, user, self)
@@ -291,11 +277,22 @@ end
# Special Defense and Speed by 2 stages each in the second turn. (Geomancy)
#===============================================================================
class Battle::Move::TwoTurnAttackRaiseUserSpAtkSpDefSpd2 < Battle::Move::TwoTurnMove
attr_reader :statUp
def initialize(battle, move)
super
@statUp = [:SPECIAL_ATTACK, 2, :SPECIAL_DEFENSE, 2, :SPEED, 2]
end
def pbMoveFailed?(user, targets)
return false if user.effects[PBEffects::TwoTurnAttack] # Charging turn
if !user.pbCanRaiseStatStage?(:SPECIAL_ATTACK, user, self) &&
!user.pbCanRaiseStatStage?(:SPECIAL_DEFENSE, user, self) &&
!user.pbCanRaiseStatStage?(:SPEED, user, self)
failed = true
(@statUp.length / 2).times do |i|
next if !user.pbCanRaiseStatStage?(@statUp[i * 2], user, self)
failed = false
break
end
if failed
@battle.pbDisplay(_INTL("{1}'s stats won't go any higher!", user.pbThis))
return true
end
@@ -309,9 +306,9 @@ class Battle::Move::TwoTurnAttackRaiseUserSpAtkSpDefSpd2 < Battle::Move::TwoTurn
def pbEffectGeneral(user)
return if !@damagingTurn
showAnim = true
[:SPECIAL_ATTACK, :SPECIAL_DEFENSE, :SPEED].each do |s|
next if !user.pbCanRaiseStatStage?(s, user, self)
if user.pbRaiseStatStage(s, 2, user, showAnim)
(@statUp.length / 2).times do |i|
next if !user.pbCanRaiseStatStage?(@statUp[i * 2], user, self)
if user.pbRaiseStatStage(@statUp[i * 2], @statUp[(i * 2) + 1], user, showAnim)
showAnim = false
end
end
@@ -323,13 +320,20 @@ end
# (Skull Bash)
#===============================================================================
class Battle::Move::TwoTurnAttackChargeRaiseUserDefense1 < Battle::Move::TwoTurnMove
attr_reader :statUp
def initialize(battle, move)
super
@statUp = [:DEFENSE, 1]
end
def pbChargingTurnMessage(user, targets)
@battle.pbDisplay(_INTL("{1} tucked in its head!", user.pbThis))
end
def pbChargingTurnEffect(user, target)
if user.pbCanRaiseStatStage?(:DEFENSE, user, self)
user.pbRaiseStatStage(:DEFENSE, 1, user)
if user.pbCanRaiseStatStage?(@statUp[0], user, self)
user.pbRaiseStatStage(@statUp[0], @statUp[1], user)
end
end
end

View File

@@ -109,6 +109,13 @@ end
# it). (Strength Sap)
#===============================================================================
class Battle::Move::HealUserByTargetAttackLowerTargetAttack1 < Battle::Move
attr_reader :statDown
def initialize(battle, move)
super
@statDown = [:ATTACK, 1]
end
def healingMove?; return true; end
def canMagicCoat?; return true; end
@@ -118,11 +125,12 @@ class Battle::Move::HealUserByTargetAttackLowerTargetAttack1 < Battle::Move
# has Contrary and is at +6" check too for symmetry. This move still
# works even if the stat stage cannot be changed due to an ability or
# other effect.
if !@battle.moldBreaker && target.hasActiveAbility?(:CONTRARY) &&
target.statStageAtMax?(:ATTACK)
@battle.pbDisplay(_INTL("But it failed!")) if show_message
return true
elsif target.statStageAtMin?(:ATTACK)
if !@battle.moldBreaker && target.hasActiveAbility?(:CONTRARY)
if target.statStageAtMax?(@statDown[0])
@battle.pbDisplay(_INTL("But it failed!")) if show_message
return true
end
elsif target.statStageAtMin?(@statDown[0])
@battle.pbDisplay(_INTL("But it failed!")) if show_message
return true
end
@@ -131,14 +139,15 @@ class Battle::Move::HealUserByTargetAttackLowerTargetAttack1 < Battle::Move
def pbEffectAgainstTarget(user, target)
# Calculate target's effective attack value
stageMul = [2, 2, 2, 2, 2, 2, 2, 3, 4, 5, 6, 7, 8]
stageDiv = [8, 7, 6, 5, 4, 3, 2, 2, 2, 2, 2, 2, 2]
max_stage = Battle::Battler::STAT_STAGE_MAXIMUM
stageMul = Battle::Battler::STAT_STAGE_MULTIPLIERS
stageDiv = Battle::Battler::STAT_STAGE_DIVISORS
atk = target.attack
atkStage = target.stages[:ATTACK] + 6
atkStage = target.stages[@statDown[0]] + max_stage
healAmt = (atk.to_f * stageMul[atkStage] / stageDiv[atkStage]).floor
# Reduce target's Attack stat
if target.pbCanLowerStatStage?(:ATTACK, user, self)
target.pbLowerStatStage(:ATTACK, 1, user)
if target.pbCanLowerStatStage?(@statDown[0], user, self)
target.pbLowerStatStage(@statDown[0], @statDown[1], user)
end
# Heal user
if target.hasActiveAbility?(:LIQUIDOOZE, true)

View File

@@ -137,7 +137,7 @@ class Battle::Move::RestoreUserConsumedItem < Battle::Move
def canSnatch?; return true; end
def pbMoveFailed?(user, targets)
if !user.recycleItem
if !user.recycleItem || user.item
@battle.pbDisplay(_INTL("But it failed!"))
return true
end
@@ -196,6 +196,8 @@ class Battle::Move::DestroyTargetBerryOrGem < Battle::Move
return if target.damageState.substitute || target.damageState.berryWeakened
return if !target.item || (!target.item.is_berry? &&
!(Settings::MECHANICS_GENERATION >= 6 && target.item.is_gem?))
return if target.unlosableItem?(target.item)
return if target.hasActiveAbility?(:STICKYHOLD) && !@battle.moldBreaker
item_name = target.itemName
target.pbRemoveItem
@battle.pbDisplay(_INTL("{1}'s {2} was incinerated!", target.pbThis, item_name))
@@ -317,6 +319,7 @@ class Battle::Move::UserConsumeBerryRaiseDefense2 < Battle::Move::StatUpMove
@battle.pbDisplay(_INTL("But it failed!"))
return true
end
# TODO: Should this return super? It can consume the berry at this point.
return super
end
@@ -383,7 +386,7 @@ class Battle::Move::UserConsumeTargetBerry < Battle::Move
def pbEffectAfterAllHits(user, target)
return if user.fainted? || target.fainted?
return if target.damageState.unaffected || target.damageState.substitute
return if !target.item || !target.item.is_berry?
return if !target.item || !target.item.is_berry? || target.unlosableItem?(target.item)
return if target.hasActiveAbility?(:STICKYHOLD) && !@battle.moldBreaker
item = target.item
itemName = target.itemName

View File

@@ -138,8 +138,16 @@ end
# user's Attack and Defense by 1 stage each. (Curse)
#===============================================================================
class Battle::Move::CurseTargetOrLowerUserSpd1RaiseUserAtkDef1 < Battle::Move
attr_reader :statUp, :statDown
def ignoresSubstitute?(user); return true; end
def initialize(battle, move)
super
@statUp = [:ATTACK, 1, :DEFENSE, 1]
@statDown = [:SPEED, 1]
end
def pbTarget(user)
if user.pbHasType?(:GHOST)
ghost_target = (Settings::MECHANICS_GENERATION >= 8) ? :RandomNearFoe : :NearFoe
@@ -150,9 +158,18 @@ class Battle::Move::CurseTargetOrLowerUserSpd1RaiseUserAtkDef1 < Battle::Move
def pbMoveFailed?(user, targets)
return false if user.pbHasType?(:GHOST)
if !user.pbCanLowerStatStage?(:SPEED, user, self) &&
!user.pbCanRaiseStatStage?(:ATTACK, user, self) &&
!user.pbCanRaiseStatStage?(:DEFENSE, user, self)
failed = true
(@statUp.length / 2).times do |i|
next if !user.pbCanRaiseStatStage?(@statUp[i * 2], user, self)
failed = false
break
end
(@statDown.length / 2).times do |i|
next if !user.pbCanLowerStatStage?(@statDown[i * 2], user, self)
failed = false
break
end
if failed
@battle.pbDisplay(_INTL("But it failed!"))
return true
end
@@ -170,15 +187,19 @@ class Battle::Move::CurseTargetOrLowerUserSpd1RaiseUserAtkDef1 < Battle::Move
def pbEffectGeneral(user)
return if user.pbHasType?(:GHOST)
# Non-Ghost effect
if user.pbCanLowerStatStage?(:SPEED, user, self)
user.pbLowerStatStage(:SPEED, 1, user)
showAnim = true
(@statDown.length / 2).times do |i|
next if !user.pbCanLowerStatStage?(@statDown[i * 2], user, self)
if user.pbLowerStatStage(@statDown[i * 2], @statDown[(i * 2) + 1], user, showAnim)
showAnim = false
end
end
showAnim = true
if user.pbCanRaiseStatStage?(:ATTACK, user, self)
showAnim = false if user.pbRaiseStatStage(:ATTACK, 1, user, showAnim)
end
if user.pbCanRaiseStatStage?(:DEFENSE, user, self)
user.pbRaiseStatStage(:DEFENSE, 1, user, showAnim)
(@statUp.length / 2).times do |i|
next if !user.pbCanRaiseStatStage?(@statUp[i * 2], user, self)
if user.pbRaiseStatStage(@statUp[i * 2], @statUp[(i * 2) + 1], user, showAnim)
showAnim = false
end
end
end
@@ -201,6 +222,8 @@ end
# Effect depends on the environment. (Secret Power)
#===============================================================================
class Battle::Move::EffectDependsOnEnvironment < Battle::Move
attr_reader :secretPower
def flinchingMove?; return [6, 10, 12].include?(@secretPower); end
def pbOnStartUse(user, targets)
@@ -250,6 +273,7 @@ class Battle::Move::EffectDependsOnEnvironment < Battle::Move
def pbEffectAfterAllHits(user, target)
return if target.fainted?
return if target.damageState.unaffected || target.damageState.substitute
return if user.hasActiveAbility?(:SHEERFORCE)
chance = pbAdditionalEffectChance(user, target)
return if @battle.pbRandom(100) >= chance
case @secretPower
@@ -668,6 +692,8 @@ end
# Uses the last move that was used. (Copycat)
#===============================================================================
class Battle::Move::UseLastMoveUsed < Battle::Move
attr_reader :moveBlacklist
def callsAnotherMove?; return true; end
def initialize(battle, move)
@@ -691,7 +717,7 @@ class Battle::Move::UseLastMoveUsed < Battle::Move
"ProtectUser", # Detect, Protect
"ProtectUserSideFromPriorityMoves", # Quick Guard # Not listed on Bulbapedia
"ProtectUserSideFromMultiTargetDamagingMoves", # Wide Guard # Not listed on Bulbapedia
"UserEnduresFaintingThisTurn", # Endure
"UserEnduresFaintingThisTurn", # Endure
"ProtectUserSideFromDamagingMovesIfUserFirstTurn", # Mat Block
"ProtectUserSideFromStatusMoves", # Crafty Shield # Not listed on Bulbapedia
"ProtectUserFromDamagingMovesKingsShield", # King's Shield
@@ -742,6 +768,7 @@ class Battle::Move::UseLastMoveUsed < Battle::Move
def pbMoveFailed?(user, targets)
if !@copied_move ||
!GameData::Move.exists?(@copied_move) ||
@moveBlacklist.include?(GameData::Move.get(@copied_move).function_code)
@battle.pbDisplay(_INTL("But it failed!"))
return true
@@ -763,7 +790,8 @@ class Battle::Move::UseLastMoveUsedByTarget < Battle::Move
def pbFailsAgainstTarget?(user, target, show_message)
if !target.lastRegularMoveUsed ||
GameData::Move.get(target.lastRegularMoveUsed).flags.none? { |f| f[/^CanMirrorMove$/i] }
!GameData::Move.exists?(target.lastRegularMoveUsed) ||
!GameData::Move.get(target.lastRegularMoveUsed).has_flag?("CanMirrorMove")
@battle.pbDisplay(_INTL("The mirror move failed!")) if show_message
return true
end
@@ -829,6 +857,8 @@ end
# Pokémon.
#===============================================================================
class Battle::Move::UseMoveDependingOnEnvironment < Battle::Move
attr_reader :npMove
def callsAnotherMove?; return true; end
def pbOnStartUse(user, targets)
@@ -983,6 +1013,8 @@ end
# Uses a random move known by any non-user Pokémon in the user's party. (Assist)
#===============================================================================
class Battle::Move::UseRandomMoveFromUserParty < Battle::Move
attr_reader :moveBlacklist
def callsAnotherMove?; return true; end
def initialize(battle, move)
@@ -1098,6 +1130,8 @@ end
# Uses a random move the user knows. Fails if user is not asleep. (Sleep Talk)
#===============================================================================
class Battle::Move::UseRandomUserMoveIfAsleep < Battle::Move
attr_reader :moveBlacklist
def usableWhenAsleep?; return true; end
def callsAnotherMove?; return true; end
@@ -1163,8 +1197,8 @@ class Battle::Move::UseRandomUserMoveIfAsleep < Battle::Move
end
#===============================================================================
# This round, reflects all moves with the "C" flag targeting the user back at
# their origin. (Magic Coat)
# This round, reflects all moves that can be Magic Coated which target the user
# or which have no target back at their origin. (Magic Coat)
#===============================================================================
class Battle::Move::BounceBackProblemCausingStatusMoves < Battle::Move
def pbEffectGeneral(user)
@@ -1174,7 +1208,7 @@ class Battle::Move::BounceBackProblemCausingStatusMoves < Battle::Move
end
#===============================================================================
# This round, snatches all used moves with the "D" flag. (Snatch)
# This round, snatches all used moves that can be Snatched. (Snatch)
#===============================================================================
class Battle::Move::StealAndUseBeneficialStatusMove < Battle::Move
def pbEffectGeneral(user)
@@ -1192,6 +1226,8 @@ end
# out. (Mimic)
#===============================================================================
class Battle::Move::ReplaceMoveThisBattleWithTargetLastMoveUsed < Battle::Move
attr_reader :moveBlacklist
def ignoresSubstitute?(user); return true; end
def initialize(battle, move)
@@ -1243,6 +1279,8 @@ end
# This move permanently turns into the last move used by the target. (Sketch)
#===============================================================================
class Battle::Move::ReplaceMoveWithTargetLastMoveUsed < Battle::Move
attr_reader :moveBlacklist
def ignoresSubstitute?(user); return true; end
def initialize(battle, move)

View File

@@ -3,7 +3,7 @@
#===============================================================================
class Battle::Move::FleeFromBattle < Battle::Move
def pbMoveFailed?(user, targets)
if !@battle.pbCanRun?(user.index)
if !@battle.pbCanRun?(user.index) || (user.wild? && user.allAllies.length > 0)
@battle.pbDisplay(_INTL("But it failed!"))
return true
end
@@ -23,7 +23,7 @@ end
class Battle::Move::SwitchOutUserStatusMove < Battle::Move
def pbMoveFailed?(user, targets)
if user.wild?
if !@battle.pbCanRun?(user.index)
if !@battle.pbCanRun?(user.index) || user.allAllies.length > 0
@battle.pbDisplay(_INTL("But it failed!"))
return true
end
@@ -59,7 +59,7 @@ end
#===============================================================================
# After inflicting damage, user switches out. Ignores trapping moves.
# (U-turn, Volt Switch)
# (Flip Turn, U-turn, Volt Switch)
#===============================================================================
class Battle::Move::SwitchOutUserDamagingMove < Battle::Move
def pbEndOfMoveUsageEffect(user, targets, numHits, switchedBattlers)
@@ -145,9 +145,9 @@ class Battle::Move::SwitchOutUserPassOnEffects < Battle::Move
end
#===============================================================================
# In wild battles, makes target flee. Fails if target is a higher level than the
# user.
# In trainer battles, target switches out.
# When used against a sole wild Pokémon, makes target flee and ends the battle;
# fails if target is a higher level than the user.
# When used against a trainer's Pokémon, target switches out.
# For status moves. (Roar, Whirlwind)
#===============================================================================
class Battle::Move::SwitchOutTargetStatusMove < Battle::Move
@@ -171,38 +171,40 @@ class Battle::Move::SwitchOutTargetStatusMove < Battle::Move
@battle.pbDisplay(_INTL("{1} anchored itself with its roots!", target.pbThis)) if show_message
return true
end
if !@battle.canRun
@battle.pbDisplay(_INTL("But it failed!")) if show_message
return true
end
if @battle.wildBattle? && target.level > user.level
@battle.pbDisplay(_INTL("But it failed!")) if show_message
return true
end
if @battle.trainerBattle?
if target.wild? && target.allAllies.length == 0 && @battle.canRun
# End the battle
if target.level > user.level
@battle.pbDisplay(_INTL("But it failed!")) if show_message
return true
end
elsif !target.wild?
# Switch target out
canSwitch = false
@battle.eachInTeamFromBattlerIndex(target.index) do |_pkmn, i|
next if !@battle.pbCanSwitchLax?(target.index, i)
canSwitch = true
break
canSwitch = @battle.pbCanSwitchIn?(target.index, i)
break if canSwitch
end
if !canSwitch
@battle.pbDisplay(_INTL("But it failed!")) if show_message
return true
end
else
@battle.pbDisplay(_INTL("But it failed!")) if show_message
return true
end
return false
end
def pbEffectGeneral(user)
@battle.decision = 3 if @battle.wildBattle? # Escaped from battle
def pbEffectAgainstTarget(user, target)
@battle.decision = 3 if target.wild? # Escaped from battle
end
def pbSwitchOutTargetEffect(user, targets, numHits, switched_battlers)
return if @battle.wildBattle? || !switched_battlers.empty?
return if !switched_battlers.empty?
return if user.fainted? || numHits == 0
targets.each do |b|
next if b.fainted? || b.damageState.unaffected
next if b.wild?
next if b.effects[PBEffects::Ingrain]
next if b.hasActiveAbility?(:SUCTIONCUPS) && !@battle.moldBreaker
newPkmn = @battle.pbGetReplacementPokemonIndex(b.index, true) # Random
@@ -218,24 +220,26 @@ class Battle::Move::SwitchOutTargetStatusMove < Battle::Move
end
#===============================================================================
# In wild battles, makes target flee. Fails if target is a higher level than the
# user.
# In trainer battles, target switches out.
# When used against a sole wild Pokémon, makes target flee and ends the battle;
# fails if target is a higher level than the user.
# When used against a trainer's Pokémon, target switches out.
# For damaging moves. (Circle Throw, Dragon Tail)
#===============================================================================
class Battle::Move::SwitchOutTargetDamagingMove < Battle::Move
def pbEffectAgainstTarget(user, target)
if @battle.wildBattle? && target.level <= user.level && @battle.canRun &&
if target.wild? && target.allAllies.length == 0 && @battle.canRun &&
target.level <= user.level &&
(target.effects[PBEffects::Substitute] == 0 || ignoresSubstitute?(user))
@battle.decision = 3
@battle.decision = 3 # Escaped from battle
end
end
def pbSwitchOutTargetEffect(user, targets, numHits, switched_battlers)
return if @battle.wildBattle? || !switched_battlers.empty?
return if !switched_battlers.empty?
return if user.fainted? || numHits == 0
targets.each do |b|
next if b.fainted? || b.damageState.unaffected || b.damageState.substitute
next if b.wild?
next if b.effects[PBEffects::Ingrain]
next if b.hasActiveAbility?(:SUCTIONCUPS) && !@battle.moldBreaker
newPkmn = @battle.pbGetReplacementPokemonIndex(b.index, true) # Random
@@ -306,7 +310,8 @@ end
#===============================================================================
# Target can no longer switch out or flee, as long as the user remains active.
# (Anchor Shot, Block, Mean Look, Spider Web, Spirit Shackle, Thousand Waves)
# Trapping is considered an additional effect for damaging moves.
# (Anchor Shot, Block, Mean Look, Spider Web, Spirit Shackle)
#===============================================================================
class Battle::Move::TrapTargetInBattle < Battle::Move
def canMagicCoat?; return true; end
@@ -339,6 +344,22 @@ class Battle::Move::TrapTargetInBattle < Battle::Move
end
end
#===============================================================================
# Target can no longer switch out or flee, as long as the user remains active.
# Trapping is not considered an additional effect. (Thousand Waves)
#===============================================================================
class Battle::Move::TrapTargetInBattleMainEffect < Battle::Move
def canMagicCoat?; return true; end
def pbEffectAgainstTarget(user, target)
return if target.fainted? || target.damageState.substitute
return if target.effects[PBEffects::MeanLook] >= 0
return if Settings::MORE_TYPE_EFFECTS && target.pbHasType?(:GHOST)
target.effects[PBEffects::MeanLook] = user.index
@battle.pbDisplay(_INTL("{1} can no longer escape!", target.pbThis))
end
end
#===============================================================================
# The target can no longer switch out or flee, while the user remains in battle.
# At the end of each round, the target's Defense and Special Defense are lowered
@@ -370,7 +391,7 @@ end
# fleeing. (Jaw Lock)
#===============================================================================
class Battle::Move::TrapUserAndTargetInBattle < Battle::Move
def pbAdditionalEffect(user, target)
def pbEffectAgainstTarget(user, target)
return if user.fainted? || target.fainted? || target.damageState.substitute
return if Settings::MORE_TYPE_EFFECTS && target.pbHasType?(:GHOST)
return if user.trappedInBattle? || target.trappedInBattle?
@@ -540,6 +561,8 @@ end
# The target uses its most recent move again. (Instruct)
#===============================================================================
class Battle::Move::TargetUsesItsLastUsedMoveAgain < Battle::Move
attr_reader :moveBlacklist
def ignoresSubstitute?(user); return true; end
def initialize(battle, move)
@@ -587,7 +610,8 @@ class Battle::Move::TargetUsesItsLastUsedMoveAgain < Battle::Move
end
def pbFailsAgainstTarget?(user, target, show_message)
if !target.lastRegularMoveUsed || !target.pbHasMove?(target.lastRegularMoveUsed)
if !target.lastRegularMoveUsed || !target.pbHasMove?(target.lastRegularMoveUsed) ||
!GameData::Move.exists?(target.lastRegularMoveUsed)
@battle.pbDisplay(_INTL("But it failed!")) if show_message
return true
end
@@ -658,8 +682,8 @@ end
# Target's last move used loses 3 PP. Damaging move. (Eerie Spell)
#===============================================================================
class Battle::Move::LowerPPOfTargetLastMoveBy3 < Battle::Move
def pbEffectAgainstTarget(user, target)
return if target.fainted?
def pbAdditionalEffect(user, target)
return if target.fainted? || target.damageState.substitute
last_move = target.pbGetMoveWithID(target.lastRegularMoveUsed)
return if !last_move || last_move.pp == 0 || last_move.total_pp <= 0
reduction = [3, last_move.pp].min
@@ -757,32 +781,34 @@ end
# For 4 rounds, the target must use the same move each round. (Encore)
#===============================================================================
class Battle::Move::DisableTargetUsingDifferentMove < Battle::Move
attr_reader :moveBlacklist
def ignoresSubstitute?(user); return true; end
def canMagicCoat?; return true; end
def initialize(battle, move)
super
@moveBlacklist = [
"DisableTargetUsingDifferentMove", # Encore
"DisableTargetUsingDifferentMove", # Encore
# Struggle
"Struggle", # Struggle
"Struggle", # Struggle
# Moves that affect the moveset
"ReplaceMoveThisBattleWithTargetLastMoveUsed", # Mimic
"ReplaceMoveWithTargetLastMoveUsed", # Sketch
"TransformUserIntoTarget", # Transform
"ReplaceMoveWithTargetLastMoveUsed", # Sketch
"TransformUserIntoTarget", # Transform
# Moves that call other moves (see also below)
"UseLastMoveUsedByTarget" # Mirror Move
"UseLastMoveUsedByTarget" # Mirror Move
]
if Settings::MECHANICS_GENERATION >= 7
@moveBlacklist += [
# Moves that call other moves
# "UseLastMoveUsedByTarget", # Mirror Move # See above
"UseLastMoveUsed", # Copycat
"UseMoveTargetIsAboutToUse", # Me First
"UseMoveDependingOnEnvironment", # Nature Power
"UseRandomUserMoveIfAsleep", # Sleep Talk
"UseRandomMoveFromUserParty", # Assist
"UseRandomMove" # Metronome
# "UseLastMoveUsedByTarget", # Mirror Move # See above
"UseLastMoveUsed", # Copycat
"UseMoveTargetIsAboutToUse", # Me First
"UseMoveDependingOnEnvironment", # Nature Power
"UseRandomUserMoveIfAsleep", # Sleep Talk
"UseRandomMoveFromUserParty", # Assist
"UseRandomMove" # Metronome
]
end
end
@@ -793,6 +819,7 @@ class Battle::Move::DisableTargetUsingDifferentMove < Battle::Move
return true
end
if !target.lastRegularMoveUsed ||
!GameData::Move.exists?(target.lastRegularMoveUsed) ||
@moveBlacklist.include?(GameData::Move.get(target.lastRegularMoveUsed).function_code)
@battle.pbDisplay(_INTL("But it failed!")) if show_message
return true

View File

@@ -189,7 +189,7 @@ class Battle::Scene
pbShowWindow(MESSAGE_BOX)
cw = @sprites["messageWindow"]
cw.setText(msg)
PBDebug.log(msg)
PBDebug.log_message(msg)
yielded = false
timer = 0.0
loop do
@@ -235,7 +235,7 @@ class Battle::Scene
pbShowWindow(MESSAGE_BOX)
cw = @sprites["messageWindow"]
cw.text = msg + "\1"
PBDebug.log(msg)
PBDebug.log_message(msg)
yielded = false
timer = 0.0
loop do
@@ -283,7 +283,7 @@ class Battle::Scene
cw.z = dw.z + 1
cw.index = 0
cw.viewport = @viewport
PBDebug.log(msg)
PBDebug.log_message(msg)
loop do
cw.visible = (!dw.busy?)
pbUpdate(cw)

View File

@@ -410,7 +410,7 @@ class Battle::Scene
# Returns the animation ID to use for a given move/user. Returns nil if that
# move has no animations defined for it.
def pbFindMoveAnimDetails(move2anim, moveID, idxUser, hitNum = 0)
real_move_id = GameData::Move.get(moveID).id
real_move_id = GameData::Move.try_get(moveID)&.id || moveID
noFlip = false
if (idxUser & 1) == 0 # On player's side
anim = move2anim[0][real_move_id]
@@ -440,7 +440,7 @@ class Battle::Scene
moveType = moveData.type
moveKind = moveData.category
moveKind += 3 if target_data.num_targets > 1 || target_data.affects_foe_side
moveKind += 3 if moveKind == 2 && target_data.num_targets > 0
moveKind += 3 if moveData.status? && target_data.num_targets > 0
# [one target physical, one target special, user status,
# multiple targets physical, multiple targets special, non-user status]
typeDefaultAnim = {

View File

@@ -1,11 +1,12 @@
#===============================================================================
# Used when generating new trainers for battle challenges
#===============================================================================
class Battle::DebugSceneNoLogging
def initialize
@battle = nil
@lastCmd = [0, 0, 0, 0]
@lastMove = [0, 0, 0, 0]
class Battle::DebugSceneNoVisuals
def initialize(log_messages = false)
@battle = nil
@lastCmd = [0, 0, 0, 0]
@lastMove = [0, 0, 0, 0]
@log_messages = log_messages
end
# Called whenever the battle begins.
@@ -42,11 +43,24 @@ class Battle::DebugSceneNoLogging
def pbRefresh; end
def pbRefreshOne(idxBattler); end
def pbDisplayMessage(msg, brief = false); end
def pbDisplayMessage(msg, brief = false)
PBDebug.log_message(msg) if @log_messages
end
alias pbDisplay pbDisplayMessage
def pbDisplayPausedMessage(msg); end
def pbDisplayConfirmMessage(msg); return true; end
def pbShowCommands(msg, commands, defaultValue); return 0; end
def pbDisplayPausedMessage(msg)
PBDebug.log_message(msg) if @log_messages
end
def pbDisplayConfirmMessage(msg)
PBDebug.log_message(msg) if @log_messages
return true
end
def pbShowCommands(msg, commands, defaultValue)
PBDebug.log_message(msg) if @log_messages
return 0
end
def pbSendOutBattlers(sendOuts, startBattle = false); end
def pbRecall(idxBattler); end

View File

@@ -1,69 +1,163 @@
# 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
attr_reader :battle
attr_reader :trainer
attr_reader :battlers
attr_reader :user, :target, :move
def initialize(battle)
@battle = battle
end
def pbAIRandom(x); return rand(x); end
def pbStdDev(choices)
sum = 0
n = 0
choices.each do |c|
sum += c[1]
n += 1
def create_ai_objects
# Initialize AI trainers
@trainers = [[], []]
@battle.player.each_with_index do |trainer, i|
@trainers[0][i] = AITrainer.new(self, 0, i, trainer)
end
return 0 if n < 2
mean = sum.to_f / n
varianceTimesN = 0
choices.each do |c|
next if c[1] <= 0
deviation = c[1].to_f - mean
varianceTimesN += deviation * deviation
if @battle.wildBattle?
@trainers[1][0] = AITrainer.new(self, 1, 0, nil)
else
@battle.opponent.each_with_index do |trainer, i|
@trainers[1][i] = AITrainer.new(self, 1, i, trainer)
end
end
# Using population standard deviation
# [(n-1) makes it a sample std dev, would be 0 with only 1 sample]
return Math.sqrt(varianceTimesN / n)
# Initialize AI battlers
@battlers = []
@battle.battlers.each_with_index do |battler, i|
@battlers[i] = AIBattler.new(self, i) if battler
end
# Initialize AI move object
@move = AIMove.new(self)
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")
return true
end
return false
# Set some class variables for the Pokémon whose action is being chosen
def set_up(idxBattler)
# Find the AI trainer choosing the action
opposes = @battle.opposes?(idxBattler)
trainer_index = @battle.pbGetOwnerIndexFromBattlerIndex(idxBattler)
@trainer = @trainers[(opposes) ? 1 : 0][trainer_index]
# Find the AI battler for which the action is being chosen
@user = @battlers[idxBattler]
@battlers.each { |b| b.refresh_battler if b }
end
#=============================================================================
# Choose an action
#=============================================================================
# Choose an action.
def pbDefaultChooseEnemyCommand(idxBattler)
return if pbEnemyShouldUseItem?(idxBattler)
return if pbEnemyShouldWithdraw?(idxBattler)
return if @battle.pbAutoFightMenu(idxBattler)
@battle.pbRegisterMegaEvolution(idxBattler) if pbEnemyShouldMegaEvolve?(idxBattler)
pbChooseMoves(idxBattler)
set_up(idxBattler)
ret = false
PBDebug.logonerr { ret = pbChooseToSwitchOut }
if ret
PBDebug.log("")
return
end
ret = false
PBDebug.logonerr { ret = pbChooseToUseItem }
if ret
PBDebug.log("")
return
end
if @battle.pbAutoFightMenu(idxBattler)
PBDebug.log("")
return
end
@battle.pbRegisterMegaEvolution(idxBattler) if pbEnemyShouldMegaEvolve?
choices = pbGetMoveScores
pbChooseMove(choices)
PBDebug.log("")
end
# Choose a replacement Pokémon (called directly from @battle, not part of
# action choosing). Must return the party index of a replacement Pokémon if
# possible.
def pbDefaultChooseNewEnemy(idxBattler)
set_up(idxBattler)
return choose_best_replacement_pokemon(idxBattler, true)
end
end
#===============================================================================
#
#===============================================================================
module Battle::AI::Handlers
MoveFailureCheck = HandlerHash.new
MoveFailureAgainstTargetCheck = HandlerHash.new
MoveEffectScore = HandlerHash.new
MoveEffectAgainstTargetScore = HandlerHash.new
MoveBasePower = HandlerHash.new
GeneralMoveScore = HandlerHash.new
GeneralMoveAgainstTargetScore = HandlerHash.new
ShouldSwitch = HandlerHash.new
ShouldNotSwitch = HandlerHash.new
AbilityRanking = AbilityHandlerHash.new
ItemRanking = ItemHandlerHash.new
def self.move_will_fail?(function_code, *args)
return MoveFailureCheck.trigger(function_code, *args) || false
end
def self.move_will_fail_against_target?(function_code, *args)
return MoveFailureAgainstTargetCheck.trigger(function_code, *args) || false
end
def self.apply_move_effect_score(function_code, score, *args)
ret = MoveEffectScore.trigger(function_code, score, *args)
return (ret.nil?) ? score : ret
end
def self.apply_move_effect_against_target_score(function_code, score, *args)
ret = MoveEffectAgainstTargetScore.trigger(function_code, score, *args)
return (ret.nil?) ? score : ret
end
def self.get_base_power(function_code, power, *args)
ret = MoveBasePower.trigger(function_code, power, *args)
return (ret.nil?) ? power : ret
end
def self.apply_general_move_score_modifiers(score, *args)
GeneralMoveScore.each do |id, score_proc|
new_score = score_proc.call(score, *args)
score = new_score if new_score
end
return score
end
def self.apply_general_move_against_target_score_modifiers(score, *args)
GeneralMoveAgainstTargetScore.each do |id, score_proc|
new_score = score_proc.call(score, *args)
score = new_score if new_score
end
return score
end
def self.should_switch?(*args)
ret = false
ShouldSwitch.each do |id, switch_proc|
ret ||= switch_proc.call(*args)
break if ret
end
return ret
end
def self.should_not_switch?(*args)
ret = false
ShouldNotSwitch.each do |id, switch_proc|
ret ||= switch_proc.call(*args)
break if ret
end
return ret
end
def self.modify_ability_ranking(ability, score, *args)
ret = AbilityRanking.trigger(ability, score, *args)
return (ret.nil?) ? score : ret
end
def self.modify_item_ranking(item, score, *args)
ret = ItemRanking.trigger(item, score, *args)
return (ret.nil?) ? score : ret
end
end

View File

@@ -1,170 +0,0 @@
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)
return false if !item
# Determine target of item (always the Pokémon choosing the action)
useType = GameData::Item.get(item).battle_use
if [1, 2, 3].include?(useType) # Use on Pokémon
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}")
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)
return nil if !@battle.internalBattle
items = @battle.pbGetOwnerItems(idxBattler)
return nil if !items || items.length == 0
# Determine target of item (always the Pokémon choosing the action)
idxTarget = idxBattler # Battler using the item
battler = @battle.battlers[idxTarget]
pkmn = battler.pokemon
# Item categories
hpItems = {
:POTION => 20,
:SUPERPOTION => (Settings::REBALANCED_HEALING_ITEM_AMOUNTS) ? 60 : 50,
:HYPERPOTION => (Settings::REBALANCED_HEALING_ITEM_AMOUNTS) ? 120 : 200,
:MAXPOTION => 999,
:BERRYJUICE => 20,
:SWEETHEART => 20,
:FRESHWATER => (Settings::REBALANCED_HEALING_ITEM_AMOUNTS) ? 30 : 50,
:SODAPOP => (Settings::REBALANCED_HEALING_ITEM_AMOUNTS) ? 50 : 60,
:LEMONADE => (Settings::REBALANCED_HEALING_ITEM_AMOUNTS) ? 70 : 80,
:MOOMOOMILK => 100,
:ORANBERRY => 10,
:SITRUSBERRY => battler.totalhp / 4,
:ENERGYPOWDER => (Settings::REBALANCED_HEALING_ITEM_AMOUNTS) ? 60 : 50,
:ENERGYROOT => (Settings::REBALANCED_HEALING_ITEM_AMOUNTS) ? 120 : 200
}
hpItems[:RAGECANDYBAR] = 20 if !Settings::RAGE_CANDY_BAR_CURES_STATUS_PROBLEMS
fullRestoreItems = [
:FULLRESTORE
]
oneStatusItems = [ # Preferred over items that heal all status problems
:AWAKENING, :CHESTOBERRY, :BLUEFLUTE,
:ANTIDOTE, :PECHABERRY,
:BURNHEAL, :RAWSTBERRY,
:PARALYZEHEAL, :PARLYZHEAL, :CHERIBERRY,
:ICEHEAL, :ASPEARBERRY
]
allStatusItems = [
:FULLHEAL, :LAVACOOKIE, :OLDGATEAU, :CASTELIACONE, :LUMIOSEGALETTE,
:SHALOURSABLE, :BIGMALASADA, :PEWTERCRUNCHIES, :LUMBERRY, :HEALPOWDER
]
allStatusItems.push(:RAGECANDYBAR) if Settings::RAGE_CANDY_BAR_CURES_STATUS_PROBLEMS
xItems = {
:XATTACK => [:ATTACK, (Settings::X_STAT_ITEMS_RAISE_BY_TWO_STAGES) ? 2 : 1],
:XATTACK2 => [:ATTACK, 2],
:XATTACK3 => [:ATTACK, 3],
:XATTACK6 => [:ATTACK, 6],
:XDEFENSE => [:DEFENSE, (Settings::X_STAT_ITEMS_RAISE_BY_TWO_STAGES) ? 2 : 1],
:XDEFENSE2 => [:DEFENSE, 2],
:XDEFENSE3 => [:DEFENSE, 3],
:XDEFENSE6 => [:DEFENSE, 6],
:XDEFEND => [:DEFENSE, (Settings::X_STAT_ITEMS_RAISE_BY_TWO_STAGES) ? 2 : 1],
:XDEFEND2 => [:DEFENSE, 2],
:XDEFEND3 => [:DEFENSE, 3],
:XDEFEND6 => [:DEFENSE, 6],
:XSPATK => [:SPECIAL_ATTACK, (Settings::X_STAT_ITEMS_RAISE_BY_TWO_STAGES) ? 2 : 1],
:XSPATK2 => [:SPECIAL_ATTACK, 2],
:XSPATK3 => [:SPECIAL_ATTACK, 3],
:XSPATK6 => [:SPECIAL_ATTACK, 6],
:XSPECIAL => [:SPECIAL_ATTACK, (Settings::X_STAT_ITEMS_RAISE_BY_TWO_STAGES) ? 2 : 1],
:XSPECIAL2 => [:SPECIAL_ATTACK, 2],
:XSPECIAL3 => [:SPECIAL_ATTACK, 3],
:XSPECIAL6 => [:SPECIAL_ATTACK, 6],
:XSPDEF => [:SPECIAL_DEFENSE, (Settings::X_STAT_ITEMS_RAISE_BY_TWO_STAGES) ? 2 : 1],
:XSPDEF2 => [:SPECIAL_DEFENSE, 2],
:XSPDEF3 => [:SPECIAL_DEFENSE, 3],
:XSPDEF6 => [:SPECIAL_DEFENSE, 6],
:XSPEED => [:SPEED, (Settings::X_STAT_ITEMS_RAISE_BY_TWO_STAGES) ? 2 : 1],
:XSPEED2 => [:SPEED, 2],
:XSPEED3 => [:SPEED, 3],
:XSPEED6 => [:SPEED, 6],
:XACCURACY => [:ACCURACY, (Settings::X_STAT_ITEMS_RAISE_BY_TWO_STAGES) ? 2 : 1],
:XACCURACY2 => [:ACCURACY, 2],
:XACCURACY3 => [:ACCURACY, 3],
:XACCURACY6 => [:ACCURACY, 6]
}
losthp = battler.totalhp - battler.hp
preferFullRestore = (battler.hp <= battler.totalhp * 2 / 3 &&
(battler.status != :NONE || battler.effects[PBEffects::Confusion] > 0))
# Find all usable items
usableHPItems = []
usableStatusItems = []
usableXItems = []
items.each do |i|
next if !i
next if !@battle.pbCanUseItemOnPokemon?(i, pkmn, battler, @battle.scene, false)
next if !ItemHandlers.triggerCanUseInBattle(i, pkmn, battler, nil,
false, self, @battle.scene, false)
# Log HP healing items
if losthp > 0
power = hpItems[i]
if power
usableHPItems.push([i, 5, power])
next
end
end
# Log Full Restores (HP healer and status curer)
if fullRestoreItems.include?(i)
usableHPItems.push([i, (preferFullRestore) ? 3 : 7, 999]) if losthp > 0
usableStatusItems.push([i, (preferFullRestore) ? 3 : 9]) if battler.status != :NONE ||
battler.effects[PBEffects::Confusion] > 0
next
end
# Log single status-curing items
if oneStatusItems.include?(i)
usableStatusItems.push([i, 5])
next
end
# Log Full Heal-type items
if allStatusItems.include?(i)
usableStatusItems.push([i, 7])
next
end
# Log stat-raising items
if xItems[i]
data = xItems[i]
usableXItems.push([i, battler.stages[data[0]], data[1]])
next
end
end
# Prioritise using a HP restoration item
if usableHPItems.length > 0 && (battler.hp <= battler.totalhp / 4 ||
(battler.hp <= battler.totalhp / 2 && pbAIRandom(100) < 30))
usableHPItems.sort! { |a, b| (a[1] == b[1]) ? a[2] <=> b[2] : a[1] <=> b[1] }
prevItem = nil
usableHPItems.each do |i|
return i[0], idxTarget if i[2] >= losthp
prevItem = i
end
return prevItem[0], idxTarget
end
# Next prioritise using a status-curing item
if usableStatusItems.length > 0 && pbAIRandom(100) < 40
usableStatusItems.sort! { |a, b| a[1] <=> b[1] }
return usableStatusItems[0][0], idxTarget
end
# Next try using an X item
if usableXItems.length > 0 && pbAIRandom(100) < 30
usableXItems.sort! { |a, b| (a[1] == b[1]) ? a[2] <=> b[2] : a[1] <=> b[1] }
prevItem = nil
usableXItems.each do |i|
break if prevItem && i[1] > prevItem[1]
return i[0], idxTarget if i[1] + i[2] >= 6
prevItem = i
end
return prevItem[0], idxTarget
end
return nil
end
end

View File

@@ -0,0 +1,675 @@
#===============================================================================
#
#===============================================================================
class Battle::AI
# Called by the AI's def pbDefaultChooseEnemyCommand, and by def pbChooseMove
# if the only moves known are bad ones (the latter forces a switch). Also
# aliased by the Battle Palace and Battle Arena.
def pbChooseToSwitchOut(force_switch = false)
return false if @user.wild?
return false if !@battle.pbCanSwitchOut?(@user.index)
# Don't switch if all foes are unable to do anything, e.g. resting after
# Hyper Beam, will Truant (i.e. free turn)
if @trainer.high_skill?
foe_can_act = false
each_foe_battler(@user.side) do |b, i|
next if !b.can_attack?
foe_can_act = true
break
end
return false if !foe_can_act
end
# Various calculations to decide whether to switch
if force_switch
PBDebug.log_ai("#{@user.name} is being forced to switch out")
else
return false if !@trainer.has_skill_flag?("ConsiderSwitching")
reserves = get_non_active_party_pokemon(@user.index)
return false if reserves.empty?
should_switch = Battle::AI::Handlers.should_switch?(@user, reserves, self, @battle)
if should_switch && @trainer.medium_skill?
should_switch = false if Battle::AI::Handlers.should_not_switch?(@user, reserves, self, @battle)
end
return false if !should_switch
end
# Want to switch; find the best replacement Pokémon
idxParty = choose_best_replacement_pokemon(@user.index, force_switch)
if idxParty < 0 # No good replacement Pokémon found
PBDebug.log(" => no good replacement Pokémon, will not switch after all")
return false
end
# Prefer using Baton Pass instead of switching
baton_pass = -1
@user.battler.eachMoveWithIndex do |m, i|
next if m.function != "SwitchOutUserPassOnEffects" # Baton Pass
next if !@battle.pbCanChooseMove?(@user.index, i, false)
baton_pass = i
break
end
if baton_pass >= 0 && @battle.pbRegisterMove(@user.index, baton_pass, false)
PBDebug.log(" => will use Baton Pass to switch out")
return true
elsif @battle.pbRegisterSwitch(@user.index, idxParty)
PBDebug.log(" => will switch with #{@battle.pbParty(@user.index)[idxParty].name}")
return true
end
return false
end
def get_non_active_party_pokemon(idxBattler)
ret = []
@battle.pbParty(idxBattler).each_with_index do |pkmn, i|
ret.push(pkmn) if @battle.pbCanSwitchIn?(idxBattler, i)
end
return ret
end
#-----------------------------------------------------------------------------
def choose_best_replacement_pokemon(idxBattler, mandatory = false)
# Get all possible replacement Pokémon
party = @battle.pbParty(idxBattler)
idxPartyStart, idxPartyEnd = @battle.pbTeamIndexRangeFromBattlerIndex(idxBattler)
reserves = []
party.each_with_index do |_pkmn, i|
next if !@battle.pbCanSwitchIn?(idxBattler, i)
if !mandatory # Not mandatory means choosing an action for the round
ally_will_switch_with_i = false
@battle.allSameSideBattlers(idxBattler).each do |b|
next if @battle.choices[b.index][0] != :SwitchOut || @battle.choices[b.index][1] != i
ally_will_switch_with_i = true
break
end
next if ally_will_switch_with_i
end
# Ignore ace if possible
if @trainer.has_skill_flag?("ReserveLastPokemon") && i == idxPartyEnd - 1
next if !mandatory || reserves.length > 0
end
reserves.push([i, 100])
break if @trainer.has_skill_flag?("UsePokemonInOrder") && reserves.length > 0
end
return -1 if reserves.length == 0
# Rate each possible replacement Pokémon
reserves.each_with_index do |reserve, i|
reserves[i][1] = rate_replacement_pokemon(idxBattler, party[reserve[0]], reserve[1])
end
reserves.sort! { |a, b| b[1] <=> a[1] } # Sort from highest to lowest rated
# Don't bother choosing to switch if all replacements are poorly rated
if @trainer.high_skill? && !mandatory
return -1 if reserves[0][1] < 100 # If best replacement rated at <100, don't switch
end
# Return the party index of the best rated replacement Pokémon
return reserves[0][0]
end
def rate_replacement_pokemon(idxBattler, pkmn, score)
pkmn_types = pkmn.types
entry_hazard_damage = calculate_entry_hazard_damage(pkmn, idxBattler & 1)
if entry_hazard_damage >= pkmn.hp
score -= 50 # pkmn will just faint
elsif entry_hazard_damage > 0
score -= 50 * entry_hazard_damage / pkmn.hp
end
if !pkmn.hasItem?(:HEAVYDUTYBOOTS) && !pokemon_airborne?(pkmn)
# Toxic Spikes
if @user.pbOwnSide.effects[PBEffects::ToxicSpikes] > 0
score -= 20 if pokemon_can_be_poisoned?(pkmn)
end
# Sticky Web
if @user.pbOwnSide.effects[PBEffects::ToxicSpikes]
score -= 15
end
end
# Predict effectiveness of foe's last used move against pkmn
each_foe_battler(@user.side) do |b, i|
next if !b.battler.lastMoveUsed
move_data = GameData::Move.try_get(b.battler.lastMoveUsed)
next if !move_data || move_data.status?
move_type = move_data.type
eff = Effectiveness.calculate(move_type, *pkmn_types)
score -= move_data.power * eff / 5
end
# Add power * effectiveness / 10 of all pkmn's moves to score
pkmn.moves.each do |m|
next if m.power == 0 || (m.pp == 0 && m.total_pp > 0)
@battle.battlers[idxBattler].allOpposing.each do |b|
next if pokemon_can_absorb_move?(b.pokemon, m, m.type)
bTypes = b.pbTypes(true)
score += m.power * Effectiveness.calculate(m.type, *bTypes) / 10
end
end
# Prefer if pkmn has lower HP and its position will be healed by Wish
position = @battle.positions[idxBattler]
if position.effects[PBEffects::Wish] > 0
amt = position.effects[PBEffects::WishAmount]
if pkmn.totalhp - pkmn.hp > amt * 2 / 3
score += 20 * [pkmn.totalhp - pkmn.hp, amt].min / pkmn.totalhp
end
end
# Prefer if user is about to faint from Perish Song
score += 20 if @user.effects[PBEffects::PerishSong] == 1
return score
end
def calculate_entry_hazard_damage(pkmn, side)
return 0 if pkmn.hasAbility?(:MAGICGUARD) || pkmn.hasItem?(:HEAVYDUTYBOOTS)
ret = 0
# Stealth Rock
if @battle.sides[side].effects[PBEffects::StealthRock] && GameData::Type.exists?(:ROCK)
pkmn_types = pkmn.types
eff = Effectiveness.calculate(:ROCK, *pkmn_types)
ret += pkmn.totalhp * eff / 8 if !Effectiveness.ineffective?(eff)
end
# Spikes
if @battle.sides[side].effects[PBEffects::Spikes] > 0 && !pokemon_airborne?(pkmn)
spikes_div = [8, 6, 4][@battle.sides[side].effects[PBEffects::Spikes] - 1]
ret += pkmn.totalhp / spikes_div
end
return ret
end
end
#===============================================================================
# Pokémon is about to faint because of Perish Song.
#===============================================================================
Battle::AI::Handlers::ShouldSwitch.add(:perish_song,
proc { |battler, reserves, ai, battle|
if battler.effects[PBEffects::PerishSong] == 1
PBDebug.log_ai("#{battler.name} wants to switch because it is about to faint from Perish Song")
next true
end
next false
}
)
#===============================================================================
# Pokémon will take a significant amount of damage at the end of this round, or
# it has an effect that causes it damage at the end of this round which it can
# remove by switching.
#===============================================================================
Battle::AI::Handlers::ShouldSwitch.add(:significant_eor_damage,
proc { |battler, reserves, ai, battle|
eor_damage = battler.rough_end_of_round_damage
# Switch if battler will take significant EOR damage
if eor_damage >= battler.hp / 2 || eor_damage >= battler.totalhp / 4
PBDebug.log_ai("#{battler.name} wants to switch because it will take a lot of EOR damage")
next true
end
# Switch to remove certain effects that cause the battler EOR damage
if ai.trainer.high_skill? && eor_damage > 0
if battler.effects[PBEffects::LeechSeed] >= 0 && ai.pbAIRandom(100) < 50
PBDebug.log_ai("#{battler.name} wants to switch to get rid of its Leech Seed")
next true
end
if battler.effects[PBEffects::Nightmare]
PBDebug.log_ai("#{battler.name} wants to switch to get rid of its Nightmare")
next true
end
if battler.effects[PBEffects::Curse]
PBDebug.log_ai("#{battler.name} wants to switch to get rid of its Curse")
next true
end
if battler.status == :POISON && battler.statusCount > 0 && !battler.has_active_ability?(:POISONHEAL)
poison_damage = battler.totalhp / 8
next_toxic_damage = battler.totalhp * (battler.effects[PBEffects::Toxic] + 1) / 16
if (battler.hp <= next_toxic_damage && battler.hp > poison_damage) ||
next_toxic_damage > poison_damage * 2
PBDebug.log_ai("#{battler.name} wants to switch to reduce toxic to regular poisoning")
next true
end
end
end
next false
}
)
#===============================================================================
# Pokémon can cure its status problem or heal some HP with its ability by
# switching out. Covers all abilities with an OnSwitchOut AbilityEffects
# handler.
#===============================================================================
Battle::AI::Handlers::ShouldSwitch.add(:cure_status_problem_by_switching_out,
proc { |battler, reserves, ai, battle|
next false if !battler.ability_active?
# Don't try to cure a status problem/heal a bit of HP if entry hazards will
# KO the battler if it switches back in
entry_hazard_damage = ai.calculate_entry_hazard_damage(battler.pokemon, battler.side)
next false if entry_hazard_damage >= battler.hp
# Check specific abilities
single_status_cure = {
:IMMUNITY => :POISON,
:INSOMNIA => :SLEEP,
:LIMBER => :PARALYSIS,
:MAGMAARMOR => :FROZEN,
:VITALSPIRIT => :SLEEP,
:WATERBUBBLE => :BURN,
:WATERVEIL => :BURN
}[battler.ability_id]
if battler.ability == :NATURALCURE || (single_status_cure && single_status_cure == battler.status)
# Cures status problem
next false if battler.wants_status_problem?(battler.status)
next false if battler.status == :SLEEP && battler.statusCount == 1 # Will wake up this round anyway
next false if entry_hazard_damage >= battler.totalhp / 4
# Don't bother curing a poisoning if Toxic Spikes will just re-poison the
# battler when it switches back in
if battler.status == :POISON && reserves.none? { |pkmn| pkmn.hasType?(:POISON) }
next false if battle.field.effects[PBEffects::ToxicSpikes] == 2
next false if battle.field.effects[PBEffects::ToxicSpikes] == 1 && battler.statusCount == 0
end
# Not worth curing status problems that still allow actions if at high HP
next false if battler.hp >= battler.totalhp / 2 && ![:SLEEP, :FROZEN].include?(battler.status)
if ai.pbAIRandom(100) < 70
PBDebug.log_ai("#{battler.name} wants to switch to cure its status problem with #{battler.ability.name}")
next true
end
elsif battler.ability == :REGENERATOR
# Not worth healing if battler would lose more HP from switching back in later
next false if entry_hazard_damage >= battler.totalhp / 3
# Not worth healing HP if already at high HP
next false if battler.hp >= battler.totalhp / 2
# Don't bother if a foe is at low HP and could be knocked out instead
if battler.check_for_move { |m| m.damagingMove? }
weak_foe = false
ai.each_foe_battler(battler.side) do |b, i|
weak_foe = true if b.hp < b.totalhp / 3
break if weak_foe
end
next false if weak_foe
end
if ai.pbAIRandom(100) < 70
PBDebug.log_ai("#{battler.name} wants to switch to heal with #{battler.ability.name}")
next true
end
end
next false
}
)
#===============================================================================
# Pokémon's position is about to be healed by Wish, and a reserve can benefit
# more from that healing than the Pokémon can.
#===============================================================================
Battle::AI::Handlers::ShouldSwitch.add(:wish_healing,
proc { |battler, reserves, ai, battle|
position = battle.positions[battler.index]
next false if position.effects[PBEffects::Wish] == 0
amt = position.effects[PBEffects::WishAmount]
next false if battler.totalhp - battler.hp >= amt * 2 / 3 # Want to heal itself instead
reserve_wants_healing_more = false
reserves.each do |pkmn|
entry_hazard_damage = ai.calculate_entry_hazard_damage(pkmn, battler.index & 1)
next if entry_hazard_damage >= pkmn.hp
reserve_wants_healing_more = (pkmn.totalhp - pkmn.hp - entry_hazard_damage >= amt * 2 / 3)
break if reserve_wants_healing_more
end
if reserve_wants_healing_more
PBDebug.log_ai("#{battler.name} wants to switch because Wish can heal a reserve more")
next true
end
next false
}
)
#===============================================================================
# Pokémon is yawning and can't do anything while asleep.
#===============================================================================
Battle::AI::Handlers::ShouldSwitch.add(:yawning,
proc { |battler, reserves, ai, battle|
# Yawning and can fall asleep because of it
next false if battler.effects[PBEffects::Yawn] == 0 || !battler.battler.pbCanSleepYawn?
# Doesn't want to be asleep (includes checking for moves usable while asleep)
next false if battler.wants_status_problem?(:SLEEP)
# Can't cure itself of sleep
if battler.ability_active?
next false if [:INSOMNIA, :NATURALCURE, :REGENERATOR, :SHEDSKIN].include?(battler.ability_id)
next false if battler.ability_id == :HYDRATION && [:Rain, :HeavyRain].include?(battler.battler.effectiveWeather)
end
next false if battler.has_active_item?([:CHESTOBERRY, :LUMBERRY]) && battler.battler.canConsumeBerry?
# Ally can't cure sleep
ally_can_heal = false
ai.each_ally(battler.index) do |b, i|
ally_can_heal = b.has_active_ability?(:HEALER)
break if ally_can_heal
end
next false if ally_can_heal
# Doesn't benefit from being asleep/isn't less affected by sleep
next false if battler.has_active_ability?([:EARLYBIRD, :MARVELSCALE])
# Not trapping another battler in battle
if ai.trainer.high_skill?
next false if ai.battlers.any? do |b|
b.effects[PBEffects::JawLock] == battler.index ||
b.effects[PBEffects::MeanLook] == battler.index ||
b.effects[PBEffects::Octolock] == battler.index ||
b.effects[PBEffects::TrappingUser] == battler.index
end
trapping = false
ai.each_foe_battler(battler.side) do |b, i|
next if b.ability_active? && Battle::AbilityEffects.triggerCertainSwitching(b.ability, b, battle)
next if b.item_active? && Battle::ItemEffects.triggerCertainSwitching(b.item, b, battle)
next if Settings::MORE_TYPE_EFFECTS && b.has_type?(:GHOST)
next if b.battler.trappedInBattle? # Relevant trapping effects are checked above
if battler.ability_active?
trapping = Battle::AbilityEffects.triggerTrappingByTarget(battler.ability, b, battler.battler, battle)
break if trapping
end
if battler.item_active?
trapping = Battle::ItemEffects.triggerTrappingByTarget(battler.item, b, battler.battler, battle)
break if trapping
end
end
next false if trapping
end
# Doesn't have sufficiently raised stats that would be lost by switching
next false if battler.stages.any? { |key, val| val >= 2 }
PBDebug.log_ai("#{battler.name} wants to switch because it is yawning and can't do anything while asleep")
next true
}
)
#===============================================================================
# Pokémon is asleep, won't wake up soon and can't do anything while asleep.
#===============================================================================
Battle::AI::Handlers::ShouldSwitch.add(:asleep,
proc { |battler, reserves, ai, battle|
# Asleep and won't wake up this round or next round
next false if battler.status != :SLEEP || battler.statusCount <= 2
# Doesn't want to be asleep (includes checking for moves usable while asleep)
next false if battler.wants_status_problem?(:SLEEP)
# Doesn't benefit from being asleep
next false if battler.has_active_ability?(:MARVELSCALE)
# Doesn't know Rest (if it does, sleep is expected, so don't apply this check)
next false if battler.check_for_move { |m| m.function == "HealUserFullyAndFallAsleep" }
# Not trapping another battler in battle
if ai.trainer.high_skill?
next false if ai.battlers.any? do |b|
b.effects[PBEffects::JawLock] == battler.index ||
b.effects[PBEffects::MeanLook] == battler.index ||
b.effects[PBEffects::Octolock] == battler.index ||
b.effects[PBEffects::TrappingUser] == battler.index
end
trapping = false
ai.each_foe_battler(battler.side) do |b, i|
next if b.ability_active? && Battle::AbilityEffects.triggerCertainSwitching(b.ability, b, battle)
next if b.item_active? && Battle::ItemEffects.triggerCertainSwitching(b.item, b, battle)
next if Settings::MORE_TYPE_EFFECTS && b.has_type?(:GHOST)
next if b.battler.trappedInBattle? # Relevant trapping effects are checked above
if battler.ability_active?
trapping = Battle::AbilityEffects.triggerTrappingByTarget(battler.ability, b, battler.battler, battle)
break if trapping
end
if battler.item_active?
trapping = Battle::ItemEffects.triggerTrappingByTarget(battler.item, b, battler.battler, battle)
break if trapping
end
end
next false if trapping
end
# Doesn't have sufficiently raised stats that would be lost by switching
next false if battler.stages.any? { |key, val| val >= 2 }
# 50% chance to not bother
next false if ai.pbAIRandom(100) < 50
PBDebug.log_ai("#{battler.name} wants to switch because it is asleep and can't do anything")
next true
}
)
#===============================================================================
# Pokémon can't use any moves and isn't Destiny Bonding/Grudging/hiding behind a
# Substitute.
#===============================================================================
Battle::AI::Handlers::ShouldSwitch.add(:battler_is_useless,
proc { |battler, reserves, ai, battle|
next false if !ai.trainer.medium_skill?
next false if battler.turnCount < 2 # Just switched in, give it a chance
next false if battle.pbCanChooseAnyMove?(battler.index)
next false if battler.effects[PBEffects::DestinyBond] || battler.effects[PBEffects::Grudge]
if battler.effects[PBEffects::Substitute]
hidden_behind_substitute = true
ai.each_foe_battler(battler.side) do |b, i|
next if !b.check_for_move { |m| m.ignoresSubstitute?(b.battler) }
hidden_behind_substitute = false
break
end
next false if hidden_behind_substitute
end
PBDebug.log_ai("#{battler.name} wants to switch because it can't do anything")
next true
}
)
#===============================================================================
# Pokémon can't do anything to any foe because its ability absorbs all damage
# the Pokémon can deal out.
#===============================================================================
Battle::AI::Handlers::ShouldSwitch.add(:foe_absorbs_all_moves_with_its_ability,
proc { |battler, reserves, ai, battle|
next false if battler.battler.turnCount < 2 # Don't switch out too quickly
next false if battler.battler.hasMoldBreaker?
# Check if battler can damage any of its foes
can_damage_foe = false
ai.each_foe_battler(battler.side) do |b, i|
if ai.trainer.high_skill? && b.rough_end_of_round_damage > 0
can_damage_foe = true # Foe is being damaged already
break
end
# Check for battler's moves that can damage the foe (b)
battler.battler.eachMove do |move|
next if move.statusMove?
if ["IgnoreTargetAbility",
"CategoryDependsOnHigherDamageIgnoreTargetAbility"].include?(move.function)
can_damage_foe = true
break
end
if !ai.pokemon_can_absorb_move?(b, move, move.pbCalcType(battler.battler))
can_damage_foe = true
break
end
end
break if can_damage_foe
end
next false if can_damage_foe
# Check if a reserve could damage any foe; only switch if one could
reserve_can_damage_foe = false
reserves.each do |pkmn|
ai.each_foe_battler(battler.side) do |b, i|
# Check for reserve's moves that can damage the foe (b)
pkmn.moves.each do |move|
next if move.status_move?
if ["IgnoreTargetAbility",
"CategoryDependsOnHigherDamageIgnoreTargetAbility"].include?(move.function_code)
reserve_can_damage_foe = true
break
end
if !ai.pokemon_can_absorb_move?(b, move, move.type)
reserve_can_damage_foe = true
break
end
end
break if reserve_can_damage_foe
end
break if reserve_can_damage_foe
end
next false if !reserve_can_damage_foe
PBDebug.log_ai("#{battler.name} wants to switch because it can't damage the foe(s)")
next true
}
)
#===============================================================================
# Pokémon doesn't have an ability that makes it immune to a foe's move, but a
# reserve does (see def pokemon_can_absorb_move?). The foe's move is chosen
# randomly, or is their most powerful move if the trainer's skill level is good
# enough.
#===============================================================================
Battle::AI::Handlers::ShouldSwitch.add(:absorb_foe_move,
proc { |battler, reserves, ai, battle|
next false if !ai.trainer.medium_skill?
# Not worth it if the battler is evasive enough
next false if battler.stages[:EVASION] >= 3
# Not worth it if abilities are being negated
next false if battle.pbCheckGlobalAbility(:NEUTRALIZINGGAS)
# Get the foe move with the highest power (or a random damaging move)
foe_moves = []
ai.each_foe_battler(battler.side) do |b, i|
b.moves.each do |move|
next if move.statusMove?
m_power = move.power
m_power = 100 if move.is_a?(Battle::Move::OHKO)
m_type = move.pbCalcType(b.battler)
foe_moves.push([m_power, m_type, move])
end
end
next false if foe_moves.empty?
if ai.trainer.high_skill?
foe_moves.sort! { |a, b| a[0] <=> b[0] } # Highest power move
chosen_move = foe_moves.last
else
chosen_move = foe_moves[ai.pbAIRandom(foe_moves.length)] # Random move
end
# Get the chosen move's information
move_power = chosen_move[0]
move_type = chosen_move[1]
move = chosen_move[2]
# Don't bother if the foe's best move isn't too strong
next false if move_power < 70
# Check battler for absorbing ability
next false if ai.pokemon_can_absorb_move?(battler, move, move_type)
# battler can't absorb move; find a party Pokémon that can
if reserves.any? { |pkmn| ai.pokemon_can_absorb_move?(pkmn, move, move_type) }
next false if ai.pbAIRandom(100) < 70
PBDebug.log_ai("#{battler.name} wants to switch because it can't absorb a foe's move but a reserve can")
next true
end
next false
}
)
#===============================================================================
# Sudden Death rule (at the end of each round, if one side has more able Pokémon
# than the other side, that side wins). Avoid fainting at all costs.
# NOTE: This rule isn't used anywhere.
#===============================================================================
Battle::AI::Handlers::ShouldSwitch.add(:sudden_death,
proc { |battler, reserves, ai, battle|
next false if !battle.rules["suddendeath"] || battler.turnCount == 0
if battler.hp <= battler.totalhp / 2
threshold = 100 * (battler.totalhp - battler.hp) / battler.totalhp
if ai.pbAIRandom(100) < threshold
PBDebug.log_ai("#{battler.name} wants to switch to avoid being KO'd and losing because of the sudden death rule")
next true
end
end
next false
}
)
#===============================================================================
# Pokémon is within 5 levels of the foe, and foe's last move was super-effective
# and powerful.
#===============================================================================
Battle::AI::Handlers::ShouldSwitch.add(:high_damage_from_foe,
proc { |battler, reserves, ai, battle|
next false if !ai.trainer.high_skill?
next false if battler.hp >= battler.totalhp / 2
big_threat = false
ai.each_foe_battler(battler.side) do |b, i|
next if (b.level - battler.level).abs > 5
next if !b.battler.lastMoveUsed || !GameData::Move.exists?(b.battler.lastMoveUsed)
move_data = GameData::Move.get(b.battler.lastMoveUsed)
next if move_data.status?
eff = battler.effectiveness_of_type_against_battler(move_data.type, b)
next if !Effectiveness.super_effective?(eff) || move_data.power < 70
switch_chance = (move_data.power > 90) ? 50 : 25
big_threat = (ai.pbAIRandom(100) < switch_chance)
break if big_threat
end
if big_threat
PBDebug.log_ai("#{battler.name} wants to switch because a foe has a powerful super-effective move")
next true
end
next false
}
)
#===============================================================================
#===============================================================================
#===============================================================================
#===============================================================================
# Don't bother switching if the battler will just faint from entry hazard damage
# upon switching back in, and if no reserve can remove the entry hazard(s).
# Switching out in this case means the battler becomes unusable, so it might as
# well stick around instead and do as much as it can.
#===============================================================================
Battle::AI::Handlers::ShouldNotSwitch.add(:lethal_entry_hazards,
proc { |battler, reserves, ai, battle|
next false if battle.rules["suddendeath"]
# Check whether battler will faint from entry hazard(s)
entry_hazard_damage = ai.calculate_entry_hazard_damage(battler.pokemon, battler.side)
next false if entry_hazard_damage < battler.hp
# Check for Rapid Spin
reserve_can_remove_hazards = false
reserves.each do |pkmn|
pkmn.moves.each do |move|
reserve_can_remove_hazards = (move.function_code == "RemoveUserBindingAndEntryHazards")
break if reserve_can_remove_hazards
end
break if reserve_can_remove_hazards
end
next false if reserve_can_remove_hazards
PBDebug.log_ai("#{battler.name} won't switch after all because it will faint from entry hazards if it switches back in")
next true
}
)
#===============================================================================
# Don't bother switching (50% chance) if the battler knows a super-effective
# move.
#===============================================================================
Battle::AI::Handlers::ShouldNotSwitch.add(:battler_has_super_effective_move,
proc { |battler, reserves, ai, battle|
next false if battler.effects[PBEffects::PerishSong] == 1
next false if battle.rules["suddendeath"]
has_super_effective_move = false
battler.battler.eachMove do |move|
next if move.pp == 0 && move.total_pp > 0
next if move.statusMove?
# NOTE: Ideally this would ignore moves that are unusable, but that would
# be too complicated to implement.
move_type = move.type
move_type = move.pbCalcType(battler.battler) if ai.trainer.medium_skill?
ai.each_foe_battler(battler.side) do |b|
# NOTE: Ideally this would ignore foes that move cannot target, but that
# is complicated enough to implement that I'm not bothering. It's
# also rare that it would matter.
eff = b.effectiveness_of_type_against_battler(move_type, battler, move)
has_super_effective_move = Effectiveness.super_effective?(eff)
break if has_super_effective_move
end
break if has_super_effective_move
end
if has_super_effective_move && ai.pbAIRandom(100) < 50
PBDebug.log_ai("#{battler.name} won't switch after all because it has a super-effective move")
next true
end
next false
}
)
#===============================================================================
# Don't bother switching if the battler has 4 or more positive stat stages.
# Negative stat stages are ignored.
#===============================================================================
Battle::AI::Handlers::ShouldNotSwitch.add(:battler_has_very_raised_stats,
proc { |battler, reserves, ai, battle|
next false if battle.rules["suddendeath"]
stat_raises = 0
battler.stages.each_value { |val| stat_raises += val if val > 0 }
if stat_raises >= 4
PBDebug.log_ai("#{battler.name} won't switch after all because it has a lot of raised stats")
next true
end
next false
}
)

View File

@@ -1,181 +0,0 @@
class Battle::AI
#=============================================================================
# Decide whether the opponent should switch Pokémon
#=============================================================================
def pbEnemyShouldWithdraw?(idxBattler)
return pbEnemyShouldWithdrawEx?(idxBattler, false)
end
def pbEnemyShouldWithdrawEx?(idxBattler, forceSwitch)
return false if @battle.wildBattle?
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 !target.fainted? && target.lastMoveUsed &&
(target.level - battler.level).abs <= 6
moveData = GameData::Move.get(target.lastMoveUsed)
moveType = moveData.type
typeMod = pbCalcTypeMod(moveType, target, battler)
if Effectiveness.super_effective?(typeMod) && moveData.base_damage > 50
switchChance = (moveData.base_damage > 70) ? 30 : 20
shouldSwitch = (pbAIRandom(100) < switchChance)
end
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
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|
next if m.function != "SwitchOutUserPassOnEffects" # Baton Pass
next if !@battle.pbCanChooseMove?(idxBattler, 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
end
end
# Pokémon is Encored into an unfavourable move
if battler.effects[PBEffects::Encore] > 0 && skill >= PBTrainerAI.mediumSkill
idxEncoredMove = battler.pbEncoredMoveIndex
if idxEncoredMove >= 0
scoreSum = 0
scoreCount = 0
battler.allOpposing.each do |b|
scoreSum += pbGetMoveScore(battler.moves[idxEncoredMove], battler, b, skill)
scoreCount += 1
end
if scoreCount > 0 && scoreSum / scoreCount <= 20 && pbAIRandom(100) < 80
shouldSwitch = true
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 (opp.effects[PBEffects::HyperBeam] > 0 ||
(opp.hasActiveAbility?(:TRUANT) && opp.effects[PBEffects::Truant])) && pbAIRandom(100) < 80
shouldSwitch = false
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
shouldSwitch = true
elsif battler.hp <= battler.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
shouldSwitch = true
end
if shouldSwitch
list = []
idxPartyStart, idxPartyEnd = @battle.pbTeamIndexRangeFromBattlerIndex(idxBattler)
@battle.pbParty(idxBattler).each_with_index do |pkmn, i|
next if i == idxPartyEnd - 1 # Don't choose to switch in ace
next if !@battle.pbCanSwitch?(idxBattler, 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
# Will contain effects that recommend against switching
spikes = battler.pbOwnSide.effects[PBEffects::Spikes]
# Don't switch to this if too little HP
if spikes > 0
spikesDmg = [8, 6, 4][spikes - 1]
next if pkmn.hp <= pkmn.totalhp / spikesDmg &&
!pkmn.hasType?(:FLYING) && !pkmn.hasActiveAbility?(:LEVITATE)
end
end
# moveType is the type of the target's last used move
if moveType && Effectiveness.ineffective?(pbCalcTypeMod(moveType, battler, battler))
weight = 65
typeMod = pbCalcTypeModPokemon(pkmn, battler.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))
weight = 40
typeMod = pbCalcTypeModPokemon(pkmn, battler.pbDirectOpposing(true))
if Effectiveness.super_effective?(typeMod)
# Greater weight if new Pokemon's type is effective against target
weight = 60
end
list.unshift(i) if pbAIRandom(100) < weight # Put this Pokemon first
else
list.push(i) # put this Pokemon last
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")
return true
end
if @battle.pbRegisterSwitch(idxBattler, list[0])
PBDebug.log("[AI] #{battler.pbThis} (#{idxBattler}) will switch with " +
@battle.pbParty(idxBattler)[list[0]].name)
return true
end
end
end
return false
end
#=============================================================================
# Choose a replacement Pokémon
#=============================================================================
def pbDefaultChooseNewEnemy(idxBattler, party)
enemies = []
idxPartyStart, idxPartyEnd = @battle.pbTeamIndexRangeFromBattlerIndex(idxBattler)
party.each_with_index do |_p, i|
next if i == idxPartyEnd - 1 && enemies.length > 0 # Ignore ace if possible
enemies.push(i) if @battle.pbCanSwitchLax?(idxBattler, i)
end
return -1 if enemies.length == 0
return pbChooseBestNewEnemy(idxBattler, party, enemies)
end
def pbChooseBestNewEnemy(idxBattler, party, enemies)
return -1 if !enemies || enemies.length == 0
best = -1
bestSum = 0
enemies.each do |i|
pkmn = party[i]
sum = 0
pkmn.moves.each do |m|
next if m.base_damage == 0
@battle.battlers[idxBattler].allOpposing.each do |b|
bTypes = b.pbTypes(true)
sum += Effectiveness.calculate(m.type, bTypes[0], bTypes[1], bTypes[2])
end
end
if best == -1 || sum > bestSum
best = i
bestSum = sum
end
end
return best
end
end

View File

@@ -0,0 +1,220 @@
#===============================================================================
#
#===============================================================================
class Battle::AI
HP_HEAL_ITEMS = {
:POTION => 20,
:SUPERPOTION => (Settings::REBALANCED_HEALING_ITEM_AMOUNTS) ? 60 : 50,
:HYPERPOTION => (Settings::REBALANCED_HEALING_ITEM_AMOUNTS) ? 120 : 200,
:MAXPOTION => 999,
:BERRYJUICE => 20,
:SWEETHEART => 20,
:FRESHWATER => (Settings::REBALANCED_HEALING_ITEM_AMOUNTS) ? 30 : 50,
:SODAPOP => (Settings::REBALANCED_HEALING_ITEM_AMOUNTS) ? 50 : 60,
:LEMONADE => (Settings::REBALANCED_HEALING_ITEM_AMOUNTS) ? 70 : 80,
:MOOMOOMILK => 100,
:ORANBERRY => 10,
:SITRUSBERRY => 1, # Actual amount is determined below (pkmn.totalhp / 4)
:ENERGYPOWDER => (Settings::REBALANCED_HEALING_ITEM_AMOUNTS) ? 60 : 50,
:ENERGYROOT => (Settings::REBALANCED_HEALING_ITEM_AMOUNTS) ? 120 : 200
}
HP_HEAL_ITEMS[:RAGECANDYBAR] = 20 if !Settings::RAGE_CANDY_BAR_CURES_STATUS_PROBLEMS
FULL_RESTORE_ITEMS = [
:FULLRESTORE
]
ONE_STATUS_CURE_ITEMS = [ # Preferred over items that heal all status problems
:AWAKENING, :CHESTOBERRY, :BLUEFLUTE,
:ANTIDOTE, :PECHABERRY,
:BURNHEAL, :RAWSTBERRY,
:PARALYZEHEAL, :PARLYZHEAL, :CHERIBERRY,
:ICEHEAL, :ASPEARBERRY
]
ALL_STATUS_CURE_ITEMS = [
:FULLHEAL, :LAVACOOKIE, :OLDGATEAU, :CASTELIACONE, :LUMIOSEGALETTE,
:SHALOURSABLE, :BIGMALASADA, :PEWTERCRUNCHIES, :LUMBERRY, :HEALPOWDER
]
ALL_STATUS_CURE_ITEMS.push(:RAGECANDYBAR) if Settings::RAGE_CANDY_BAR_CURES_STATUS_PROBLEMS
ONE_STAT_RAISE_ITEMS = {
:XATTACK => [:ATTACK, (Settings::X_STAT_ITEMS_RAISE_BY_TWO_STAGES) ? 2 : 1],
:XATTACK2 => [:ATTACK, 2],
:XATTACK3 => [:ATTACK, 3],
:XATTACK6 => [:ATTACK, 6],
:XDEFENSE => [:DEFENSE, (Settings::X_STAT_ITEMS_RAISE_BY_TWO_STAGES) ? 2 : 1],
:XDEFENSE2 => [:DEFENSE, 2],
:XDEFENSE3 => [:DEFENSE, 3],
:XDEFENSE6 => [:DEFENSE, 6],
:XDEFEND => [:DEFENSE, (Settings::X_STAT_ITEMS_RAISE_BY_TWO_STAGES) ? 2 : 1],
:XDEFEND2 => [:DEFENSE, 2],
:XDEFEND3 => [:DEFENSE, 3],
:XDEFEND6 => [:DEFENSE, 6],
:XSPATK => [:SPECIAL_ATTACK, (Settings::X_STAT_ITEMS_RAISE_BY_TWO_STAGES) ? 2 : 1],
:XSPATK2 => [:SPECIAL_ATTACK, 2],
:XSPATK3 => [:SPECIAL_ATTACK, 3],
:XSPATK6 => [:SPECIAL_ATTACK, 6],
:XSPECIAL => [:SPECIAL_ATTACK, (Settings::X_STAT_ITEMS_RAISE_BY_TWO_STAGES) ? 2 : 1],
:XSPECIAL2 => [:SPECIAL_ATTACK, 2],
:XSPECIAL3 => [:SPECIAL_ATTACK, 3],
:XSPECIAL6 => [:SPECIAL_ATTACK, 6],
:XSPDEF => [:SPECIAL_DEFENSE, (Settings::X_STAT_ITEMS_RAISE_BY_TWO_STAGES) ? 2 : 1],
:XSPDEF2 => [:SPECIAL_DEFENSE, 2],
:XSPDEF3 => [:SPECIAL_DEFENSE, 3],
:XSPDEF6 => [:SPECIAL_DEFENSE, 6],
:XSPEED => [:SPEED, (Settings::X_STAT_ITEMS_RAISE_BY_TWO_STAGES) ? 2 : 1],
:XSPEED2 => [:SPEED, 2],
:XSPEED3 => [:SPEED, 3],
:XSPEED6 => [:SPEED, 6],
:XACCURACY => [:ACCURACY, (Settings::X_STAT_ITEMS_RAISE_BY_TWO_STAGES) ? 2 : 1],
:XACCURACY2 => [:ACCURACY, 2],
:XACCURACY3 => [:ACCURACY, 3],
:XACCURACY6 => [:ACCURACY, 6]
}
ALL_STATS_RAISE_ITEMS = [
:MAXMUSHROOMS
]
REVIVE_ITEMS = {
:REVIVE => 5,
:MAXREVIVE => 7,
:REVIVALHERB => 7,
:MAXHONEY => 7
}
#-----------------------------------------------------------------------------
# Decide whether the opponent should use an item on the Pokémon.
def pbChooseToUseItem
item = nil
idxTarget = nil # Party index (battle_use type 1/2/3) or battler index
idxMove = nil
item, idxTarget, idxMove = choose_item_to_use
return false if !item
# Register use of item
@battle.pbRegisterItem(@user.index, item, idxTarget, idxMove)
PBDebug.log_ai("#{@user.name} will use item #{GameData::Item.get(item).name}")
return true
end
# Return values are:
# item ID
# target index (party index for items with a battle use of 1/2/3, battler
# index otherwise)
# move index (for items usable on moves only)
def choose_item_to_use
return nil if !@battle.internalBattle
items = @battle.pbGetOwnerItems(@user.index)
return nil if !items || items.length == 0
# Find all items usable on the Pokémon choosing this action
pkmn = @user.battler.pokemon
usable_items = {}
items.each do |item|
usage = get_usability_of_item_on_pkmn(item, @user.party_index, @user.side)
usage.each_pair do |key, vals|
usable_items[key] ||= []
usable_items[key] += vals
end
end
# Prioritise using a HP restoration item
if usable_items[:hp_heal] && (pkmn.hp <= pkmn.totalhp / 4 ||
(pkmn.hp <= pkmn.totalhp / 2 && pbAIRandom(100) < 30))
usable_items[:hp_heal].sort! { |a, b| (a[2] == b[2]) ? a[3] <=> b[3] : a[2] <=> b[2] }
usable_items[:hp_heal].each do |item|
return item[0], item[1] if item[3] >= (pkmn.totalhp - pkmn.hp) * 0.75
end
return usable_items[:hp_heal].last[0], usable_items[:hp_heal].last[1]
end
# Next prioritise using a status-curing item
if usable_items[:status_cure] &&
([:SLEEP, :FROZEN].include?(pkmn.status) || pbAIRandom(100) < 40)
usable_items[:status_cure].sort! { |a, b| a[2] <=> b[2] }
return usable_items[:status_cure].first[0], usable_items[:status_cure].first[1]
end
# Next try using an item that raises all stats (Max Mushrooms)
if usable_items[:all_stats_raise] && pbAIRandom(100) < 30
return usable_items[:stat_raise].first[0], usable_items[:stat_raise].first[1]
end
# Next try using an X item
if usable_items[:stat_raise] && pbAIRandom(100) < 30
usable_items[:stat_raise].sort! { |a, b| (a[2] == b[2]) ? a[3] <=> b[3] : a[2] <=> b[2] }
return usable_items[:stat_raise].last[0], usable_items[:stat_raise].last[1]
end
# Find items usable on other Pokémon in the user's team
# NOTE: Currently only checks Revives.
usable_items = {}
@battle.eachInTeamFromBattlerIndex(@user.index) do |pkmn, i|
next if !pkmn.fainted? # Remove this line to check unfainted Pokémon too
items.each do |item|
usage = get_usability_of_item_on_pkmn(item, i, @user.side)
usage.each_pair do |key, vals|
usable_items[key] ||= []
usable_items[key] += vals
end
end
end
# Try using a Revive (prefer Max Revive-type items over Revive)
if usable_items[:revive] &&
(@battle.pbAbleNonActiveCount(@user.index) == 0 || pbAIRandom(100) < 40)
usable_items[:revive].sort! { |a, b| (a[2] == b[2]) ? a[1] <=> b[1] : a[2] <=> b[2] }
return usable_items[:revive].last[0], usable_items[:revive].last[1]
end
return nil
end
def get_usability_of_item_on_pkmn(item, party_index, side)
pkmn = @battle.pbParty(side)[party_index]
battler = @battle.pbFindBattler(party_index, side)
ret = {}
return ret if !@battle.pbCanUseItemOnPokemon?(item, pkmn, battler, @battle.scene, false)
return ret if !ItemHandlers.triggerCanUseInBattle(item, pkmn, battler, nil,
false, self, @battle.scene, false)
want_to_cure_status = (pkmn.status != :NONE)
if battler
if want_to_cure_status
want_to_cure_status = @battlers[battler.index].wants_status_problem?(pkmn.status)
want_to_cure_status = false if pkmn.status == :SLEEP && pkmn.statusCount <= 2
end
want_to_cure_status ||= (battler.effects[PBEffects::Confusion] > 1)
end
if HP_HEAL_ITEMS.include?(item)
if pkmn.hp < pkmn.totalhp
heal_amount = HP_HEAL_ITEMS[item]
heal_amount = pkmn.totalhp / 4 if item == :SITURUSBERRY
ret[:hp_heal] ||= []
ret[:hp_heal].push([item, party_index, 5, heal_amount])
end
elsif FULL_RESTORE_ITEMS.include?(item)
prefer_full_restore = (pkmn.hp <= pkmn.totalhp * 2 / 3 && want_to_cure_status)
if pkmn.hp < pkmn.totalhp
ret[:hp_heal] ||= []
ret[:hp_heal].push([item, party_index, (prefer_full_restore) ? 3 : 7, 999])
end
if want_to_cure_status
ret[:status_cure] ||= []
ret[:status_cure].push([item, party_index, (prefer_full_restore) ? 3 : 9])
end
elsif ONE_STATUS_CURE_ITEMS.include?(item)
if want_to_cure_status
ret[:status_cure] ||= []
ret[:status_cure].push([item, party_index, 5])
end
elsif ALL_STATUS_CURE_ITEMS.include?(item)
if want_to_cure_status
ret[:status_cure] ||= []
ret[:status_cure].push([item, party_index, 7])
end
elsif ONE_STAT_RAISE_ITEMS.include?(item)
stat_data = ONE_STAT_RAISE_ITEMS[item]
if battler && stat_raise_worthwhile?(@battlers[battler.index], stat_data[0])
ret[:stat_raise] ||= []
ret[:stat_raise].push([item, party_index, battler.stages[stat_data[0]], stat_data[1]])
end
elsif ALL_STATS_RAISE_ITEMS.include?(item)
if battler
ret[:all_stats_raise] ||= []
ret[:all_stats_raise].push([item, party_index])
end
elsif REVIVE_ITEMS.include?(item)
ret[:revive] ||= []
ret[:revive].push([item, party_index, REVIVE_ITEMS[item]])
end
return ret
end
end

View File

@@ -0,0 +1,13 @@
#===============================================================================
#
#===============================================================================
class Battle::AI
# Decide whether the opponent should Mega Evolve.
def pbEnemyShouldMegaEvolve?
if @battle.pbCanMegaEvolve?(@user.index) # Simple "always should if possible"
PBDebug.log_ai("#{@user.name} will Mega Evolve")
return true
end
return false
end
end

View File

@@ -1,295 +0,0 @@
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
# Figure out useful information about the choices
totalScore = 0
maxScore = 0
choices.each do |c|
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
stDev = pbStdDev(choices)
if stDev >= 40 && pbAIRandom(100) < 90
preferredMoves = []
choices.each do |c|
next if c[1] < 200 && c[1] < maxScore * 0.8
preferredMoves.push(c)
preferredMoves.push(c) if c[1] == maxScore # Doubly prefer the best move
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
return
end
end
end
# Decide whether all choices are bad, and if so, try switching instead
if !wildBattler && skill >= PBTrainerAI.highSkill
badMoves = false
if ((maxScore <= 20 && user.turnCount > 2) ||
(maxScore <= 40 && user.turnCount > 5)) && pbAIRandom(100) < 80
badMoves = true
end
if !badMoves && totalScore < 100 && user.turnCount > 1
badMoves = true
choices.each do |c|
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 $INTERNAL
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)
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)
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
break
end
# Log the result
if @battle.choices[idxBattler][2]
PBDebug.log("[AI] #{user.pbThis} (#{user.index}) will use #{@battle.choices[idxBattler][2].name}")
end
end
#=============================================================================
# Get scores for the given move against each possible target
#=============================================================================
# Wild Pokémon choose their moves randomly.
def pbRegisterMoveWild(_user, idxMove, choices)
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)
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)
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)
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)
scoresAndTargets.push([score, b.index]) if score > 0
end
if scoresAndTargets.length > 0
# Get the one best target for the move
scoresAndTargets.sort! { |a, b| b[0] <=> a[0] }
choices.push([idxMove, scoresAndTargets[0][0], scoresAndTargets[0][1]])
end
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)
# A score of 0 here means it absolutely should not be used
return 0 if score <= 0
if skill >= PBTrainerAI.mediumSkill
# 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
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)
miss = true
miss = false if user.hasActiveAbility?(:NOGUARD) || target.hasActiveAbility?(:NOGUARD)
if miss && pbRoughStat(user, :SPEED, skill) > pbRoughStat(target, :SPEED, skill)
# Knows what can get past semi-invulnerability
if target.effects[PBEffects::SkyDrop] >= 0 ||
target.inTwoTurnAttack?("TwoTurnAttackInvulnerableInSky",
"TwoTurnAttackInvulnerableInSkyParalyzeTarget",
"TwoTurnAttackInvulnerableInSkyTargetCannotAct")
miss = false if move.hitsFlyingTargets?
elsif target.inTwoTurnAttack?("TwoTurnAttackInvulnerableUnderground")
miss = false if move.hitsDiggingTargets?
elsif target.inTwoTurnAttack?("TwoTurnAttackInvulnerableUnderwater")
miss = false if move.hitsDivingTargets?
end
end
score -= 80 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
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
end
# If user is frozen, prefer a move that can thaw the user
if user.status == :FROZEN
if move.thawsUser?
score += 40
else
user.eachMove do |m|
next unless m.thawsUser?
score -= 60
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
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
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)
#=============================================================================
def pbGetMoveScoreDamage(score, move, user, target, skill)
return 0 if score <= 0
# 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
# 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
end
realDamage *= 1.3 if canFlinch
end
# Convert damage to percentage of target's remaining HP
damagePercentage = realDamage * 100.0 / target.hp
# Don't prefer weak attacks
# damagePercentage /= 2 if damagePercentage<20
# Prefer damaging attack if level difference is significantly high
damagePercentage *= 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
end
end

View File

@@ -0,0 +1,389 @@
#===============================================================================
#
#===============================================================================
class Battle::AI
MOVE_FAIL_SCORE = 20
MOVE_USELESS_SCORE = 60 # Move predicted to do nothing or just be detrimental
MOVE_BASE_SCORE = 100
# Returns a value between 0.0 and 1.0. All move scores are lowered by this
# value multiplied by the highest-scoring move's score.
def move_score_threshold
return 0.6 + 0.35 * (([@trainer.skill, 100].min / 100.0) ** 0.5) # 0.635 to 0.95
end
#-----------------------------------------------------------------------------
# Get scores for the user's moves.
# NOTE: For any move with a target type that can target a foe (or which
# includes a foe(s) if it has multiple targets), the score calculated
# for a target ally will be inverted. The MoveHandlers for those moves
# should therefore treat an ally as a foe when calculating a score
# against it.
def pbGetMoveScores
choices = []
@user.battler.eachMoveWithIndex do |orig_move, idxMove|
# Unchoosable moves aren't considered
if !@battle.pbCanChooseMove?(@user.index, idxMove, false)
if orig_move.pp == 0 && orig_move.total_pp > 0
PBDebug.log_ai("#{@user.name} cannot use #{orig_move.name} (no PP left)")
else
PBDebug.log_ai("#{@user.name} cannot choose to use #{orig_move.name}")
end
next
end
# Set up move in class variables
set_up_move_check(orig_move)
# Predict whether the move will fail (generally)
if @trainer.has_skill_flag?("PredictMoveFailure") && pbPredictMoveFailure
PBDebug.log_ai("#{@user.name} is considering using #{orig_move.name}...")
PBDebug.log_score_change(MOVE_FAIL_SCORE - MOVE_BASE_SCORE, "move will fail")
add_move_to_choices(choices, idxMove, MOVE_FAIL_SCORE)
next
end
# Get the move's target type
target_data = @move.pbTarget(@user.battler)
if @move.function == "CurseTargetOrLowerUserSpd1RaiseUserAtkDef1" &&
@move.rough_type == :GHOST && @user.has_active_ability?([:LIBERO, :PROTEAN])
target_data = GameData::Target.get((Settings::MECHANICS_GENERATION >= 8) ? :RandomNearFoe : :NearFoe)
end
case target_data.num_targets
when 0 # No targets, affects the user or a side or the whole field
# Includes: BothSides, FoeSide, None, User, UserSide
PBDebug.log_ai("#{@user.name} is considering using #{orig_move.name}...")
score = MOVE_BASE_SCORE
PBDebug.logonerr { score = pbGetMoveScore }
add_move_to_choices(choices, idxMove, score)
when 1 # One target to be chosen by the trainer
# Includes: Foe, NearAlly, NearFoe, NearOther, Other, RandomNearFoe, UserOrNearAlly
redirected_target = get_redirected_target(target_data)
num_targets = 0
@battle.allBattlers.each do |b|
next if redirected_target && b.index != redirected_target
next if !@battle.pbMoveCanTarget?(@user.battler.index, b.index, target_data)
next if target_data.targets_foe && !@user.battler.opposes?(b)
PBDebug.log_ai("#{@user.name} is considering using #{orig_move.name} against #{b.name} (#{b.index})...")
score = MOVE_BASE_SCORE
PBDebug.logonerr { score = pbGetMoveScore([b]) }
add_move_to_choices(choices, idxMove, score, b.index)
num_targets += 1
end
PBDebug.log(" no valid targets") if num_targets == 0
else # Multiple targets at once
# Includes: AllAllies, AllBattlers, AllFoes, AllNearFoes, AllNearOthers, UserAndAllies
targets = []
@battle.allBattlers.each do |b|
next if !@battle.pbMoveCanTarget?(@user.battler.index, b.index, target_data)
targets.push(b)
end
PBDebug.log_ai("#{@user.name} is considering using #{orig_move.name}...")
score = MOVE_BASE_SCORE
PBDebug.logonerr { score = pbGetMoveScore(targets) }
add_move_to_choices(choices, idxMove, score)
end
end
@battle.moldBreaker = false
return choices
end
# If the target of a move can be changed by an external effect, this method
# returns the battler index of the new target.
def get_redirected_target(target_data)
return nil if @move.move.cannotRedirect?
return nil if !target_data.can_target_one_foe? || target_data.num_targets != 1
return nil if @user.has_active_ability?([:PROPELLERTAIL, :STALWART])
priority = @battle.pbPriority(true)
near_only = !target_data.can_choose_distant_target?
# Spotlight, Follow Me/Rage Powder
new_target = -1
strength = 100 # Lower strength takes priority
priority.each do |b|
next if b.fainted? || b.effects[PBEffects::SkyDrop] >= 0
next if !b.opposes?(@user.battler)
next if near_only && !b.near?(@user.battler)
if b.effects[PBEffects::Spotlight] > 0 && b.effects[PBEffects::Spotlight] - 50 < strength
new_target = b.index
strength = b.effects[PBEffects::Spotlight] - 50 # Spotlight takes priority
elsif (b.effects[PBEffects::RagePowder] && @user.battler.affectedByPowder?) ||
(b.effects[PBEffects::FollowMe] > 0 && b.effects[PBEffects::FollowMe] < strength)
new_target = b.index
strength = b.effects[PBEffects::FollowMe]
end
end
return new_target if new_target >= 0
calc_type = @move.rough_type
priority.each do |b|
next if b.index == @user.index
next if near_only && !b.near?(@user.battler)
case calc_type
when :ELECTRIC
new_target = b.index if b.hasActiveAbility?(:LIGHTNINGROD)
when :WATER
new_target = b.index if b.hasActiveAbility?(:STORMDRAIN)
end
break if new_target >= 0
end
return (new_target >= 0) ? new_target : nil
end
def add_move_to_choices(choices, idxMove, score, idxTarget = -1)
choices.push([idxMove, score, idxTarget])
# If the user is a wild Pokémon, doubly prefer one of its moves (the choice
# is random but consistent and does not correlate to any other property of
# the user)
if @user.wild? && @user.pokemon.personalID % @user.battler.moves.length == idxMove
choices.push([idxMove, score, idxTarget])
end
end
#-----------------------------------------------------------------------------
# Set some extra class variables for the move being assessed.
def set_up_move_check(move)
case move.function
when "UseLastMoveUsed"
if @battle.lastMoveUsed &&
GameData::Move.exists?(@battle.lastMoveUsed) &&
!move.moveBlacklist.include?(GameData::Move.get(@battle.lastMoveUsed).function_code)
move = Battle::Move.from_pokemon_move(@battle, Pokemon::Move.new(@battle.lastMoveUsed))
end
when "UseMoveDependingOnEnvironment"
move.pbOnStartUse(@user.battler, []) # Determine which move is used instead
move = Battle::Move.from_pokemon_move(@battle, Pokemon::Move.new(move.npMove))
end
@battle.moldBreaker = @user.has_mold_breaker?
@move.set_up(move)
end
# Set some extra class variables for the target being assessed.
def set_up_move_check_target(target)
@target = (target) ? @battlers[target.index] : nil
@target&.refresh_battler
if @target && @move.function == "UseLastMoveUsedByTarget"
if @target.battler.lastRegularMoveUsed &&
GameData::Move.exists?(@target.battler.lastRegularMoveUsed) &&
GameData::Move.get(@target.battler.lastRegularMoveUsed).has_flag?("CanMirrorMove")
@battle.moldBreaker = @user.has_mold_breaker?
mov = Battle::Move.from_pokemon_move(@battle, Pokemon::Move.new(@target.battler.lastRegularMoveUsed))
@move.set_up(mov)
end
end
end
#-----------------------------------------------------------------------------
# Returns whether the move will definitely fail (assuming no battle conditions
# change between now and using the move).
def pbPredictMoveFailure
# User is asleep and will not wake up
return true if @user.battler.asleep? && @user.statusCount > 1 && !@move.move.usableWhenAsleep?
# User is awake and can't use moves that are only usable when asleep
return true if !@user.battler.asleep? && @move.move.usableWhenAsleep?
# NOTE: Truanting is not considered, because if it is, a Pokémon with Truant
# will want to switch due to terrible moves every other round (because
# all of its moves will fail), and this is disruptive and shouldn't be
# how such Pokémon behave.
# Primal weather
return true if @battle.pbWeather == :HeavyRain && @move.rough_type == :FIRE
return true if @battle.pbWeather == :HarshSun && @move.rough_type == :WATER
# Move effect-specific checks
return true if Battle::AI::Handlers.move_will_fail?(@move.function, @move, @user, self, @battle)
return false
end
# Returns whether the move will definitely fail against the target (assuming
# no battle conditions change between now and using the move).
def pbPredictMoveFailureAgainstTarget
# Move effect-specific checks
return true if Battle::AI::Handlers.move_will_fail_against_target?(@move.function, @move, @user, @target, self, @battle)
# Immunity to priority moves because of Psychic Terrain
return true if @battle.field.terrain == :Psychic && @target.battler.affectedByTerrain? &&
@target.opposes?(@user) && @move.rough_priority(@user) > 0
# Immunity because of ability
return true if @move.move.pbImmunityByAbility(@user.battler, @target.battler, false)
# Immunity because of Dazzling/Queenly Majesty
if @move.rough_priority(@user) > 0 && @target.opposes?(@user)
each_same_side_battler(@target.side) do |b, i|
return true if b.has_active_ability?([:DAZZLING, :QUEENLYMAJESTY])
end
end
# Type immunity
calc_type = @move.rough_type
typeMod = @move.move.pbCalcTypeMod(calc_type, @user.battler, @target.battler)
return true if @move.move.pbDamagingMove? && Effectiveness.ineffective?(typeMod)
# Dark-type immunity to moves made faster by Prankster
return true if Settings::MECHANICS_GENERATION >= 7 && @move.statusMove? &&
@user.has_active_ability?(:PRANKSTER) && @target.has_type?(:DARK) && @target.opposes?(@user)
# Airborne-based immunity to Ground moves
return true if @move.damagingMove? && calc_type == :GROUND &&
@target.battler.airborne? && !@move.move.hitsFlyingTargets?
# Immunity to powder-based moves
return true if @move.move.powderMove? && !@target.battler.affectedByPowder?
# Substitute
return true if @target.effects[PBEffects::Substitute] > 0 && @move.statusMove? &&
!@move.move.ignoresSubstitute?(@user.battler) && @user.index != @target.index
return false
end
#-----------------------------------------------------------------------------
# Get a score for the given move being used against the given target.
# Assumes def set_up_move_check has previously been called.
def pbGetMoveScore(targets = nil)
# Get the base score for the move
score = MOVE_BASE_SCORE
# Scores for each target in turn
if targets
# Reset the base score for the move (each target will add its own score)
score = 0
affected_targets = 0
# Get a score for the move against each target in turn
orig_move = @move.move # In case move is Mirror Move and changes depending on the target
targets.each do |target|
set_up_move_check(orig_move)
set_up_move_check_target(target)
t_score = pbGetMoveScoreAgainstTarget
next if t_score < 0
score += t_score
affected_targets += 1
end
# Set the default score if no targets were affected
if affected_targets == 0
score = (@trainer.has_skill_flag?("PredictMoveFailure")) ? MOVE_USELESS_SCORE : MOVE_BASE_SCORE
end
# Score based on how many targets were affected
if affected_targets == 0 && @trainer.has_skill_flag?("PredictMoveFailure")
if !@move.move.worksWithNoTargets?
PBDebug.log_score_change(MOVE_FAIL_SCORE - MOVE_BASE_SCORE, "move will fail")
return MOVE_FAIL_SCORE
end
else
score /= affected_targets if affected_targets > 1 # Average the score against multiple targets
# Bonus for affecting multiple targets
if @trainer.has_skill_flag?("PreferMultiTargetMoves") && affected_targets > 1
old_score = score
score += (affected_targets - 1) * 10
PBDebug.log_score_change(score - old_score, "affects multiple battlers")
end
end
end
# If we're here, the move either has no targets or at least one target will
# be affected (or the move is usable even if no targets are affected, e.g.
# Self-Destruct)
if @trainer.has_skill_flag?("ScoreMoves")
# Modify the score according to the move's effect
old_score = score
score = Battle::AI::Handlers.apply_move_effect_score(@move.function,
score, @move, @user, self, @battle)
PBDebug.log_score_change(score - old_score, "function code modifier (generic)")
# Modify the score according to various other effects
score = Battle::AI::Handlers.apply_general_move_score_modifiers(
score, @move, @user, self, @battle)
end
score = score.to_i
score = 0 if score < 0
return score
end
#-----------------------------------------------------------------------------
# Returns the score of @move being used against @target. A return value of -1
# means the move will fail or do nothing against the target.
# Assumes def set_up_move_check and def set_up_move_check_target have
# previously been called.
def pbGetMoveScoreAgainstTarget
# Predict whether the move will fail against the target
if @trainer.has_skill_flag?("PredictMoveFailure") && pbPredictMoveFailureAgainstTarget
PBDebug.log(" move will not affect #{@target.name}")
return -1
end
# Score the move
score = MOVE_BASE_SCORE
if @trainer.has_skill_flag?("ScoreMoves")
# Modify the score according to the move's effect against the target
old_score = score
score = Battle::AI::Handlers.apply_move_effect_against_target_score(@move.function,
MOVE_BASE_SCORE, @move, @user, @target, self, @battle)
PBDebug.log_score_change(score - old_score, "function code modifier (against target)")
# Modify the score according to various other effects against the target
score = Battle::AI::Handlers.apply_general_move_against_target_score_modifiers(
score, @move, @user, @target, self, @battle)
end
# Add the score against the target to the overall score
target_data = @move.pbTarget(@user.battler)
if target_data.targets_foe && !@target.opposes?(@user) && @target.index != @user.index
if score == MOVE_USELESS_SCORE
PBDebug.log(" move is useless against #{@target.name}")
return -1
end
old_score = score
score = ((1.85 * MOVE_BASE_SCORE) - score).to_i
PBDebug.log_score_change(score - old_score, "score inverted (move targets ally but can target foe)")
end
return score
end
#-----------------------------------------------------------------------------
# Make the final choice of which move to use depending on the calculated
# scores for each move. Moves with higher scores are more likely to be chosen.
def pbChooseMove(choices)
user_battler = @user.battler
# If no moves can be chosen, auto-choose a move or Struggle
if choices.length == 0
@battle.pbAutoChooseMove(user_battler.index)
PBDebug.log_ai("#{@user.name} will auto-use a move or Struggle")
return
end
# Figure out useful information about the choices
max_score = 0
choices.each { |c| max_score = c[1] if max_score < c[1] }
# Decide whether all choices are bad, and if so, try switching instead
if @trainer.high_skill? && @user.can_switch_lax?
badMoves = false
if max_score <= MOVE_USELESS_SCORE
badMoves = true
elsif max_score < MOVE_BASE_SCORE * move_score_threshold && user_battler.turnCount > 2
badMoves = true if pbAIRandom(100) < 80
end
if badMoves
PBDebug.log_ai("#{@user.name} wants to switch due to terrible moves")
return if pbChooseToSwitchOut(true)
PBDebug.log_ai("#{@user.name} won't switch after all")
end
end
# Calculate a minimum score threshold and reduce all move scores by it
threshold = (max_score * move_score_threshold.to_f).floor
choices.each { |c| c[3] = [c[1] - threshold, 0].max }
total_score = choices.sum { |c| c[3] }
# Log the available choices
if $INTERNAL
PBDebug.log_ai("Move choices for #{@user.name}:")
choices.each_with_index do |c, i|
chance = sprintf("%5.1f", (c[3] > 0) ? 100.0 * c[3] / total_score : 0)
log_msg = " * #{chance}% to use #{user_battler.moves[c[0]].name}"
log_msg += " (target #{c[2]})" if c[2] >= 0
log_msg += ": score #{c[1]}"
PBDebug.log(log_msg)
end
end
# Pick a move randomly from choices weighted by their scores
randNum = pbAIRandom(total_score)
choices.each do |c|
randNum -= c[3]
next if randNum >= 0
@battle.pbRegisterMove(user_battler.index, c[0], false)
@battle.pbRegisterTarget(user_battler.index, c[2]) if c[2] >= 0
break
end
# Log the result
if @battle.choices[user_battler.index][2]
move_name = @battle.choices[user_battler.index][2].name
if @battle.choices[user_battler.index][3] >= 0
PBDebug.log(" => will use #{move_name} (target #{@battle.choices[user_battler.index][3]})")
else
PBDebug.log(" => will use #{move_name}")
end
end
end
end

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,799 @@
#===============================================================================
#
#===============================================================================
class Battle::AI
# Main method for calculating the score for moves that raise a battler's
# stat(s).
# By default, assumes that a stat raise is a good thing. However, this score
# is inverted (by desire_mult) if the target opposes the user. If the move
# could target a foe but is targeting an ally, the score is also inverted, but
# only because it is inverted again in def pbGetMoveScoreAgainstTarget.
def get_score_for_target_stat_raise(score, target, stat_changes, whole_effect = true,
fixed_change = false, ignore_contrary = false)
whole_effect = false if @move.damagingMove?
# Decide whether the target raising its stat(s) is a good thing
desire_mult = 1
if target.opposes?(@user) ||
(@move.pbTarget(@user.battler).targets_foe && target.index != @user.index)
desire_mult = -1
end
# If target has Contrary, use different calculations to score the stat change
if !ignore_contrary && !fixed_change && !@battle.moldBreaker && target.has_active_ability?(:CONTRARY)
if desire_mult > 0 && whole_effect
PBDebug.log_score_change(MOVE_USELESS_SCORE - score, "don't prefer raising target's stats (it has Contrary)")
return MOVE_USELESS_SCORE
end
return get_score_for_target_stat_drop(score, target, stat_changes, whole_effect, fixed_change, true)
end
# Don't make score changes if the move is a damaging move and its additional
# effect (the stat raise(s)) will be negated
add_effect = @move.get_score_change_for_additional_effect(@user, target)
return score if add_effect == -999 # Additional effect will be negated
# Don't make score changes if target will faint from EOR damage
if target.rough_end_of_round_damage >= target.hp
ret = (whole_effect) ? MOVE_USELESS_SCORE : score
PBDebug.log(" ignore stat change (target predicted to faint this round)")
return ret
end
# Don't make score changes if foes have Unaware and target can't make use of
# extra stat stages
if !target.has_move_with_function?("PowerHigherWithUserPositiveStatStages")
foe_is_aware = false
each_foe_battler(target.side) do |b, i|
foe_is_aware = true if !b.has_active_ability?(:UNAWARE)
end
if !foe_is_aware
ret = (whole_effect) ? MOVE_USELESS_SCORE : score
PBDebug.log(" ignore stat change (target's foes have Unaware)")
return ret
end
end
# Figure out which stat raises can happen
real_stat_changes = []
stat_changes.each_with_index do |stat, idx|
next if idx.odd?
if !stat_raise_worthwhile?(target, stat, fixed_change)
if target.index == @user.index
PBDebug.log(" raising the user's #{GameData::Stat.get(stat).name} isn't worthwhile")
else
PBDebug.log(" raising the target's #{GameData::Stat.get(stat).name} isn't worthwhile")
end
next
end
# Calculate amount that stat will be raised by
increment = stat_changes[idx + 1]
increment *= 2 if !fixed_change && !@battle.moldBreaker && target.has_active_ability?(:SIMPLE)
increment = [increment, Battle::Battler::STAT_STAGE_MAXIMUM - target.stages[stat]].min # The actual stages gained
# Count this as a valid stat raise
real_stat_changes.push([stat, increment]) if increment > 0
end
# Discard move if it can't raise any stats
if real_stat_changes.length == 0
return (whole_effect) ? MOVE_USELESS_SCORE : score
end
# Make score change based on the additional effect chance
score += add_effect
# Make score changes based on the general concept of raising stats at all
score = get_target_stat_raise_score_generic(score, target, real_stat_changes, desire_mult)
# Make score changes based on the specific changes to each stat that will be
# raised
real_stat_changes.each do |change|
old_score = score
score = get_target_stat_raise_score_one(score, target, change[0], change[1], desire_mult)
if target.index == @user.index
PBDebug.log_score_change(score - old_score, "raising the user's #{GameData::Stat.get(change[0]).name} by #{change[1]}")
else
PBDebug.log_score_change(score - old_score, "raising the target's #{GameData::Stat.get(change[0]).name} by #{change[1]}")
end
end
return score
end
#-----------------------------------------------------------------------------
# Returns whether the target raising the given stat will have any impact.
def stat_raise_worthwhile?(target, stat, fixed_change = false)
if !fixed_change
return false if !target.battler.pbCanRaiseStatStage?(stat, @user.battler, @move.move)
end
# Check if target won't benefit from the stat being raised
return true if target.has_move_with_function?("SwitchOutUserPassOnEffects",
"PowerHigherWithUserPositiveStatStages")
case stat
when :ATTACK
return false if !target.check_for_move { |m| m.physicalMove?(m.type) &&
m.function != "UseUserDefenseInsteadOfUserAttack" &&
m.function != "UseTargetAttackInsteadOfUserAttack" }
when :DEFENSE
each_foe_battler(target.side) do |b, i|
return true if b.check_for_move { |m| m.physicalMove?(m.type) ||
m.function == "UseTargetDefenseInsteadOfTargetSpDef" }
end
return false
when :SPECIAL_ATTACK
return false if !target.check_for_move { |m| m.specialMove?(m.type) }
when :SPECIAL_DEFENSE
each_foe_battler(target.side) do |b, i|
return true if b.check_for_move { |m| m.specialMove?(m.type) &&
m.function != "UseTargetDefenseInsteadOfTargetSpDef" }
end
return false
when :SPEED
moves_that_prefer_high_speed = [
"PowerHigherWithUserFasterThanTarget",
"PowerHigherWithUserPositiveStatStages"
]
if !target.has_move_with_function?(*moves_that_prefer_high_speed)
meaningful = false
target_speed = target.rough_stat(:SPEED)
each_foe_battler(target.side) do |b, i|
b_speed = b.rough_stat(:SPEED)
meaningful = true if target_speed < b_speed && target_speed * 2.5 > b_speed
break if meaningful
end
return false if !meaningful
end
when :ACCURACY
min_accuracy = 100
target.battler.moves.each do |m|
next if m.accuracy == 0 || m.is_a?(Battle::Move::OHKO)
min_accuracy = m.accuracy if m.accuracy < min_accuracy
end
if min_accuracy >= 90 && target.stages[:ACCURACY] >= 0
meaningful = false
each_foe_battler(target.side) do |b, i|
meaningful = true if b.stages[:EVASION] > 0
break if meaningful
end
return false if !meaningful
end
when :EVASION
end
return true
end
#-----------------------------------------------------------------------------
# Make score changes based on the general concept of raising stats at all.
def get_target_stat_raise_score_generic(score, target, stat_changes, desire_mult = 1)
total_increment = stat_changes.sum { |change| change[1] }
# Prefer if move is a status move and it's the user's first/second turn
if @user.turnCount < 2 && @move.statusMove?
score += total_increment * desire_mult * 5
end
if @trainer.has_skill_flag?("HPAware")
# Prefer if user is at high HP, don't prefer if user is at low HP
if target.index != @user.index
score += total_increment * desire_mult * ((100 * @user.hp / @user.totalhp) - 50) / 8 # +6 to -6 per stage
end
# Prefer if target is at high HP, don't prefer if target is at low HP
score += total_increment * desire_mult * ((100 * target.hp / target.totalhp) - 50) / 8 # +6 to -6 per stage
end
# NOTE: There are no abilities that trigger upon stat raise, but this is
# where they would be accounted for if they existed.
return score
end
# Make score changes based on the raising of a specific stat.
def get_target_stat_raise_score_one(score, target, stat, increment, desire_mult = 1)
# Figure out how much the stat will actually change by
max_stage = Battle::Battler::STAT_STAGE_MAXIMUM
stage_mul = Battle::Battler::STAT_STAGE_MULTIPLIERS
stage_div = Battle::Battler::STAT_STAGE_DIVISORS
if [:ACCURACY, :EVASION].include?(stat)
stage_mul = Battle::Battler::ACC_EVA_STAGE_MULTIPLIERS
stage_div = Battle::Battler::ACC_EVA_STAGE_DIVISORS
end
old_stage = target.stages[stat]
new_stage = old_stage + increment
inc_mult = (stage_mul[new_stage + max_stage].to_f * stage_div[old_stage + max_stage]) / (stage_div[new_stage + max_stage] * stage_mul[old_stage + max_stage])
inc_mult -= 1
inc_mult *= desire_mult
# Stat-based score changes
case stat
when :ATTACK
# Modify score depending on current stat stage
# More strongly prefer if the target has no special moves
if old_stage >= 2 && increment == 1
score -= 10 * ((target.opposes?(@user)) ? 1 : desire_mult)
else
has_special_moves = target.check_for_move { |m| m.specialMove?(m.type) }
inc = (has_special_moves) ? 8 : 12
score += inc * inc_mult
end
when :DEFENSE
# Modify score depending on current stat stage
if old_stage >= 2 && increment == 1
score -= 10 * ((target.opposes?(@user)) ? 1 : desire_mult)
else
score += 10 * inc_mult
end
when :SPECIAL_ATTACK
# Modify score depending on current stat stage
# More strongly prefer if the target has no physical moves
if old_stage >= 2 && increment == 1
score -= 10 * ((target.opposes?(@user)) ? 1 : desire_mult)
else
has_physical_moves = target.check_for_move { |m| m.physicalMove?(m.type) &&
m.function != "UseUserDefenseInsteadOfUserAttack" &&
m.function != "UseTargetAttackInsteadOfUserAttack" }
inc = (has_physical_moves) ? 8 : 12
score += inc * inc_mult
end
when :SPECIAL_DEFENSE
# Modify score depending on current stat stage
if old_stage >= 2 && increment == 1
score -= 10 * ((target.opposes?(@user)) ? 1 : desire_mult)
else
score += 10 * inc_mult
end
when :SPEED
# Prefer if target is slower than a foe
target_speed = target.rough_stat(:SPEED)
each_foe_battler(target.side) do |b, i|
b_speed = b.rough_stat(:SPEED)
next if b_speed > target_speed * 2.5 # Much too slow to reasonably catch up
if b_speed > target_speed
if b_speed < target_speed * (increment + 2) / 2
score += 15 * inc_mult # Target will become faster than b
else
score += 8 * inc_mult
end
break
end
end
# Prefer if the target has Electro Ball or Power Trip/Stored Power
moves_that_prefer_high_speed = [
"PowerHigherWithUserFasterThanTarget",
"PowerHigherWithUserPositiveStatStages"
]
if target.has_move_with_function?(*moves_that_prefer_high_speed)
score += 5 * inc_mult
end
# Don't prefer if any foe has Gyro Ball
each_foe_battler(target.side) do |b, i|
next if !b.has_move_with_function?("PowerHigherWithTargetFasterThanUser")
score -= 5 * inc_mult
end
# Don't prefer if target has Speed Boost (will be gaining Speed anyway)
if target.has_active_ability?(:SPEEDBOOST)
score -= 15 * ((target.opposes?(@user)) ? 1 : desire_mult)
end
when :ACCURACY
# Modify score depending on current stat stage
if old_stage >= 2 && increment == 1
score -= 10 * ((target.opposes?(@user)) ? 1 : desire_mult)
else
min_accuracy = 100
target.battler.moves.each do |m|
next if m.accuracy == 0 || m.is_a?(Battle::Move::OHKO)
min_accuracy = m.accuracy if m.accuracy < min_accuracy
end
min_accuracy = min_accuracy * stage_mul[old_stage] / stage_div[old_stage]
if min_accuracy < 90
score += 10 * inc_mult
end
end
when :EVASION
# Prefer if a foe of the target will take damage at the end of the round
each_foe_battler(target.side) do |b, i|
eor_damage = b.rough_end_of_round_damage
score += 5 * inc_mult if eor_damage > 0
end
# Modify score depending on current stat stage
if old_stage >= 2 && increment == 1
score -= 10 * ((target.opposes?(@user)) ? 1 : desire_mult)
else
score += 10 * inc_mult
end
end
# Prefer if target has Stored Power
if target.has_move_with_function?("PowerHigherWithUserPositiveStatStages")
score += 5 * increment * desire_mult
end
# Don't prefer if any foe has Punishment
each_foe_battler(target.side) do |b, i|
next if !b.has_move_with_function?("PowerHigherWithTargetPositiveStatStages")
score -= 5 * increment * desire_mult
end
return score
end
#-----------------------------------------------------------------------------
# Main method for calculating the score for moves that lower a battler's
# stat(s).
# By default, assumes that a stat drop is a good thing. However, this score
# is inverted (by desire_mult) if the target is the user or an ally. This
# inversion does not happen if the move could target a foe but is targeting an
# ally, but only because it is inverted in def pbGetMoveScoreAgainstTarget
# instead.
def get_score_for_target_stat_drop(score, target, stat_changes, whole_effect = true,
fixed_change = false, ignore_contrary = false)
whole_effect = false if @move.damagingMove?
# Decide whether the target lowering its stat(s) is a good thing
desire_mult = -1
if target.opposes?(@user) ||
(@move.pbTarget(@user.battler).targets_foe && target.index != @user.index)
desire_mult = 1
end
# If target has Contrary, use different calculations to score the stat change
if !ignore_contrary && !fixed_change && !@battle.moldBreaker && target.has_active_ability?(:CONTRARY)
if desire_mult > 0 && whole_effect
PBDebug.log_score_change(MOVE_USELESS_SCORE - score, "don't prefer lowering target's stats (it has Contrary)")
return MOVE_USELESS_SCORE
end
return get_score_for_target_stat_raise(score, target, stat_changes, whole_effect, fixed_change, true)
end
# Don't make score changes if the move is a damaging move and its additional
# effect (the stat drop(s)) will be negated
add_effect = @move.get_score_change_for_additional_effect(@user, target)
return score if add_effect == -999 # Additional effect will be negated
# Don't make score changes if target will faint from EOR damage
if target.rough_end_of_round_damage >= target.hp
ret = (whole_effect) ? MOVE_USELESS_SCORE : score
PBDebug.log(" ignore stat change (target predicted to faint this round)")
return ret
end
# Don't make score changes if foes have Unaware and target can't make use of
# its lowered stat stages
foe_is_aware = false
each_foe_battler(target.side) do |b, i|
foe_is_aware = true if !b.has_active_ability?(:UNAWARE)
end
if !foe_is_aware
ret = (whole_effect) ? MOVE_USELESS_SCORE : score
PBDebug.log(" ignore stat change (target's foes have Unaware)")
return ret
end
# Figure out which stat drops can happen
real_stat_changes = []
stat_changes.each_with_index do |stat, idx|
next if idx.odd?
if !stat_drop_worthwhile?(target, stat, fixed_change)
if target.index == @user.index
PBDebug.log(" lowering the user's #{GameData::Stat.get(stat).name} isn't worthwhile")
else
PBDebug.log(" lowering the target's #{GameData::Stat.get(stat).name} isn't worthwhile")
end
next
end
# Calculate amount that stat will be lowered by
decrement = stat_changes[idx + 1]
decrement *= 2 if !fixed_change && !@battle.moldBreaker && target.has_active_ability?(:SIMPLE)
decrement = [decrement, Battle::Battler::STAT_STAGE_MAXIMUM + target.stages[stat]].min # The actual stages lost
# Count this as a valid stat drop
real_stat_changes.push([stat, decrement]) if decrement > 0
end
# Discard move if it can't lower any stats
if real_stat_changes.length == 0
return (whole_effect) ? MOVE_USELESS_SCORE : score
end
# Make score change based on the additional effect chance
score += add_effect
# Make score changes based on the general concept of lowering stats at all
score = get_target_stat_drop_score_generic(score, target, real_stat_changes, desire_mult)
# Make score changes based on the specific changes to each stat that will be
# lowered
real_stat_changes.each do |change|
old_score = score
score = get_target_stat_drop_score_one(score, target, change[0], change[1], desire_mult)
if target.index == @user.index
PBDebug.log_score_change(score - old_score, "lowering the user's #{GameData::Stat.get(change[0]).name} by #{change[1]}")
else
PBDebug.log_score_change(score - old_score, "lowering the target's #{GameData::Stat.get(change[0]).name} by #{change[1]}")
end
end
return score
end
#-----------------------------------------------------------------------------
# Returns whether the target lowering the given stat will have any impact.
def stat_drop_worthwhile?(target, stat, fixed_change = false)
if !fixed_change
return false if !target.battler.pbCanLowerStatStage?(stat, @user.battler, @move.move)
end
# Check if target won't benefit from the stat being lowered
case stat
when :ATTACK
return false if !target.check_for_move { |m| m.physicalMove?(m.type) &&
m.function != "UseUserDefenseInsteadOfUserAttack" &&
m.function != "UseTargetAttackInsteadOfUserAttack" }
when :DEFENSE
each_foe_battler(target.side) do |b, i|
return true if b.check_for_move { |m| m.physicalMove?(m.type) ||
m.function == "UseTargetDefenseInsteadOfTargetSpDef" }
end
return false
when :SPECIAL_ATTACK
return false if !target.check_for_move { |m| m.specialMove?(m.type) }
when :SPECIAL_DEFENSE
each_foe_battler(target.side) do |b, i|
return true if b.check_for_move { |m| m.specialMove?(m.type) &&
m.function != "UseTargetDefenseInsteadOfTargetSpDef" }
end
return false
when :SPEED
moves_that_prefer_high_speed = [
"PowerHigherWithUserFasterThanTarget",
"PowerHigherWithUserPositiveStatStages"
]
if !target.has_move_with_function?(*moves_that_prefer_high_speed)
meaningful = false
target_speed = target.rough_stat(:SPEED)
each_foe_battler(target.side) do |b, i|
b_speed = b.rough_stat(:SPEED)
meaningful = true if target_speed > b_speed && target_speed < b_speed * 2.5
break if meaningful
end
return false if !meaningful
end
when :ACCURACY
meaningful = false
target.battler.moves.each do |m|
meaningful = true if m.accuracy > 0 && !m.is_a?(Battle::Move::OHKO)
break if meaningful
end
return false if !meaningful
when :EVASION
end
return true
end
#-----------------------------------------------------------------------------
# Make score changes based on the general concept of lowering stats at all.
def get_target_stat_drop_score_generic(score, target, stat_changes, desire_mult = 1)
total_decrement = stat_changes.sum { |change| change[1] }
# Prefer if move is a status move and it's the user's first/second turn
if @user.turnCount < 2 && @move.statusMove?
score += total_decrement * desire_mult * 5
end
if @trainer.has_skill_flag?("HPAware")
# Prefer if user is at high HP, don't prefer if user is at low HP
if target.index != @user.index
score += total_decrement * desire_mult * ((100 * @user.hp / @user.totalhp) - 50) / 8 # +6 to -6 per stage
end
# Prefer if target is at high HP, don't prefer if target is at low HP
score += total_decrement * desire_mult * ((100 * target.hp / target.totalhp) - 50) / 8 # +6 to -6 per stage
end
# Don't prefer if target has an ability that triggers upon stat loss
# (Competitive, Defiant)
if target.opposes?(@user) && Battle::AbilityEffects::OnStatLoss[target.ability]
score -= 10
end
return score
end
# Make score changes based on the lowering of a specific stat.
def get_target_stat_drop_score_one(score, target, stat, decrement, desire_mult = 1)
# Figure out how much the stat will actually change by
max_stage = Battle::Battler::STAT_STAGE_MAXIMUM
stage_mul = Battle::Battler::STAT_STAGE_MULTIPLIERS
stage_div = Battle::Battler::STAT_STAGE_DIVISORS
if [:ACCURACY, :EVASION].include?(stat)
stage_mul = Battle::Battler::ACC_EVA_STAGE_MULTIPLIERS
stage_div = Battle::Battler::ACC_EVA_STAGE_DIVISORS
end
old_stage = target.stages[stat]
new_stage = old_stage - decrement
dec_mult = (stage_mul[old_stage + max_stage].to_f * stage_div[new_stage + max_stage]) / (stage_div[old_stage + max_stage] * stage_mul[new_stage + max_stage])
dec_mult -= 1
dec_mult *= desire_mult
# Stat-based score changes
case stat
when :ATTACK
# Modify score depending on current stat stage
# More strongly prefer if the target has no special moves
if old_stage <= -2 && decrement == 1
score -= 10 * ((target.opposes?(@user)) ? 1 : desire_mult)
else
has_special_moves = target.check_for_move { |m| m.specialMove?(m.type) }
dec = (has_special_moves) ? 8 : 12
score += dec * dec_mult
end
when :DEFENSE
# Modify score depending on current stat stage
if old_stage <= -2 && decrement == 1
score -= 10 * ((target.opposes?(@user)) ? 1 : desire_mult)
else
score += 10 * dec_mult
end
when :SPECIAL_ATTACK
# Modify score depending on current stat stage
# More strongly prefer if the target has no physical moves
if old_stage <= -2 && decrement == 1
score -= 10 * ((target.opposes?(@user)) ? 1 : desire_mult)
else
has_physical_moves = target.check_for_move { |m| m.physicalMove?(m.type) &&
m.function != "UseUserDefenseInsteadOfUserAttack" &&
m.function != "UseTargetAttackInsteadOfUserAttack" }
dec = (has_physical_moves) ? 8 : 12
score += dec * dec_mult
end
when :SPECIAL_DEFENSE
# Modify score depending on current stat stage
if old_stage <= -2 && decrement == 1
score -= 10 * ((target.opposes?(@user)) ? 1 : desire_mult)
else
score += 10 * dec_mult
end
when :SPEED
# Prefer if target is faster than an ally
target_speed = target.rough_stat(:SPEED)
each_foe_battler(target.side) do |b, i|
b_speed = b.rough_stat(:SPEED)
next if target_speed > b_speed * 2.5 # Much too fast to reasonably be overtaken
if target_speed > b_speed
if target_speed < b_speed * 2 / (decrement + 2)
score += 15 * dec_mult # Target will become slower than b
else
score += 8 * dec_mult
end
break
end
end
# Prefer if any ally has Electro Ball
each_foe_battler(target.side) do |b, i|
next if !b.has_move_with_function?("PowerHigherWithUserFasterThanTarget")
score += 5 * dec_mult
end
# Don't prefer if target has Speed Boost (will be gaining Speed anyway)
if target.has_active_ability?(:SPEEDBOOST)
score -= 15 * ((target.opposes?(@user)) ? 1 : desire_mult)
end
when :ACCURACY
# Modify score depending on current stat stage
if old_stage <= -2 && decrement == 1
score -= 10 * ((target.opposes?(@user)) ? 1 : desire_mult)
else
score += 10 * dec_mult
end
when :EVASION
# Modify score depending on current stat stage
if old_stage <= -2 && decrement == 1
score -= 10 * ((target.opposes?(@user)) ? 1 : desire_mult)
else
score += 10 * dec_mult
end
end
# Prefer if target has Stored Power
if target.has_move_with_function?("PowerHigherWithUserPositiveStatStages")
score += 5 * decrement * desire_mult
end
# Don't prefer if any foe has Punishment
each_foe_battler(target.side) do |b, i|
next if !b.has_move_with_function?("PowerHigherWithTargetPositiveStatStages")
score -= 5 * decrement * desire_mult
end
return score
end
#-----------------------------------------------------------------------------
def get_score_for_weather(weather, move_user, starting = false)
return 0 if @battle.pbCheckGlobalAbility(:AIRLOCK) ||
@battle.pbCheckGlobalAbility(:CLOUDNINE)
ret = 0
if starting
weather_extender = {
:Sun => :HEATROCK,
:Rain => :DAMPROCK,
:Sandstorm => :SMOOTHROCK,
:Hail => :ICYROCK
}[weather]
ret += 4 if weather_extender && move_user.has_active_item?(weather_extender)
end
each_battler do |b, i|
# Check each battler for weather-specific effects
case weather
when :Sun
# Check for Fire/Water moves
if b.has_damaging_move_of_type?(:FIRE)
ret += (b.opposes?(move_user)) ? -10 : 10
end
if b.has_damaging_move_of_type?(:WATER)
ret += (b.opposes?(move_user)) ? 10 : -10
end
# Check for moves that freeze
if b.has_move_with_function?("FreezeTarget", "FreezeFlinchTarget") ||
(b.has_move_with_function?("EffectDependsOnEnvironment") &&
[:Snow, :Ice].include?(@battle.environment))
ret += (b.opposes?(move_user)) ? 5 : -5
end
when :Rain
# Check for Fire/Water moves
if b.has_damaging_move_of_type?(:WATER)
ret += (b.opposes?(move_user)) ? -10 : 10
end
if b.has_damaging_move_of_type?(:FIRE)
ret += (b.opposes?(move_user)) ? 10 : -10
end
when :Sandstorm
# Check for battlers affected by sandstorm's effects
if b.battler.takesSandstormDamage? # End of round damage
ret += (b.opposes?(move_user)) ? 10 : -10
end
if b.has_type?(:ROCK) # +SpDef for Rock types
ret += (b.opposes?(move_user)) ? -10 : 10
end
when :Hail
# Check for battlers affected by hail's effects
if b.battler.takesHailDamage? # End of round damage
ret += (b.opposes?(move_user)) ? 10 : -10
end
when :ShadowSky
# Check for battlers affected by Shadow Sky's effects
if b.has_damaging_move_of_type?(:SHADOW)
ret += (b.opposes?(move_user)) ? 10 : -10
end
if b.battler.takesShadowSkyDamage? # End of round damage
ret += (b.opposes?(move_user)) ? 10 : -10
end
end
# Check each battler's abilities/other moves affected by the new weather
if @trainer.medium_skill? && !b.has_active_item?(:UTILITYUMBRELLA)
beneficial_abilities = {
:Sun => [:CHLOROPHYLL, :FLOWERGIFT, :FORECAST, :HARVEST, :LEAFGUARD, :SOLARPOWER],
:Rain => [:DRYSKIN, :FORECAST, :HYDRATION, :RAINDISH, :SWIFTSWIM],
:Sandstorm => [:SANDFORCE, :SANDRUSH, :SANDVEIL],
:Hail => [:FORECAST, :ICEBODY, :SLUSHRUSH, :SNOWCLOAK]
}[weather]
if beneficial_abilities && beneficial_abilities.length > 0 &&
b.has_active_ability?(beneficial_abilities)
ret += (b.opposes?(move_user)) ? -5 : 5
end
if weather == :Hail && b.ability == :ICEFACE
ret += (b.opposes?(move_user)) ? -5 : 5
end
negative_abilities = {
:Sun => [:DRYSKIN]
}[weather]
if negative_abilities && negative_abilities.length > 0 &&
b.has_active_ability?(negative_abilities)
ret += (b.opposes?(move_user)) ? 5 : -5
end
beneficial_moves = {
:Sun => ["HealUserDependingOnWeather",
"RaiseUserAtkSpAtk1Or2InSun",
"TwoTurnAttackOneTurnInSun",
"TypeAndPowerDependOnWeather"],
:Rain => ["ConfuseTargetAlwaysHitsInRainHitsTargetInSky",
"ParalyzeTargetAlwaysHitsInRainHitsTargetInSky",
"TypeAndPowerDependOnWeather"],
:Sandstorm => ["HealUserDependingOnSandstorm",
"TypeAndPowerDependOnWeather"],
:Hail => ["FreezeTargetAlwaysHitsInHail",
"StartWeakenDamageAgainstUserSideIfHail",
"TypeAndPowerDependOnWeather"],
:ShadowSky => ["TypeAndPowerDependOnWeather"]
}[weather]
if beneficial_moves && beneficial_moves.length > 0 &&
b.has_move_with_function?(*beneficial_moves)
ret += (b.opposes?(move_user)) ? -5 : 5
end
negative_moves = {
:Sun => ["ConfuseTargetAlwaysHitsInRainHitsTargetInSky",
"ParalyzeTargetAlwaysHitsInRainHitsTargetInSky"],
:Rain => ["HealUserDependingOnWeather",
"TwoTurnAttackOneTurnInSun"],
:Sandstorm => ["HealUserDependingOnWeather",
"TwoTurnAttackOneTurnInSun"],
:Hail => ["HealUserDependingOnWeather",
"TwoTurnAttackOneTurnInSun"]
}[weather]
if negative_moves && negative_moves.length > 0 &&
b.has_move_with_function?(*negative_moves)
ret += (b.opposes?(move_user)) ? 5 : -5
end
end
end
return ret
end
#-----------------------------------------------------------------------------
def get_score_for_terrain(terrain, move_user, starting = false)
ret = 0
ret += 4 if starting && terrain != :None && move_user.has_active_item?(:TERRAINEXTENDER)
# Inherent effects of terrain
each_battler do |b, i|
next if !b.battler.affectedByTerrain?
case terrain
when :Electric
# Immunity to sleep
if b.status == :NONE
ret += (b.opposes?(move_user)) ? -8 : 8
end
if b.effects[PBEffects::Yawn] > 0
ret += (b.opposes?(move_user)) ? -10 : 10
end
# Check for Electric moves
if b.has_damaging_move_of_type?(:ELECTRIC)
ret += (b.opposes?(move_user)) ? -10 : 10
end
when :Grassy
# End of round healing
ret += (b.opposes?(move_user)) ? -8 : 8
# Check for Grass moves
if b.has_damaging_move_of_type?(:GRASS)
ret += (b.opposes?(move_user)) ? -10 : 10
end
when :Misty
# Immunity to status problems/confusion
if b.status == :NONE || b.effects[PBEffects::Confusion] == 0
ret += (b.opposes?(move_user)) ? -8 : 8
end
# Check for Dragon moves
if b.has_damaging_move_of_type?(:DRAGON)
ret += (b.opposes?(move_user)) ? 10 : -10
end
when :Psychic
# Check for priority moves
if b.check_for_move { |m| m.priority > 0 && m.pbTarget(b.battler)&.can_target_one_foe? }
ret += (b.opposes?(move_user)) ? 10 : -10
end
# Check for Psychic moves
if b.has_damaging_move_of_type?(:PSYCHIC)
ret += (b.opposes?(move_user)) ? -10 : 10
end
end
end
# Held items relating to terrain
seed = {
:Electric => :ELECTRICSEED,
:Grassy => :GRASSYSEED,
:Misty => :MISTYSEED,
:Psychic => :PSYCHICSEED
}[terrain]
each_battler do |b, i|
if seed && b.has_active_item?(seed)
ret += (b.opposes?(move_user)) ? -8 : 8
end
end
# Check for abilities/moves affected by the terrain
if @trainer.medium_skill?
abils = {
:Electric => :SURGESURFER,
:Grassy => :GRASSPELT
}[terrain]
good_moves = {
:Electric => ["DoublePowerInElectricTerrain"],
:Grassy => ["HealTargetDependingOnGrassyTerrain",
"HigherPriorityInGrassyTerrain"],
:Misty => ["UserFaintsPowersUpInMistyTerrainExplosive"],
:Psychic => ["HitsAllFoesAndPowersUpInPsychicTerrain"]
}[terrain]
bad_moves = {
:Grassy => ["DoublePowerIfTargetUnderground",
"LowerTargetSpeed1WeakerInGrassyTerrain",
"RandomPowerDoublePowerIfTargetUnderground"]
}[terrain]
each_battler do |b, i|
next if !b.battler.affectedByTerrain?
# Abilities
if b.has_active_ability?(:MIMICRY)
ret += (b.opposes?(move_user)) ? -5 : 5
end
if abils && b.has_active_ability?(abils)
ret += (b.opposes?(move_user)) ? -8 : 8
end
# Moves
if b.has_move_with_function?("EffectDependsOnEnvironment",
"SetUserTypesBasedOnEnvironment",
"TypeAndPowerDependOnTerrain",
"UseMoveDependingOnEnvironment")
ret += (b.opposes?(move_user)) ? -5 : 5
end
if good_moves && b.has_move_with_function?(*good_moves)
ret += (b.opposes?(move_user)) ? -5 : 5
end
if bad_moves && b.has_move_with_function?(*bad_moves)
ret += (b.opposes?(move_user)) ? 5 : -5
end
end
end
return ret
end
end

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::ExtraType] == target.effects[PBEffects::ExtraType]
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

View File

@@ -0,0 +1,517 @@
#===============================================================================
# Don't prefer hitting a wild shiny Pokémon.
#===============================================================================
Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:shiny_target,
proc { |score, move, user, target, ai, battle|
if target.wild? && target.battler.shiny?
old_score = score
score -= 20
PBDebug.log_score_change(score - old_score, "avoid attacking a shiny wild Pokémon")
end
next score
}
)
#===============================================================================
# Prefer Shadow moves (for flavour).
#===============================================================================
Battle::AI::Handlers::GeneralMoveScore.add(:shadow_moves,
proc { |score, move, user, ai, battle|
if move.rough_type == :SHADOW
old_score = score
score += 10
PBDebug.log_score_change(score - old_score, "prefer using a Shadow move")
end
next score
}
)
#===============================================================================
# If user is frozen, prefer a move that can thaw the user.
#===============================================================================
Battle::AI::Handlers::GeneralMoveScore.add(:thawing_move_when_frozen,
proc { |score, move, user, ai, battle|
if ai.trainer.medium_skill? && user.status == :FROZEN
old_score = score
if move.move.thawsUser?
score += 20
PBDebug.log_score_change(score - old_score, "move will thaw the user")
elsif user.check_for_move { |m| m.thawsUser? }
score -= 20 # Don't prefer this move if user knows another move that thaws
PBDebug.log_score_change(score - old_score, "user knows another move will thaw it")
end
end
next score
}
)
#===============================================================================
# Prefer using a priority move if the user is slower than the target and...
# - the user is at low HP, or
# - the target is predicted to be knocked out by the move.
#===============================================================================
Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:priority_move_against_faster_target,
proc { |score, move, user, target, ai, battle|
if ai.trainer.high_skill? && target.faster_than?(user) && move.rough_priority(user) > 0
# User is at risk of being knocked out
if ai.trainer.has_skill_flag?("HPAware") && user.hp < user.totalhp / 3
old_score = score
score += 8
PBDebug.log_score_change(score - old_score, "user at low HP and move has priority over faster target")
end
# Target is predicted to be knocked out by the move
if move.damagingMove? && move.rough_damage >= target.hp
old_score = score
score += 8
PBDebug.log_score_change(score - old_score, "target at low HP and move has priority over faster target")
end
# Any foe knows Quick Guard and can protect against priority moves
old_score = score
ai.each_foe_battler(user.side) do |b, i|
next if !b.has_move_with_function?("ProtectUserSideFromPriorityMoves")
next if Settings::MECHANICS_GENERATION <= 5 && b.effects[PBEffects::ProtectRate] > 1
score -= 5
end
if score != old_score
PBDebug.log_score_change(score - old_score, "a foe knows Quick Guard and may protect against priority moves")
end
end
next score
}
)
#===============================================================================
# Don't prefer a move that can be Magic Coated if the target (or any foe if the
# move doesn't have a target) knows Magic Coat/has Magic Bounce.
#===============================================================================
Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:target_can_Magic_Coat_or_Bounce_move,
proc { |score, move, user, target, ai, battle|
if move.statusMove? && move.move.canMagicCoat? && target.opposes?(user) &&
(target.faster_than?(user) || !target.battler.semiInvulnerable?)
old_score = score
if !battle.moldBreaker && target.has_active_ability?(:MAGICBOUNCE)
score = Battle::AI::MOVE_USELESS_SCORE
PBDebug.log_score_change(score - old_score, "useless because target will Magic Bounce it")
elsif target.has_move_with_function?("BounceBackProblemCausingStatusMoves") &&
target.can_attack? && !target.battler.semiInvulnerable?
score -= 7
PBDebug.log_score_change(score - old_score, "target knows Magic Coat and could bounce it")
end
end
next score
}
)
Battle::AI::Handlers::GeneralMoveScore.add(:any_foe_can_Magic_Coat_or_Bounce_move,
proc { |score, move, user, ai, battle|
if move.statusMove? && move.move.canMagicCoat? && move.pbTarget(user.battler).num_targets == 0
old_score = score
ai.each_foe_battler(user.side) do |b, i|
next if user.faster_than?(b) && b.battler.semiInvulnerable?
if b.has_active_ability?(:MAGICBOUNCE) && !battle.moldBreaker
score = Battle::AI::MOVE_USELESS_SCORE
PBDebug.log_score_change(score - old_score, "useless because a foe will Magic Bounce it")
break
elsif b.has_move_with_function?("BounceBackProblemCausingStatusMoves") &&
b.can_attack? && !b.battler.semiInvulnerable?
score -= 7
PBDebug.log_score_change(score - old_score, "a foe knows Magic Coat and could bounce it")
break
end
end
end
next score
}
)
#===============================================================================
# Don't prefer a move that can be Snatched if any other battler knows Snatch.
#===============================================================================
Battle::AI::Handlers::GeneralMoveScore.add(:any_battler_can_Snatch_move,
proc { |score, move, user, ai, battle|
if move.statusMove? && move.move.canSnatch?
ai.each_battler do |b, i|
next if b.index == user.index
next if b.effects[PBEffects::SkyDrop] >= 0
next if !b.has_move_with_function?("StealAndUseBeneficialStatusMove")
old_score = score
score -= 7
PBDebug.log_score_change(score - old_score, "another battler could Snatch it")
break
end
end
next score
}
)
#===============================================================================
# Pick a good move for the Choice items.
#===============================================================================
Battle::AI::Handlers::GeneralMoveScore.add(:good_move_for_choice_item,
proc { |score, move, user, ai, battle|
next score if !ai.trainer.medium_skill?
next score if !user.has_active_item?([:CHOICEBAND, :CHOICESPECS, :CHOICESCARF]) &&
!user.has_active_ability?(:GORILLATACTICS)
old_score = score
# Really don't prefer status moves (except Trick)
if move.statusMove? && move.function != "UserTargetSwapItems"
score -= 25
PBDebug.log_score_change(score - old_score, "don't want to be Choiced into a status move")
next score
end
# Don't prefer moves which are 0x against at least one type
move_type = move.rough_type
GameData::Type.each do |type_data|
score -= 8 if type_data.immunities.include?(move_type)
end
# Don't prefer moves with lower accuracy
if move.accuracy > 0
score -= (0.4 * (100 - move.accuracy)).to_i # -0 (100%) to -39 (1%)
end
# Don't prefer moves with low PP
score -= 10 if move.move.pp <= 5
PBDebug.log_score_change(score - old_score, "move is less suitable to be Choiced into")
next score
}
)
#===============================================================================
# Prefer damaging moves if the foe is down to their last Pokémon (opportunistic).
# Prefer damaging moves if the AI is down to its last Pokémon but the foe has
# more (desperate).
#===============================================================================
Battle::AI::Handlers::GeneralMoveScore.add(:damaging_move_and_either_side_no_reserves,
proc { |score, move, user, ai, battle|
if ai.trainer.medium_skill? && move.damagingMove?
reserves = battle.pbAbleNonActiveCount(user.idxOwnSide)
foes = battle.pbAbleNonActiveCount(user.idxOpposingSide)
# Don't mess with scores just because a move is damaging; need to play well
next score if ai.trainer.high_skill? && foes > reserves # AI is outnumbered
# Prefer damaging moves depending on remaining Pokémon
old_score = score
if foes == 0 # Foe is down to their last Pokémon
score += 10 # => Go for the kill
PBDebug.log_score_change(score - old_score, "prefer damaging moves (no foe party Pokémon left)")
elsif reserves == 0 # AI is down to its last Pokémon, foe has reserves
score += 5 # => Go out with a bang
PBDebug.log_score_change(score - old_score, "prefer damaging moves (no ally party Pokémon left)")
end
end
next score
}
)
#===============================================================================
# Don't prefer Fire-type moves if target knows Powder and is faster than the
# user.
#===============================================================================
Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:target_can_powder_fire_moves,
proc { |score, move, user, target, ai, battle|
if ai.trainer.high_skill? && move.rough_type == :FIRE &&
target.has_move_with_function?("TargetNextFireMoveDamagesTarget") &&
target.faster_than?(user)
old_score = score
score -= 5 # Only 5 because we're not sure target will use Powder
PBDebug.log_score_change(score - old_score, "target knows Powder and could negate Fire moves")
end
next score
}
)
#===============================================================================
# Don't prefer moves if target knows a move that can make them Electric-type,
# and if target is unaffected by Electric moves.
#===============================================================================
Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:target_can_make_moves_Electric_and_be_immune,
proc { |score, move, user, target, ai, battle|
next score if !ai.trainer.high_skill?
next score if !target.has_move_with_function?("TargetMovesBecomeElectric") &&
!(move.rough_type == :NORMAL && target.has_move_with_function?("NormalMovesBecomeElectric"))
next score if !ai.pokemon_can_absorb_move?(target, move, :ELECTRIC) &&
!Effectiveness.ineffective?(target.effectiveness_of_type_against_battler(:ELECTRIC, user))
priority = move.rough_priority(user)
if priority > 0 || (priority == 0 && target.faster_than?(user)) # Target goes first
old_score = score
score -= 5 # Only 5 because we're not sure target will use Electrify/Ion Deluge
PBDebug.log_score_change(score - old_score, "target knows Electrify/Ion Deluge and is immune to Electric moves")
end
next score
}
)
#===============================================================================
# Don't prefer attacking the target if they'd be semi-invulnerable.
#===============================================================================
Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:target_semi_invulnerable,
proc { |score, move, user, target, ai, battle|
if ai.trainer.medium_skill? && move.rough_accuracy > 0 &&
(target.battler.semiInvulnerable? || target.effects[PBEffects::SkyDrop] >= 0)
next score if user.has_active_ability?(:NOGUARD) || target.has_active_ability?(:NOGUARD)
priority = move.rough_priority(user)
if priority > 0 || (priority == 0 && user.faster_than?(target)) # User goes first
miss = true
if ai.trainer.high_skill?
# Knows what can get past semi-invulnerability
if target.effects[PBEffects::SkyDrop] >= 0 ||
target.battler.inTwoTurnAttack?("TwoTurnAttackInvulnerableInSky",
"TwoTurnAttackInvulnerableInSkyParalyzeTarget",
"TwoTurnAttackInvulnerableInSkyTargetCannotAct")
miss = false if move.move.hitsFlyingTargets?
elsif target.battler.inTwoTurnAttack?("TwoTurnAttackInvulnerableUnderground")
miss = false if move.move.hitsDiggingTargets?
elsif target.battler.inTwoTurnAttack?("TwoTurnAttackInvulnerableUnderwater")
miss = false if move.move.hitsDivingTargets?
end
end
if miss
old_score = score
score = Battle::AI::MOVE_USELESS_SCORE
PBDebug.log_score_change(score - old_score, "target is semi-invulnerable")
end
end
end
next score
}
)
#===============================================================================
# Account for accuracy of move.
#===============================================================================
Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:predicted_accuracy,
proc { |score, move, user, target, ai, battle|
acc = move.rough_accuracy.to_i
if acc < 90
old_score = score
score -= (0.25 * (100 - acc)).to_i # -2 (89%) to -24 (1%)
PBDebug.log_score_change(score - old_score, "accuracy (predicted #{acc}%)")
end
next score
}
)
#===============================================================================
# Adjust score based on how much damage it can deal.
# Prefer the move even more if it's predicted to do enough damage to KO the
# target.
#===============================================================================
Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:predicted_damage,
proc { |score, move, user, target, ai, battle|
if move.damagingMove?
dmg = move.rough_damage
old_score = score
if target.effects[PBEffects::Substitute] > 0
target_hp = target.effects[PBEffects::Substitute]
score += ([15.0 * dmg / target.effects[PBEffects::Substitute], 20].min).to_i
PBDebug.log_score_change(score - old_score, "damaging move (predicted damage #{dmg} = #{100 * dmg / target.hp}% of target's Substitute)")
else
score += ([25.0 * dmg / target.hp, 30].min).to_i
PBDebug.log_score_change(score - old_score, "damaging move (predicted damage #{dmg} = #{100 * dmg / target.hp}% of target's HP)")
if ai.trainer.has_skill_flag?("HPAware") && dmg > target.hp * 1.1 # Predicted to KO the target
old_score = score
score += 10
PBDebug.log_score_change(score - old_score, "predicted to KO the target")
if move.move.multiHitMove? && target.hp == target.totalhp &&
(target.has_active_ability?(:STURDY) || target.has_active_item?(:FOCUSSASH))
old_score = score
score += 8
PBDebug.log_score_change(score - old_score, "predicted to overcome the target's Sturdy/Focus Sash")
end
end
end
end
next score
}
)
#===============================================================================
# Prefer flinching external effects (note that move effects which cause
# flinching are dealt with in the function code part of score calculation).
#===============================================================================
Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:external_flinching_effects,
proc { |score, move, user, target, ai, battle|
if ai.trainer.medium_skill? && move.damagingMove? && !move.move.flinchingMove? &&
user.faster_than?(target) && target.effects[PBEffects::Substitute] == 0
if user.has_active_item?([:KINGSROCK, :RAZORFANG]) ||
user.has_active_ability?(:STENCH)
if battle.moldBreaker || !target.has_active_ability?([:INNERFOCUS, :SHIELDDUST])
old_score = score
score += 8
score += 5 if move.move.multiHitMove?
PBDebug.log_score_change(score - old_score, "added chance to cause flinching")
end
end
end
next score
}
)
#===============================================================================
# If target is frozen, don't prefer moves that could thaw them.
#===============================================================================
Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:thawing_move_against_frozen_target,
proc { |score, move, user, target, ai, battle|
if ai.trainer.medium_skill? && target.status == :FROZEN
if move.rough_type == :FIRE || (Settings::MECHANICS_GENERATION >= 6 && move.move.thawsUser?)
old_score = score
score -= 20
PBDebug.log_score_change(score - old_score, "thaws the target")
end
end
next score
}
)
#===============================================================================
# Don't prefer a damaging move if it will trigger the target's ability or held
# item when used, e.g. Effect Spore/Rough Skin, Pickpocket, Rocky Helmet, Red
# Card.
# NOTE: These abilities/items may not be triggerable after all (e.g. they
# require the move to make contact but it doesn't), or may have a negative
# effect for the target (e.g. Air Balloon popping), but it's too much
# effort to go into detail deciding all this.
#===============================================================================
Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:trigger_target_ability_or_item_upon_hit,
proc { |score, move, user, target, ai, battle|
if ai.trainer.high_skill? && move.damagingMove? && target.effects[PBEffects::Substitute] == 0
if target.ability_active?
if Battle::AbilityEffects::OnBeingHit[target.ability] ||
(Battle::AbilityEffects::AfterMoveUseFromTarget[target.ability] &&
(!user.has_active_ability?(:SHEERFORCE) || move.move.addlEffect == 0))
old_score = score
score += 8
PBDebug.log_score_change(score - old_score, "can trigger the target's ability")
end
end
if target.battler.isSpecies?(:CRAMORANT) && target.ability == :GULPMISSILE &&
target.battler.form > 0 && !target.effects[PBEffects::Transform]
old_score = score
score += 8
PBDebug.log_score_change(score - old_score, "can trigger the target's ability")
end
if target.item_active?
if Battle::ItemEffects::OnBeingHit[target.item] ||
(Battle::ItemEffects::AfterMoveUseFromTarget[target.item] &&
(!user.has_active_ability?(:SHEERFORCE) || move.move.addlEffect == 0))
old_score = score
score += 8
PBDebug.log_score_change(score - old_score, "can trigger the target's item")
end
end
end
next score
}
)
#===============================================================================
# Prefer a damaging move if it will trigger the user's ability when used, e.g.
# Poison Touch, Magician.
#===============================================================================
Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:trigger_user_ability_upon_hit,
proc { |score, move, user, target, ai, battle|
if ai.trainer.high_skill? && user.ability_active? && move.damagingMove? &&
target.effects[PBEffects::Substitute] == 0
# NOTE: The only ability with an OnDealingHit effect also requires the
# move to make contact. The only abilities with an OnEndOfUsingMove
# effect revolve around damaging moves.
if (Battle::AbilityEffects::OnDealingHit[user.ability] && move.move.contactMove?) ||
Battle::AbilityEffects::OnEndOfUsingMove[user.ability]
old_score = score
score += 8
PBDebug.log_score_change(score - old_score, "can trigger the user's ability")
end
end
next score
}
)
#===============================================================================
# Don't prefer damaging moves that will knock out the target if they are using
# Destiny Bond or Grudge.
#===============================================================================
Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:knocking_out_a_destiny_bonder_or_grudger,
proc { |score, move, user, target, ai, battle|
if (ai.trainer.has_skill_flag?("HPAware") || ai.trainer.high_skill?) && move.damagingMove? &&
(target.effects[PBEffects::DestinyBond] || target.effects[PBEffects::Grudge])
priority = move.rough_priority(user)
if priority > 0 || (priority == 0 && user.faster_than?(target)) # User goes first
if move.rough_damage > target.hp * 1.1 # Predicted to KO the target
old_score = score
if target.effects[PBEffects::DestinyBond]
score -= 20
score -= 10 if battle.pbAbleNonActiveCount(user.idxOwnSide) == 0
PBDebug.log_score_change(score - old_score, "don't want to KO the Destiny Bonding target")
elsif target.effects[PBEffects::Grudge]
score -= 15
score -= 7 if battle.pbAbleNonActiveCount(user.idxOwnSide) == 0
PBDebug.log_score_change(score - old_score, "don't want to KO the Grudge-using target")
end
end
end
end
next score
}
)
#===============================================================================
# Don't prefer damaging moves if the target is using Rage, unless the move will
# deal enough damage to KO the target within two rounds.
#===============================================================================
Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:damaging_a_raging_target,
proc { |score, move, user, target, ai, battle|
if ai.trainer.medium_skill? && target.effects[PBEffects::Rage] && move.damagingMove?
# Worth damaging the target if it can be knocked out within two rounds
if ai.trainer.has_skill_flag?("HPAware")
next score if (move.rough_damage + target.rough_end_of_round_damage) * 2 > target.hp * 1.1
end
old_score = score
score -= 10
PBDebug.log_score_change(score - old_score, "don't want to damage a Raging target")
end
next score
}
)
#===============================================================================
# Don't prefer damaging moves if the target is Biding, unless the move will deal
# enough damage to KO the target before it retaliates (assuming the move is used
# repeatedly until the target retaliates). Doesn't do a score change if the user
# will be immune to Bide's damage.
#===============================================================================
Battle::AI::Handlers::GeneralMoveAgainstTargetScore.add(:damaging_a_biding_target,
proc { |score, move, user, target, ai, battle|
if ai.trainer.medium_skill? && target.effects[PBEffects::Bide] > 0 && move.damagingMove?
eff = user.effectiveness_of_type_against_battler(:NORMAL, target) # Bide is Normal type
if !Effectiveness.ineffective?(eff)
# Worth damaging the target if it can be knocked out before Bide ends
if ai.trainer.has_skill_flag?("HPAware")
dmg = move.rough_damage
eor_dmg = target.rough_end_of_round_damage
hits_possible = target.effects[PBEffects::Bide] - 1
eor_dmg *= hits_possible
hits_possible += 1 if user.faster_than?(target)
next score if dmg * hits_possible + eor_dmg > target.hp * 1.1
end
old_score = score
score -= 20
PBDebug.log_score_change(score - old_score, "don't want to damage the Biding target")
end
end
next score
}
)
#===============================================================================
# Don't prefer a dancing move if the target has the Dancer ability.
#===============================================================================
Battle::AI::Handlers::GeneralMoveScore.add(:dance_move_against_dancer,
proc { |score, move, user, ai, battle|
if move.move.danceMove?
old_score = score
ai.each_foe_battler(user.side) do |b, i|
score -= 10 if b.has_active_ability?(:DANCER)
end
PBDebug.log_score_change(score - old_score, "don't want to use a dance move because a foe has Dancer")
end
next score
}
)

File diff suppressed because it is too large Load Diff

View File

@@ -1,674 +0,0 @@
class Battle::AI
#=============================================================================
#
#=============================================================================
def pbTargetsMultiple?(move, user)
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 }
when :UserAndAllies
@battle.allSameSideBattlers(user).each { |_b| num_targets += 1 }
when :AllNearFoes
@battle.allOtherSideBattlers(user).each { |b| num_targets += 1 if b.near?(user) }
when :AllFoes
@battle.allOtherSideBattlers(user).each { |_b| num_targets += 1 }
when :AllNearOthers
@battle.allBattlers.each { |b| num_targets += 1 if b.near?(user) }
when :AllBattlers
@battle.allBattlers.each { |_b| num_targets += 1 }
end
return num_targets > 1
end
#=============================================================================
# Move's type effectiveness
#=============================================================================
def pbCalcTypeModSingle(moveType, defType, user, target)
ret = Effectiveness.calculate(moveType, defType)
if Effectiveness.ineffective_type?(moveType, defType)
# Ring Target
if target.hasActiveItem?(:RINGTARGET)
ret = Effectiveness::NORMAL_EFFECTIVE_MULTIPLIER
end
# Foresight
if (user.hasActiveAbility?(:SCRAPPY) || target.effects[PBEffects::Foresight]) &&
defType == :GHOST
ret = Effectiveness::NORMAL_EFFECTIVE_MULTIPLIER
end
# Miracle Eye
if target.effects[PBEffects::MiracleEye] && defType == :DARK
ret = Effectiveness::NORMAL_EFFECTIVE_MULTIPLIER
end
elsif Effectiveness.super_effective_type?(moveType, defType)
# Delta Stream's weather
if target.effectiveWeather == :StrongWinds && defType == :FLYING
ret = Effectiveness::NORMAL_EFFECTIVE_MULTIPLIER
end
end
# Grounded Flying-type Pokémon become susceptible to Ground moves
if !target.airborne? && defType == :FLYING && moveType == :GROUND
ret = Effectiveness::NORMAL_EFFECTIVE_MULTIPLIER
end
return ret
end
def pbCalcTypeMod(moveType, user, target)
ret = Effectiveness::NORMAL_EFFECTIVE_MULTIPLIER
return ret if !moveType
return ret if moveType == :GROUND && target.pbHasType?(:FLYING) && target.hasActiveItem?(:IRONBALL)
# Get effectivenesses
if moveType == :SHADOW
if target.shadowPokemon?
ret = Effectiveness::NOT_VERY_EFFECTIVE_MULTIPLIER
else
ret = Effectiveness::SUPER_EFFECTIVE_MULTIPLIER
end
else
target.pbTypes(true).each do |type|
ret *= pbCalcTypeModSingle(moveType, type, user, target)
end
end
return ret
end
# For switching. Determines the effectiveness of a potential switch-in against
# an opposing battler.
def pbCalcTypeModPokemon(battlerThis, battlerOther)
ret = Effectiveness::NORMAL_EFFECTIVE_MULTIPLIER
battlerThis.types.each do |thisType|
ret *= Effectiveness.calculate(thisType, *battlerOther.types)
end
return ret
end
#=============================================================================
# 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)
# Type effectiveness
return true if (move.damagingMove? && Effectiveness.ineffective?(typeMod)) || score <= 0
# Immunity due to ability/item/other effects
if skill >= PBTrainerAI.mediumSkill
case type
when :GROUND
return true if target.airborne? && !move.hitsFlyingTargets?
when :FIRE
return true if target.hasActiveAbility?(:FLASHFIRE)
when :WATER
return true if target.hasActiveAbility?([:DRYSKIN, :STORMDRAIN, :WATERABSORB])
when :GRASS
return true if target.hasActiveAbility?(:SAPSIPPER)
when :ELECTRIC
return true if target.hasActiveAbility?([:LIGHTNINGROD, :MOTORDRIVE, :VOLTABSORB])
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) &&
target.hasActiveAbility?(:TELEPATHY)
return true if move.statusMove? && move.canMagicCoat? && target.hasActiveAbility?(:MAGICBOUNCE) &&
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)
end
return true if move.statusMove? && target.effects[PBEffects::Substitute] > 0 &&
!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)
return true if move.priority > 0 && @battle.field.terrain == :Psychic &&
target.affectedByTerrain? && target.opposes?(user)
end
return false
end
#=============================================================================
# Get approximate properties for a battler
#=============================================================================
def pbRoughType(move, user, skill)
ret = move.type
if skill >= PBTrainerAI.highSkill
ret = move.pbCalcType(user)
end
return ret
end
def pbRoughStat(battler, stat, skill)
return battler.pbSpeed if skill >= PBTrainerAI.highSkill && 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
value = 0
case stat
when :ATTACK then value = battler.attack
when :DEFENSE then value = battler.defense
when :SPECIAL_ATTACK then value = battler.spatk
when :SPECIAL_DEFENSE then value = battler.spdef
when :SPEED then value = battler.speed
end
return (value.to_f * stageMul[stage] / stageDiv[stage]).floor
end
#=============================================================================
# Get a better move's base damage value
#=============================================================================
def pbMoveBaseDamage(move, user, target, skill)
baseDmg = move.baseDamage
baseDmg = 60 if baseDmg == 1
return baseDmg if skill < PBTrainerAI.mediumSkill
# 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)
when "FixedDamageUserLevelRandom" # Psywave
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)
# 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,
# Trump Card, Flail, Electro Ball, Low Kick, Fling, Spit Up
when "DoublePowerIfTargetInSky",
"FlinchTargetDoublePowerIfTargetInSky",
"DoublePowerIfTargetPoisoned",
"DoublePowerIfTargetParalyzedCureTarget",
"DoublePowerIfTargetAsleepCureTarget",
"DoublePowerIfUserPoisonedBurnedParalyzed",
"DoublePowerIfTargetStatusProblem",
"DoublePowerIfTargetHPLessThanHalf",
"DoublePowerIfAllyFaintedLastTurn",
"TypeAndPowerDependOnWeather",
"PowerHigherWithUserHappiness",
"PowerLowerWithUserHappiness",
"PowerHigherWithUserHP",
"PowerHigherWithTargetHP",
"PowerHigherWithUserPositiveStatStages",
"PowerHigherWithTargetPositiveStatStages",
"TypeDependsOnUserIVs",
"PowerHigherWithConsecutiveUse",
"PowerHigherWithConsecutiveUseOnUserSide",
"PowerHigherWithLessPP",
"PowerLowerWithUserHP",
"PowerHigherWithUserFasterThanTarget",
"PowerHigherWithTargetWeight",
"ThrowUserItemAtTarget",
"PowerDependsOnUserStockpile"
baseDmg = move.pbBaseDamage(baseDmg, user, target)
when "DoublePowerIfUserHasNoItem" # Acrobatics
baseDmg *= 2 if !user.item || user.hasActiveItem?(:FLYINGGEM)
when "PowerHigherWithTargetFasterThanUser" # Gyro Ball
targetSpeed = pbRoughStat(target, :SPEED, skill)
userSpeed = pbRoughStat(user, :SPEED, skill)
baseDmg = [[(25 * targetSpeed / userSpeed).floor, 150].min, 1].max
when "RandomlyDamageOrHealTarget" # Present
baseDmg = 50
when "RandomPowerDoublePowerIfTargetUnderground" # Magnitude
baseDmg = 71
baseDmg *= 2 if target.inTwoTurnAttack?("TwoTurnAttackInvulnerableUnderground") # Dig
when "TypeAndPowerDependOnUserBerry" # Natural Gift
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 &&
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)
baseDmg *= 5
else
baseDmg = (baseDmg * 31 / 10).floor # Average damage dealt
end
when "HitTwoToFiveTimesOrThreeForAshGreninja"
if user.isSpecies?(:GRENINJA) && user.form == 2
baseDmg *= 4 # 3 hits at 20 power = 4 hits at 15 power
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|
mult += 1 if pkmn&.able? && pkmn.status == :NONE
end
baseDmg *= mult
when "TwoTurnAttackOneTurnInSun" # Solar Beam
baseDmg = move.pbBaseDamageMultiplier(baseDmg, user, target)
when "MultiTurnAttackPowersUpEachTurn" # Rollout
baseDmg *= 2 if user.effects[PBEffects::DefenseCurl]
when "MultiTurnAttackBideThenReturnDoubleDamage" # Bide
baseDmg = 40
when "UserFaintsFixedDamageUserHP" # Final Gambit
baseDmg = user.hp
when "EffectivenessIncludesFlyingType" # Flying Press
if GameData::Type.exists?(:FLYING)
if skill >= PBTrainerAI.highSkill
targetTypes = target.pbTypes(true)
mult = Effectiveness.calculate(:FLYING, *targetTypes)
else
mult = Effectiveness.calculate(:FLYING, target.types[0], target.types[1], target.effects[PBEffects::ExtraType])
end
baseDmg *= mult
end
baseDmg *= 2 if skill >= PBTrainerAI.mediumSkill && target.effects[PBEffects::Minimize]
when "DoublePowerIfUserLastMoveFailed" # Stomping Tantrum
baseDmg *= 2 if user.lastRoundMoveFailed
when "HitTwoTimesFlinchTarget" # Double Iron Bash
baseDmg *= 2
baseDmg *= 2 if skill >= PBTrainerAI.mediumSkill && target.effects[PBEffects::Minimize]
end
return baseDmg
end
#=============================================================================
# Damage calculation
#=============================================================================
def pbRoughDamage(move, user, target, skill, baseDmg)
# Fixed damage moves
return baseDmg if move.is_a?(Battle::Move::FixedDamageMove)
# Get the move's type
type = pbRoughType(move, user, skill)
##### Calculate user's attack stat #####
atk = pbRoughStat(user, :ATTACK, skill)
if move.function == "UseTargetAttackInsteadOfUserAttack" # Foul Play
atk = pbRoughStat(target, :ATTACK, skill)
elsif move.function == "UseUserBaseDefenseInsteadOfUserBaseAttack" # Body Press
atk = pbRoughStat(user, :DEFENSE, skill)
elsif move.specialMove?(type)
if move.function == "UseTargetAttackInsteadOfUserAttack" # Foul Play
atk = pbRoughStat(target, :SPECIAL_ATTACK, skill)
else
atk = pbRoughStat(user, :SPECIAL_ATTACK, skill)
end
end
##### Calculate target's defense stat #####
defense = pbRoughStat(target, :DEFENSE, skill)
if move.specialMove?(type) && move.function != "UseTargetDefenseInsteadOfTargetSpDef" # Psyshock
defense = pbRoughStat(target, :SPECIAL_DEFENSE, skill)
end
##### Calculate all multiplier effects #####
multipliers = {
:base_damage_multiplier => 1.0,
:attack_multiplier => 1.0,
:defense_multiplier => 1.0,
: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?
# NOTE: These abilities aren't suitable for checking at the start of the
# round.
abilityBlacklist = [:ANALYTIC, :SNIPER, :TINTEDLENS, :AERILATE, :PIXILATE, :REFRIGERATE]
canCheck = true
abilityBlacklist.each do |m|
next if move.id != m
canCheck = false
break
end
if canCheck
Battle::AbilityEffects.triggerDamageCalcFromUser(
user.ability, user, target, move, multipliers, baseDmg, type
)
end
end
if skill >= PBTrainerAI.mediumSkill && !moldBreaker
user.allAllies.each do |b|
next if !b.abilityActive?
Battle::AbilityEffects.triggerDamageCalcFromAlly(
b.ability, user, target, move, multipliers, baseDmg, type
)
end
end
if skill >= PBTrainerAI.bestSkill && !moldBreaker && target.abilityActive?
# NOTE: These abilities aren't suitable for checking at the start of the
# round.
abilityBlacklist = [:FILTER, :SOLIDROCK]
canCheck = true
abilityBlacklist.each do |m|
next if move.id != m
canCheck = false
break
end
if canCheck
Battle::AbilityEffects.triggerDamageCalcFromTarget(
target.ability, user, target, move, multipliers, baseDmg, type
)
end
end
if skill >= PBTrainerAI.bestSkill && !moldBreaker
target.allAllies.each do |b|
next if !b.abilityActive?
Battle::AbilityEffects.triggerDamageCalcFromTargetAlly(
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?
# NOTE: These items aren't suitable for checking at the start of the
# round.
itemBlacklist = [:EXPERTBELT, :LIFEORB]
if !itemBlacklist.include?(user.item_id)
Battle::ItemEffects.triggerDamageCalcFromUser(
user.item, user, target, move, multipliers, baseDmg, type
)
user.effects[PBEffects::GemConsumed] = nil # Untrigger consuming of Gems
end
end
if skill >= PBTrainerAI.bestSkill &&
target.itemActive? && target.item && !target.item.is_berry?
Battle::ItemEffects.triggerDamageCalcFromTarget(
target.item, user, target, move, multipliers, baseDmg, type
)
end
# Global abilities
if skill >= PBTrainerAI.mediumSkill &&
((@battle.pbCheckGlobalAbility(:DARKAURA) && type == :DARK) ||
(@battle.pbCheckGlobalAbility(:FAIRYAURA) && type == :FAIRY))
if @battle.pbCheckGlobalAbility(:AURABREAK)
multipliers[:base_damage_multiplier] *= 2 / 3.0
else
multipliers[:base_damage_multiplier] *= 4 / 3.0
end
end
# Parental Bond
if skill >= PBTrainerAI.mediumSkill && 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
multipliers[:base_damage_multiplier] *= 2
end
# Mud Sport and Water Sport
if skill >= PBTrainerAI.mediumSkill
if type == :ELECTRIC
if @battle.allBattlers.any? { |b| b.effects[PBEffects::MudSport] }
multipliers[:base_damage_multiplier] /= 3
end
if @battle.field.effects[PBEffects::MudSportField] > 0
multipliers[:base_damage_multiplier] /= 3
end
end
if type == :FIRE
if @battle.allBattlers.any? { |b| b.effects[PBEffects::WaterSport] }
multipliers[:base_damage_multiplier] /= 3
end
if @battle.field.effects[PBEffects::WaterSportField] > 0
multipliers[:base_damage_multiplier] /= 3
end
end
end
# Terrain moves
if skill >= PBTrainerAI.mediumSkill
case @battle.field.terrain
when :Electric
multipliers[:base_damage_multiplier] *= 1.5 if type == :ELECTRIC && user.affectedByTerrain?
when :Grassy
multipliers[:base_damage_multiplier] *= 1.5 if type == :GRASS && user.affectedByTerrain?
when :Psychic
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 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)
multipliers[:final_damage_multiplier] *= 0.75
end
# Weather
if skill >= PBTrainerAI.mediumSkill
case user.effectiveWeather
when :Sun, :HarshSun
case type
when :FIRE
multipliers[:final_damage_multiplier] *= 1.5
when :WATER
multipliers[:final_damage_multiplier] /= 2
end
when :Rain, :HeavyRain
case type
when :FIRE
multipliers[:final_damage_multiplier] /= 2
when :WATER
multipliers[:final_damage_multiplier] *= 1.5
end
when :Sandstorm
if target.pbHasType?(:ROCK) && move.specialMove?(type) &&
move.function != "UseTargetDefenseInsteadOfTargetSpDef" # Psyshock
multipliers[:defense_multiplier] *= 1.5
end
end
end
# Critical hits - n/a
# Random variance - n/a
# STAB
if skill >= PBTrainerAI.mediumSkill && 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)
multipliers[:final_damage_multiplier] *= typemod
end
# Burn
if skill >= PBTrainerAI.highSkill && 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 target.pbOwnSide.effects[PBEffects::AuroraVeil] > 0
if @battle.pbSideBattlerCount(target) > 1
multipliers[:final_damage_multiplier] *= 2 / 3.0
else
multipliers[:final_damage_multiplier] /= 2
end
elsif target.pbOwnSide.effects[PBEffects::Reflect] > 0 && move.physicalMove?(type)
if @battle.pbSideBattlerCount(target) > 1
multipliers[:final_damage_multiplier] *= 2 / 3.0
else
multipliers[:final_damage_multiplier] /= 2
end
elsif target.pbOwnSide.effects[PBEffects::LightScreen] > 0 && move.specialMove?(type)
if @battle.pbSideBattlerCount(target) > 1
multipliers[:final_damage_multiplier] *= 2 / 3.0
else
multipliers[:final_damage_multiplier] /= 2
end
end
end
# Minimize
if skill >= PBTrainerAI.highSkill && 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 = [(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
#=============================================================================
# Accuracy calculation
#=============================================================================
def pbRoughAccuracy(move, user, target, skill)
# "Always hit" effects and "always hit" accuracy
if skill >= PBTrainerAI.mediumSkill
return 125 if target.effects[PBEffects::Minimize] && move.tramplesMinimize? &&
Settings::MECHANICS_GENERATION >= 6
return 125 if target.effects[PBEffects::Telekinesis] > 0
end
baseAcc = move.accuracy
if skill >= PBTrainerAI.highSkill
baseAcc = move.pbBaseAccuracy(user, target)
end
return 125 if baseAcc == 0 && skill >= PBTrainerAI.mediumSkill
# Get the move's type
type = pbRoughType(move, user, skill)
# Calculate all modifier effects
modifiers = {}
modifiers[:base_accuracy] = baseAcc
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
# Calculation
accStage = [[modifiers[:accuracy_stage], -6].max, 6].min + 6
evaStage = [[modifiers[:evasion_stage], -6].max, 6].min + 6
stageMul = [3, 3, 3, 3, 3, 3, 3, 4, 5, 6, 7, 8, 9]
stageDiv = [9, 8, 7, 6, 5, 4, 3, 3, 3, 3, 3, 3, 3]
accuracy = 100.0 * stageMul[accStage] / stageDiv[accStage]
evasion = 100.0 * stageMul[evaStage] / stageDiv[evaStage]
accuracy = (accuracy * modifiers[:accuracy_multiplier]).round
evasion = (evasion * modifiers[:evasion_multiplier]).round
evasion = 1 if evasion < 1
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
# 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|
next if !b.abilityActive?
Battle::AbilityEffects.triggerAccuracyCalcFromAlly(
b.ability, modifiers, user, target, move, type
)
end
end
if skill >= PBTrainerAI.bestSkill && target.abilityActive? && !moldBreaker
Battle::AbilityEffects.triggerAccuracyCalcFromTarget(
target.ability, modifiers, user, target, move, type
)
end
# Item effects that alter accuracy calculation
if skill >= PBTrainerAI.mediumSkill && user.itemActive?
Battle::ItemEffects.triggerAccuracyCalcFromUser(
user.item, modifiers, user, target, move, type
)
end
if skill >= PBTrainerAI.bestSkill && target.itemActive?
Battle::ItemEffects.triggerAccuracyCalcFromTarget(
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]
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
end
if skill >= PBTrainerAI.highSkill
if move.function == "BadPoisonTarget" && # Toxic
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[:accuracy_multiplier] = 0
end
end
end
end
end

View File

@@ -0,0 +1,899 @@
#===============================================================================
#
#===============================================================================
class Battle::AI
def pbAIRandom(x); return rand(x); end
#-----------------------------------------------------------------------------
def each_battler
@battlers.each_with_index do |battler, i|
next if !battler || battler.fainted?
yield battler, i
end
end
def each_foe_battler(side)
@battlers.each_with_index do |battler, i|
next if !battler || battler.fainted?
yield battler, i if i.even? != side.even?
end
end
def each_same_side_battler(side)
@battlers.each_with_index do |battler, i|
next if !battler || battler.fainted?
yield battler, i if i.even? == side.even?
end
end
def each_ally(index)
@battlers.each_with_index do |battler, i|
next if !battler || battler.fainted?
yield battler, i if i != index && i.even? == index.even?
end
end
#-----------------------------------------------------------------------------
# Assumes that pkmn's ability is not negated by a global effect (e.g.
# Neutralizing Gas).
# pkmn is either a Battle::AI::AIBattler or a Pokemon.
# move is a Battle::Move or a Pokemon::Move.
def pokemon_can_absorb_move?(pkmn, move, move_type)
return false if pkmn.is_a?(Battle::AI::AIBattler) && !pkmn.ability_active?
# Check pkmn's ability
# Anything with a Battle::AbilityEffects::MoveImmunity handler
case pkmn.ability_id
when :BULLETPROOF
move_data = GameData::Move.get(move.id)
return move_data.has_flag?("Bomb")
when :FLASHFIRE
return move_type == :FIRE
when :LIGHTNINGROD, :MOTORDRIVE, :VOLTABSORB
return move_type == :ELECTRIC
when :SAPSIPPER
return move_type == :GRASS
when :SOUNDPROOF
move_data = GameData::Move.get(move.id)
return move_data.has_flag?("Sound")
when :STORMDRAIN, :WATERABSORB, :DRYSKIN
return move_type == :WATER
when :TELEPATHY
# NOTE: The move is being used by a foe of pkmn.
return false
when :WONDERGUARD
types = pkmn.types
types = pkmn.pbTypes(true) if pkmn.is_a?(Battle::AI::AIBattler)
return !Effectiveness.super_effective_type?(move_type, *types)
end
return false
end
# Used by Toxic Spikes.
def pokemon_can_be_poisoned?(pkmn)
# Check pkmn's immunity to being poisoned
return false if @battle.field.terrain == :Misty
return false if pkmn.hasType?(:POISON)
return false if pkmn.hasType?(:STEEL)
return false if pkmn.hasAbility?(:IMMUNITY)
return false if pkmn.hasAbility?(:PASTELVEIL)
return false if pkmn.hasAbility?(:FLOWERVEIL) && pkmn.hasType?(:GRASS)
return false if pkmn.hasAbility?(:LEAFGUARD) && [:Sun, :HarshSun].include?(@battle.pbWeather)
return false if pkmn.hasAbility?(:COMATOSE) && pkmn.isSpecies?(:KOMALA)
return false if pkmn.hasAbility?(:SHIELDSDOWN) && pkmn.isSpecies?(:MINIOR) && pkmn.form < 7
return true
end
def pokemon_airborne?(pkmn)
return false if pkmn.hasItem?(:IRONBALL)
return false if @battle.field.effects[PBEffects::Gravity] > 0
return true if pkmn.hasType?(:FLYING)
return true if pkmn.hasAbility?(:LEVITATE)
return true if pkmn.hasItem?(:AIRBALLOON)
return false
end
#-----------------------------------------------------------------------------
# These values are taken from the Complete-Fire-Red-Upgrade decomp here:
# https://github.com/Skeli789/Complete-Fire-Red-Upgrade/blob/f7f35becbd111c7e936b126f6328fc52d9af68c8/src/ability_battle_effects.c#L41
BASE_ABILITY_RATINGS = {
10 => [:DELTASTREAM, :DESOLATELAND, :HUGEPOWER, :MOODY, :PARENTALBOND,
:POWERCONSTRUCT, :PRIMORDIALSEA, :PUREPOWER, :SHADOWTAG,
:STANCECHANGE, :WONDERGUARD],
9 => [:ARENATRAP, :DRIZZLE, :DROUGHT, :IMPOSTER, :MAGICBOUNCE, :MAGICGUARD,
:MAGNETPULL, :SANDSTREAM, :SPEEDBOOST],
8 => [:ADAPTABILITY, :AERILATE, :CONTRARY, :DISGUISE, :DRAGONSMAW,
:ELECTRICSURGE, :GALVANIZE, :GRASSYSURGE, :ILLUSION, :LIBERO,
:MISTYSURGE, :MULTISCALE, :MULTITYPE, :NOGUARD, :POISONHEAL,
:PIXILATE, :PRANKSTER, :PROTEAN, :PSYCHICSURGE, :REFRIGERATE,
:REGENERATOR, :RKSSYSTEM, :SERENEGRACE, :SHADOWSHIELD, :SHEERFORCE,
:SIMPLE, :SNOWWARNING, :TECHNICIAN, :TRANSISTOR, :WATERBUBBLE],
7 => [:BEASTBOOST, :BULLETPROOF, :COMPOUNDEYES, :DOWNLOAD, :FURCOAT,
:HUSTLE, :ICESCALES, :INTIMIDATE, :LEVITATE, :LIGHTNINGROD,
:MEGALAUNCHER, :MOLDBREAKER, :MOXIE, :NATURALCURE, :SAPSIPPER,
:SHEDSKIN, :SKILLLINK, :SOULHEART, :STORMDRAIN, :TERAVOLT, :THICKFAT,
:TINTEDLENS, :TOUGHCLAWS, :TRIAGE, :TURBOBLAZE, :UNBURDEN,
:VOLTABSORB, :WATERABSORB],
6 => [:BATTLEBOND, :CHLOROPHYLL, :COMATOSE, :DARKAURA, :DRYSKIN,
:FAIRYAURA, :FILTER, :FLASHFIRE, :FORECAST, :GALEWINGS, :GUTS,
:INFILTRATOR, :IRONBARBS, :IRONFIST, :MIRRORARMOR, :MOTORDRIVE,
:NEUROFORCE, :PRISMARMOR, :QUEENLYMAJESTY, :RECKLESS, :ROUGHSKIN,
:SANDRUSH, :SCHOOLING, :SCRAPPY, :SHIELDSDOWN, :SOLIDROCK, :STAKEOUT,
:STAMINA, :STEELWORKER, :STRONGJAW, :STURDY, :SWIFTSWIM, :TOXICBOOST,
:TRACE, :UNAWARE, :VICTORYSTAR],
5 => [:AFTERMATH, :AIRLOCK, :ANALYTIC, :BERSERK, :BLAZE, :CLOUDNINE,
:COMPETITIVE, :CORROSION, :DANCER, :DAZZLING, :DEFIANT, :FLAREBOOST,
:FLUFFY, :GOOEY, :HARVEST, :HEATPROOF, :INNARDSOUT, :LIQUIDVOICE,
:MARVELSCALE, :MUMMY, :NEUTRALIZINGGAS, :OVERCOAT, :OVERGROW,
:PRESSURE, :QUICKFEET, :ROCKHEAD, :SANDSPIT, :SHIELDDUST, :SLUSHRUSH,
:SWARM, :TANGLINGHAIR, :TORRENT],
4 => [:ANGERPOINT, :BADDREAMS, :CHEEKPOUCH, :CLEARBODY, :CURSEDBODY,
:EARLYBIRD, :EFFECTSPORE, :FLAMEBODY, :FLOWERGIFT, :FULLMETALBODY,
:GORILLATACTICS, :HYDRATION, :ICEFACE, :IMMUNITY, :INSOMNIA,
:JUSTIFIED, :MERCILESS, :PASTELVEIL, :POISONPOINT, :POISONTOUCH,
:RIPEN, :SANDFORCE, :SOUNDPROOF, :STATIC, :SURGESURFER, :SWEETVEIL,
:SYNCHRONIZE, :VITALSPIRIT, :WATERCOMPACTION, :WATERVEIL,
:WHITESMOKE, :WONDERSKIN],
3 => [:AROMAVEIL, :AURABREAK, :COTTONDOWN, :DAUNTLESSSHIELD,
:EMERGENCYEXIT, :GLUTTONY, :GULPMISSLE, :HYPERCUTTER, :ICEBODY,
:INTREPIDSWORD, :LIMBER, :LIQUIDOOZE, :LONGREACH, :MAGICIAN,
:OWNTEMPO, :PICKPOCKET, :RAINDISH, :RATTLED, :SANDVEIL,
:SCREENCLEANER, :SNIPER, :SNOWCLOAK, :SOLARPOWER, :STEAMENGINE,
:STICKYHOLD, :SUPERLUCK, :UNNERVE, :WIMPOUT],
2 => [:BATTLEARMOR, :COLORCHANGE, :CUTECHARM, :DAMP, :GRASSPELT,
:HUNGERSWITCH, :INNERFOCUS, :LEAFGUARD, :LIGHTMETAL, :MIMICRY,
:OBLIVIOUS, :POWERSPOT, :PROPELLORTAIL, :PUNKROCK, :SHELLARMOR,
:STALWART, :STEADFAST, :STEELYSPIRIT, :SUCTIONCUPS, :TANGLEDFEET,
:WANDERINGSPIRIT, :WEAKARMOR],
1 => [:BIGPECKS, :KEENEYE, :MAGMAARMOR, :PICKUP, :RIVALRY, :STENCH],
0 => [:ANTICIPATION, :ASONECHILLINGNEIGH, :ASONEGRIMNEIGH, :BALLFETCH,
:BATTERY, :CHILLINGNEIGH, :CURIOUSMEDICINE, :FLOWERVEIL, :FOREWARN,
:FRIENDGUARD, :FRISK, :GRIMNEIGH, :HEALER, :HONEYGATHER, :ILLUMINATE,
:MINUS, :PLUS, :POWEROFALCHEMY, :QUICKDRAW, :RECEIVER, :RUNAWAY,
:SYMBIOSIS, :TELEPATHY, :UNSEENFIST],
-1 => [:DEFEATIST, :HEAVYMETAL, :KLUTZ, :NORMALIZE, :PERISHBODY, :STALL,
:ZENMODE],
-2 => [:SLOWSTART, :TRUANT]
}
#-----------------------------------------------------------------------------
BASE_ITEM_RATINGS = {
10 => [:EVIOLITE, :FOCUSSASH, :LIFEORB, :THICKCLUB],
9 => [:ASSAULTVEST, :BLACKSLUDGE, :CHOICEBAND, :CHOICESCARF, :CHOICESPECS,
:DEEPSEATOOTH, :LEFTOVERS],
8 => [:LEEK, :STICK, :THROATSPRAY, :WEAKNESSPOLICY],
7 => [:EXPERTBELT, :LIGHTBALL, :LUMBERRY, :POWERHERB, :ROCKYHELMET,
:SITRUSBERRY],
6 => [:KINGSROCK, :LIECHIBERRY, :LIGHTCLAY, :PETAYABERRY, :RAZORFANG,
:REDCARD, :SALACBERRY, :SHELLBELL, :WHITEHERB,
# Type-resisting berries
:BABIRIBERRY, :CHARTIBERRY, :CHILANBERRY, :CHOPLEBERRY, :COBABERRY,
:COLBURBERRY, :HABANBERRY, :KASIBBERRY, :KEBIABERRY, :OCCABERRY,
:PASSHOBERRY, :PAYAPABERRY, :RINDOBERRY, :ROSELIBERRY, :SHUCABERRY,
:TANGABERRY, :WACANBERRY, :YACHEBERRY,
# Gems
:BUGGEM, :DARKGEM, :DRAGONGEM, :ELECTRICGEM, :FAIRYGEM, :FIGHTINGGEM,
:FIREGEM, :FLYINGGEM, :GHOSTGEM, :GRASSGEM, :GROUNDGEM, :ICEGEM,
:NORMALGEM, :POISONGEM, :PSYCHICGEM, :ROCKGEM, :STEELGEM, :WATERGEM,
# Legendary Orbs
:ADAMANTORB, :GRISEOUSORB, :LUSTROUSORB, :SOULDEW,
# Berries that heal HP and may confuse
:AGUAVBERRY, :FIGYBERRY, :IAPAPABERRY, :MAGOBERRY, :WIKIBERRY],
5 => [:CUSTAPBERRY, :DEEPSEASCALE, :EJECTBUTTON, :FOCUSBAND, :JABOCABERRY,
:KEEBERRY, :LANSATBERRY, :MARANGABERRY, :MENTALHERB, :METRONOME,
:MUSCLEBAND, :QUICKCLAW, :RAZORCLAW, :ROWAPBERRY, :SCOPELENS,
:WISEGLASSES,
# Type power boosters
:BLACKBELT, :BLACKGLASSES, :CHARCOAL, :DRAGONFANG, :HARDSTONE,
:MAGNET, :METALCOAT, :MIRACLESEED, :MYSTICWATER, :NEVERMELTICE,
:POISONBARB, :SHARPBEAK, :SILKSCARF,:SILVERPOWDER, :SOFTSAND,
:SPELLTAG, :TWISTEDSPOON,
:ODDINCENSE, :ROCKINCENSE, :ROSEINCENSE, :SEAINCENSE, :WAVEINCENSE,
# Plates
:DRACOPLATE, :DREADPLATE, :EARTHPLATE, :FISTPLATE, :FLAMEPLATE,
:ICICLEPLATE, :INSECTPLATE, :IRONPLATE, :MEADOWPLATE, :MINDPLATE,
:PIXIEPLATE, :SKYPLATE, :SPLASHPLATE, :SPOOKYPLATE, :STONEPLATE,
:TOXICPLATE, :ZAPPLATE,
# Weather/terrain extenders
:DAMPROCK, :HEATROCK, :ICYROCK, :SMOOTHROCK, :TERRAINEXTENDER],
4 => [:ADRENALINEORB, :APICOTBERRY, :BLUNDERPOLICY, :CHESTOBERRY,
:EJECTPACK, :ENIGMABERRY, :GANLONBERRY, :HEAVYDUTYBOOTS,
:ROOMSERVICE, :SAFETYGOGGLES, :SHEDSHELL, :STARFBERRY],
3 => [:BIGROOT, :BRIGHTPOWDER, :LAXINCENSE, :LEPPABERRY, :PERSIMBERRY,
:PROTECTIVEPADS, :UTILITYUMBRELLA,
# Status problem-curing berries (except Chesto which is in 4)
:ASPEARBERRY, :CHERIBERRY, :PECHABERRY, :RAWSTBERRY],
2 => [:ABSORBBULB, :BERRYJUICE, :CELLBATTERY, :GRIPCLAW, :LUMINOUSMOSS,
:MICLEBERRY, :ORANBERRY, :SNOWBALL, :WIDELENS, :ZOOMLENS,
# Terrain seeds
:ELECTRICSEED, :GRASSYSEED, :MISTYSEED, :PSYCHICSEED],
1 => [:AIRBALLOON, :BINDINGBAND, :DESTINYKNOT, :FLOATSTONE, :LUCKYPUNCH,
:METALPOWDER, :QUICKPOWDER,
# Drives
:BURNDRIVE, :CHILLDRIVE, :DOUSEDRIVE, :SHOCKDRIVE,
# Memories
:BUGMEMORY, :DARKMEMORY, :DRAGONMEMORY, :ELECTRICMEMORY,
:FAIRYMEMORY, :FIGHTINGMEMORY, :FIREMEMORY, :FLYINGMEMORY,
:GHOSTMEMORY, :GRASSMEMORY, :GROUNDMEMORY, :ICEMEMORY, :POISONMEMORY,
:PSYCHICMEMORY, :ROCKMEMORY, :STEELMEMORY, :WATERMEMORY
],
0 => [:SMOKEBALL],
-5 => [:FULLINCENSE, :LAGGINGTAIL, :RINGTARGET],
-6 => [:MACHOBRACE, :POWERANKLET, :POWERBAND, :POWERBELT, :POWERBRACER,
:POWERLENS, :POWERWEIGHT],
-7 => [:FLAMEORB, :IRONBALL, :TOXICORB],
-9 => [:STICKYBARB]
}
end
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::AbilityRanking.add(:BLAZE,
proc { |ability, score, battler, ai|
next score if battler.has_damaging_move_of_type?(:FIRE)
next 0
}
)
Battle::AI::Handlers::AbilityRanking.add(:CUTECHARM,
proc { |ability, score, battler, ai|
next 0 if battler.gender == 2
next score
}
)
Battle::AI::Handlers::AbilityRanking.copy(:CUTECHARM, :RIVALRY)
Battle::AI::Handlers::AbilityRanking.add(:FRIENDGUARD,
proc { |ability, score, battler, ai|
has_ally = false
ai.each_ally(battler.side) { |b, i| has_ally = true }
next score if has_ally
next 0
}
)
Battle::AI::Handlers::AbilityRanking.copy(:FRIENDGUARD, :HEALER, :SYMBOISIS, :TELEPATHY)
Battle::AI::Handlers::AbilityRanking.add(:GALEWINGS,
proc { |ability, score, battler, ai|
next score if battler.check_for_move { |m| m.type == :FLYING }
next 0
}
)
Battle::AI::Handlers::AbilityRanking.add(:HUGEPOWER,
proc { |ability, score, battler, ai|
next score if ai.stat_raise_worthwhile?(battler, :ATTACK, true)
next 0
}
)
Battle::AI::Handlers::AbilityRanking.copy(:HUGEPOWER, :PUREPOWER)
Battle::AI::Handlers::AbilityRanking.add(:IRONFIST,
proc { |ability, score, battler, ai|
next score if battler.check_for_move { |m| m.punchingMove? }
next 0
}
)
Battle::AI::Handlers::AbilityRanking.add(:LIQUIDVOICE,
proc { |ability, score, battler, ai|
next score if battler.check_for_move { |m| m.soundMove? }
next 0
}
)
Battle::AI::Handlers::AbilityRanking.add(:MEGALAUNCHER,
proc { |ability, score, battler, ai|
next score if battler.check_for_move { |m| m.pulseMove? }
next 0
}
)
Battle::AI::Handlers::AbilityRanking.add(:OVERGROW,
proc { |ability, score, battler, ai|
next score if battler.has_damaging_move_of_type?(:GRASS)
next 0
}
)
Battle::AI::Handlers::AbilityRanking.add(:PRANKSTER,
proc { |ability, score, battler, ai|
next score if battler.check_for_move { |m| m.statusMove? }
next 0
}
)
Battle::AI::Handlers::AbilityRanking.add(:PUNKROCK,
proc { |ability, score, battler, ai|
next score if battler.check_for_move { |m| m.damagingMove? && m.soundMove? }
next 1
}
)
Battle::AI::Handlers::AbilityRanking.add(:RECKLESS,
proc { |ability, score, battler, ai|
next score if battler.check_for_move { |m| m.recoilMove? }
next 0
}
)
Battle::AI::Handlers::AbilityRanking.add(:ROCKHEAD,
proc { |ability, score, battler, ai|
next score if battler.check_for_move { |m| m.recoilMove? && !m.is_a?(Battle::Move::CrashDamageIfFailsUnusableInGravity) }
next 0
}
)
Battle::AI::Handlers::AbilityRanking.add(:RUNAWAY,
proc { |ability, score, battler, ai|
next 0 if battler.wild?
next score
}
)
Battle::AI::Handlers::AbilityRanking.add(:SANDFORCE,
proc { |ability, score, battler, ai|
next score if battler.has_damaging_move_of_type?(:GROUND, :ROCK, :STEEL)
next 2
}
)
Battle::AI::Handlers::AbilityRanking.add(:SKILLLINK,
proc { |ability, score, battler, ai|
next score if battler.check_for_move { |m| m.is_a?(Battle::Move::HitTwoToFiveTimes) }
next 0
}
)
Battle::AI::Handlers::AbilityRanking.add(:STEELWORKER,
proc { |ability, score, battler, ai|
next score if battler.has_damaging_move_of_type?(:STEEL)
next 0
}
)
Battle::AI::Handlers::AbilityRanking.add(:SWARM,
proc { |ability, score, battler, ai|
next score if battler.has_damaging_move_of_type?(:BUG)
next 0
}
)
Battle::AI::Handlers::AbilityRanking.add(:TORRENT,
proc { |ability, score, battler, ai|
next score if battler.has_damaging_move_of_type?(:WATER)
next 0
}
)
Battle::AI::Handlers::AbilityRanking.add(:TRIAGE,
proc { |ability, score, battler, ai|
next score if battler.check_for_move { |m| m.healingMove? }
next 0
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::ItemRanking.add(:ADAMANTORB,
proc { |item, score, battler, ai|
next score if battler.battler.isSpecies?(:DIALGA) &&
battler.has_damaging_move_of_type?(:DRAGON, :STEEL)
next 0
}
)
Battle::AI::Handlers::ItemRanking.add(:AGUAVBERRY,
proc { |item, score, battler, ai|
if Settings::MECHANICS_GENERATION == 7 # Heals 50%
score += 2
elsif Settings::MECHANICS_GENERATION <= 6 # Heals 12.5%
score -= 3
end
if ai.trainer.high_skill?
if battler.battler.nature.stat_changes.any? { |val| val[0] == :SPECIAL_DEFENSE && val[1] < 0 }
score -= 2 # Will confuse
end
end
next score
}
)
Battle::AI::Handlers::ItemRanking.add(:ASSAULTVEST,
proc { |item, score, battler, ai|
if ai.trainer.high_skill?
score += 1 if !battler.check_for_move { |m| m.statusMove? && !m.is_a?(Battle::Move::UseMoveTargetIsAboutToUse) }
end
next score
}
)
Battle::AI::Handlers::ItemRanking.add(:BERRYJUICE,
proc { |item, score, battler, ai|
next [10 - (battler.totalhp / 15), 1].max
}
)
Battle::AI::Handlers::ItemRanking.add(:BIGROOT,
proc { |item, score, battler, ai|
next score if battler.check_for_move do |m|
m.is_a?(Battle::Move::HealUserByHalfOfDamageDone) ||
m.is_a?(Battle::Move::HealUserByHalfOfDamageDoneIfTargetAsleep) ||
m.is_a?(Battle::Move::HealUserByThreeQuartersOfDamageDone) ||
m.is_a?(Battle::Move::HealUserByTargetAttackLowerTargetAttack1) ||
m.is_a?(Battle::Move::StartLeechSeedTarget)
end
next 0
}
)
Battle::AI::Handlers::ItemRanking.add(:BINDINGBAND,
proc { |item, score, battler, ai|
next score if battler.check_for_move { |m| m.is_a?(Battle::Move::BindTarget) }
next 0
}
)
Battle::AI::Handlers::ItemRanking.copy(:BINDINGBAND, :GRIPCLAW)
Battle::AI::Handlers::ItemRanking.add(:BLACKSLUDGE,
proc { |item, score, battler, ai|
next score if battler.has_type?(:POISON)
next -9
}
)
Battle::AI::Handlers::ItemRanking.add(:CHESTOBERRY,
proc { |item, score, battler, ai|
if ai.trainer.high_skill?
score += 1 if battler.has_move_with_function("HealUserFullyAndFallAsleep")
end
next score
}
)
Battle::AI::Handlers::ItemRanking.add(:CHOICEBAND,
proc { |item, score, battler, ai|
next score if battler.check_for_move { |m| m.physicalMove?(m.type) }
next 0
}
)
Battle::AI::Handlers::ItemRanking.copy(:CHOICEBAND, :MUSCLEBAND)
Battle::AI::Handlers::ItemRanking.add(:CHOICESPECS,
proc { |item, score, battler, ai|
next score if battler.check_for_move { |m| m.specialMove?(m.type) }
next 0
}
)
Battle::AI::Handlers::ItemRanking.copy(:CHOICESPECS, :WISEGLASSES)
Battle::AI::Handlers::ItemRanking.add(:DEEPSEASCALE,
proc { |item, score, battler, ai|
next score if battler.battler.isSpecies?(:CLAMPERL)
next 0
}
)
Battle::AI::Handlers::ItemRanking.add(:DAMPROCK,
proc { |item, score, battler, ai|
next score if battler.check_for_move { |m| m.is_a?(Battle::Move::StartRainWeather) }
next 0
}
)
Battle::AI::Handlers::ItemRanking.add(:DEEPSEATOOTH,
proc { |item, score, battler, ai|
next score if battler.battler.isSpecies?(:CLAMPERL) &&
battler.check_for_move { |m| m.specialMove?(m.type) }
next 0
}
)
Battle::AI::Handlers::ItemRanking.add(:ELECTRICSEED,
proc { |item, score, battler, ai|
next score if battler.check_for_move { |m| m.is_a?(Battle::Move::StartElectricTerrain) }
next 0
}
)
Battle::AI::Handlers::ItemRanking.add(:EVIOLITE,
proc { |item, score, battler, ai|
next score if battler.battler.pokemon.species_data.get_evolutions(true).length > 0
next 0
}
)
Battle::AI::Handlers::ItemRanking.add(:FIGYBERRY,
proc { |item, score, battler, ai|
if Settings::MECHANICS_GENERATION == 7 # Heals 50%
score += 2
elsif Settings::MECHANICS_GENERATION <= 6 # Heals 12.5%
score -= 3
end
if ai.trainer.high_skill?
if battler.battler.nature.stat_changes.any? { |val| val[0] == :ATTACK && val[1] < 0 }
score -= 2 # Will confuse
end
end
next score
}
)
Battle::AI::Handlers::ItemRanking.add(:FLAMEORB,
proc { |item, score, battler, ai|
next 0 if battler.status != :NONE
next 7 if battler.wants_status_problem?(:BURN)
next score
}
)
Battle::AI::Handlers::ItemRanking.add(:FULLINCENSE,
proc { |item, score, battler, ai|
if ai.trainer.high_skill?
score = 7 if battler.has_active_ability?(:ANALYTIC)
end
next score
}
)
Battle::AI::Handlers::ItemRanking.copy(:FULLINCENSE, :LAGGINGTAIL)
Battle::AI::Handlers::ItemRanking.add(:GRASSYSEED,
proc { |item, score, battler, ai|
next score if battler.check_for_move { |m| m.is_a?(Battle::Move::StartGrassyTerrain) }
next 0
}
)
Battle::AI::Handlers::ItemRanking.add(:GRISEOUSORB,
proc { |item, score, battler, ai|
next score if battler.battler.isSpecies?(:GIRATINA) &&
battler.has_damaging_move_of_type?(:DRAGON, :GHOST)
next 0
}
)
Battle::AI::Handlers::ItemRanking.add(:HEATROCK,
proc { |item, score, battler, ai|
next score if battler.check_for_move { |m| m.is_a?(Battle::Move::StartSunWeather) }
next 0
}
)
Battle::AI::Handlers::ItemRanking.add(:IAPAPABERRY,
proc { |item, score, battler, ai|
if Settings::MECHANICS_GENERATION == 7 # Heals 50%
score += 2
elsif Settings::MECHANICS_GENERATION <= 6 # Heals 12.5%
score -= 3
end
if ai.trainer.high_skill?
if battler.battler.nature.stat_changes.any? { |val| val[0] == :DEFENSE && val[1] < 0 }
score -= 2 # Will confuse
end
end
next score
}
)
Battle::AI::Handlers::ItemRanking.add(:ICYROCK,
proc { |item, score, battler, ai|
next score if battler.check_for_move { |m| m.is_a?(Battle::Move::StartHailWeather) }
next 0
}
)
Battle::AI::Handlers::ItemRanking.add(:IRONBALL,
proc { |item, score, battler, ai|
next 0 if battler.has_move_with_function?("ThrowUserItemAtTarget")
next score
}
)
Battle::AI::Handlers::ItemRanking.add(:KINGSROCK,
proc { |item, score, battler, ai|
if ai.trainer.high_skill?
score += 1 if battler.check_for_move { |m| m.multiHitMove? }
end
next score
}
)
Battle::AI::Handlers::ItemRanking.copy(:KINGSROCK, :RAZORFANG)
Battle::AI::Handlers::ItemRanking.add(:LEEK,
proc { |item, score, battler, ai|
next score if (battler.battler.isSpecies?(:FARFETCHD) || battler.battler.isSpecies?(:SIRFETCHD)) &&
battler.check_for_move { |m| m.damagingMove? }
next 0
}
)
Battle::AI::Handlers::ItemRanking.copy(:LEEK, :STICK)
Battle::AI::Handlers::ItemRanking.add(:LIGHTBALL,
proc { |item, score, battler, ai|
next score if battler.battler.isSpecies?(:PIKACHU) &&
battler.check_for_move { |m| m.damagingMove? }
next 0
}
)
Battle::AI::Handlers::ItemRanking.add(:LIGHTCLAY,
proc { |item, score, battler, ai|
next score if battler.check_for_move do |m|
m.is_a?(Battle::Move::StartWeakenPhysicalDamageAgainstUserSide) ||
m.is_a?(Battle::Move::StartWeakenSpecialDamageAgainstUserSide) ||
m.is_a?(Battle::Move::StartWeakenDamageAgainstUserSideIfHail)
end
next 0
}
)
Battle::AI::Handlers::ItemRanking.add(:LUCKYPUNCH,
proc { |item, score, battler, ai|
next score if battler.battler.isSpecies?(:CHANSEY)
next 0
}
)
Battle::AI::Handlers::ItemRanking.add(:LUSTROUSORB,
proc { |item, score, battler, ai|
next score if battler.battler.isSpecies?(:PALKIA) &&
battler.has_damaging_move_of_type?(:DRAGON, :WATER)
next 0
}
)
Battle::AI::Handlers::ItemRanking.add(:MAGOBERRY,
proc { |item, score, battler, ai|
if Settings::MECHANICS_GENERATION == 7 # Heals 50%
score += 2
elsif Settings::MECHANICS_GENERATION <= 6 # Heals 12.5%
score -= 3
end
if ai.trainer.high_skill?
if battler.battler.nature.stat_changes.any? { |val| val[0] == :SPEED && val[1] < 0 }
score -= 2 # Will confuse
end
end
next score
}
)
Battle::AI::Handlers::ItemRanking.add(:METALPOWDER,
proc { |item, score, battler, ai|
next score if battler.battler.isSpecies?(:DITTO) && !battler.effects[PBEffects::Transform]
next 0
}
)
Battle::AI::Handlers::ItemRanking.copy(:METALPOWDER, :QUICKPOWDER)
Battle::AI::Handlers::ItemRanking.add(:MISTYSEED,
proc { |item, score, battler, ai|
next score if battler.check_for_move { |m| m.is_a?(Battle::Move::StartMistyTerrain) }
next 0
}
)
Battle::AI::Handlers::ItemRanking.add(:ORANBERRY,
proc { |item, score, battler, ai|
next [10 - (battler.totalhp / 8), 1].max
}
)
Battle::AI::Handlers::ItemRanking.add(:POWERHERB,
proc { |item, score, battler, ai|
next score if battler.check_for_move do |m|
m.is_a?(Battle::Move::TwoTurnMove) &&
!m.is_a?(Battle::Move::TwoTurnAttackInvulnerableInSkyTargetCannotAct)
end
next 0
}
)
Battle::AI::Handlers::ItemRanking.add(:PSYCHICSEED,
proc { |item, score, battler, ai|
next score if battler.check_for_move { |m| m.is_a?(Battle::Move::StartPsychicTerrain) }
next 0
}
)
Battle::AI::Handlers::ItemRanking.add(:RINGTARGET,
proc { |item, score, battler, ai|
has_immunity = false
battler.pbTypes(true).each do |type|
has_immunity = GameData::Type.get(type).immunities.length > 0
break if has_immunity
end
next score if has_immunity
next 0
}
)
Battle::AI::Handlers::ItemRanking.add(:SMOOTHROCK,
proc { |item, score, battler, ai|
next score if battler.check_for_move { |m| m.is_a?(Battle::Move::StartSandstormWeather) }
next 0
}
)
Battle::AI::Handlers::ItemRanking.add(:SOULDEW,
proc { |item, score, battler, ai|
next 0 if !battler.battler.isSpecies?(:LATIAS) && !battler.battler.isSpecies?(:LATIOS)
if Settings::SOUL_DEW_POWERS_UP_TYPES
next 0 if !battler.has_damaging_move_of_type?(:PSYCHIC, :DRAGON)
elsif battler.check_for_move { |m| m.specialMove?(m.type) }
next 10
else
next 6 # Boosts SpDef
end
next score
}
)
Battle::AI::Handlers::ItemRanking.add(:TERRAINEXTENDER,
proc { |item, score, battler, ai|
next score if battler.check_for_move do |m|
m.is_a?(Battle::Move::StartElectricTerrain) ||
m.is_a?(Battle::Move::StartGrassyTerrain) ||
m.is_a?(Battle::Move::StartMistyTerrain) ||
m.is_a?(Battle::Move::StartPsychicTerrain)
end
next 0
}
)
Battle::AI::Handlers::ItemRanking.add(:THICKCLUB,
proc { |item, score, battler, ai|
next score if (battler.battler.isSpecies?(:CUBONE) || battler.battler.isSpecies?(:MAROWAK)) &&
battler.check_for_move { |m| m.physicalMove?(m.type) }
next 0
}
)
Battle::AI::Handlers::ItemRanking.add(:THROATSPRAY,
proc { |item, score, battler, ai|
next score if battler.check_for_move { |m| m.soundMove? }
next 0
}
)
Battle::AI::Handlers::ItemRanking.add(:TOXICORB,
proc { |item, score, battler, ai|
next 0 if battler.status != :NONE
next 7 if battler.wants_status_problem?(:POISON)
next score
}
)
Battle::AI::Handlers::ItemRanking.add(:WHITEHERB,
proc { |item, score, battler, ai|
if ai.trainer.high_skill?
score += 1 if battler.has_move_with_function("LowerUserDefSpDef1RaiseUserAtkSpAtkSpd2")
end
next score
}
)
Battle::AI::Handlers::ItemRanking.add(:WIKIBERRY,
proc { |item, score, battler, ai|
if Settings::MECHANICS_GENERATION == 7 # Heals 50%
score += 2
elsif Settings::MECHANICS_GENERATION <= 6 # Heals 12.5%
score -= 3
end
if ai.trainer.high_skill?
if battler.battler.nature.stat_changes.any? { |val| val[0] == :SPECIAL_ATTACK && val[1] < 0 }
score -= 2 # Will confuse
end
end
next score
}
)
Battle::AI::Handlers::ItemRanking.add(:ZOOMLENS,
proc { |item, score, battler, ai|
if ai.trainer.high_skill?
score += 1 if battler.stages[:ACCURACY] < 0
battler.battler.eachMove do |m|
next if m.accuracy == 0 || m.is_a?(Battle::Move::OHKO)
next if m.accuracy > 70
score += 1
break
end
end
next score
}
)
Battle::AI::Handlers::ItemRanking.addIf(:type_boosting_items,
proc { |item|
next [:BLACKBELT, :BLACKGLASSES, :CHARCOAL, :DRAGONFANG, :HARDSTONE,
:MAGNET, :METALCOAT, :MIRACLESEED, :MYSTICWATER, :NEVERMELTICE,
:POISONBARB, :SHARPBEAK, :SILKSCARF, :SILVERPOWDER, :SOFTSAND,
:SPELLTAG, :TWISTEDSPOON,
:DRACOPLATE, :DREADPLATE, :EARTHPLATE, :FISTPLATE, :FLAMEPLATE,
:ICICLEPLATE, :INSECTPLATE, :IRONPLATE, :MEADOWPLATE, :MINDPLATE,
:PIXIEPLATE, :SKYPLATE, :SPLASHPLATE, :SPOOKYPLATE, :STONEPLATE,
:TOXICPLATE, :ZAPPLATE,
:ODDINCENSE, :ROCKINCENSE, :ROSEINCENSE, :SEAINCENSE, :WAVEINCENSE].include?(item)
},
proc { |item, score, battler, ai|
boosters = {
:BUG => [:SILVERPOWDER, :INSECTPLATE],
:DARK => [:BLACKGLASSES, :DREADPLATE],
:DRAGON => [:DRAGONFANG, :DRACOPLATE],
:ELECTRIC => [:MAGNET, :ZAPPLATE],
:FAIRY => [:PIXIEPLATE],
:FIGHTING => [:BLACKBELT, :FISTPLATE],
:FIRE => [:CHARCOAL, :FLAMEPLATE],
:FLYING => [:SHARPBEAK, :SKYPLATE],
:GHOST => [:SPELLTAG, :SPOOKYPLATE],
:GRASS => [:MIRACLESEED, :MEADOWPLATE, :ROSEINCENSE],
:GROUND => [:SOFTSAND, :EARTHPLATE],
:ICE => [:NEVERMELTICE, :ICICLEPLATE],
:NORMAL => [:SILKSCARF],
:POISON => [:POISONBARB, :TOXICPLATE],
:PSYCHIC => [:TWISTEDSPOON, :MINDPLATE, :ODDINCENSE],
:ROCK => [:HARDSTONE, :STONEPLATE, :ROCKINCENSE],
:STEEL => [:METALCOAT, :IRONPLATE],
:WATER => [:MYSTICWATER, :SPLASHPLATE, :SEAINCENSE, :WAVEINCENSE],
}
boosted_type = nil
boosters.each_pair do |type, items|
next if !items.include?(item)
boosted_type = type
break
end
next score if boosted_type && battler.has_damaging_move_of_type?(boosted_type)
next 0
}
)
Battle::AI::Handlers::ItemRanking.addIf(:gems,
proc { |item|
next [:FIREGEM, :WATERGEM, :ELECTRICGEM, :GRASSGEM, :ICEGEM, :FIGHTINGGEM,
:POISONGEM, :GROUNDGEM, :FLYINGGEM, :PSYCHICGEM, :BUGGEM, :ROCKGEM,
:GHOSTGEM, :DRAGONGEM, :DARKGEM, :STEELGEM, :FAIRYGEM, :NORMALGEM].include?(item)
},
proc { |item, score, battler, ai|
score += 2 if Settings::MECHANICS_GENERATION <= 5 # 1.5x boost rather than 1.3x
boosted_type = {
:BUGGEM => :BUG,
:DARKGEM => :DARK,
:DRAGONGEM => :DRAGON,
:ELECTRICGEM => :ELECTRIC,
:FAIRYGEM => :FAIRY,
:FIGHTINGGEM => :FIGHTING,
:FIREGEM => :FIRE,
:FLYINGGEM => :FLYING,
:GHOSTGEM => :GHOST,
:GRASSGEM => :GRASS,
:GROUNDGEM => :GROUND,
:ICEGEM => :ICE,
:NORMALGEM => :NORMAL,
:POISONGEM => :POISON,
:PSYCHICGEM => :PSYCHIC,
:ROCKGEM => :ROCK,
:STEELGEM => :STEEL,
:WATERGEM => :WATER,
}[item]
next score if boosted_type && battler.has_damaging_move_of_type?(boosted_type)
next 0
}
)

View File

@@ -0,0 +1,100 @@
#===============================================================================
# AI skill levels:
# 0: Wild Pokémon
# 1-31: Basic trainer (young/inexperienced)
# 32-47: Medium skill
# 48-99: High skill
# 100+: Best skill (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.
#
# Skill flags:
# PredictMoveFailure
# ScoreMoves
# PreferMultiTargetMoves
# HPAware (considers HP values of user/target for "worth it?" score changes)
# ConsiderSwitching (can choose to switch out Pokémon)
# ReserveLastPokemon (don't switch it in if possible)
# UsePokemonInOrder (uses earliest-listed Pokémon possible)
#
# Anti-skill flags are skill flags with "Anti" at the beginning. An "AntiXYZ"
# flag will negate the corresponding "XYZ" flag.
#===============================================================================
class Battle::AI::AITrainer
attr_reader :side, :trainer_index
attr_reader :skill
def initialize(ai, side, index, trainer)
@ai = ai
@side = side
@trainer_index = index
@trainer = trainer
@skill = 0
@skill_flags = []
set_up_skill
set_up_skill_flags
sanitize_skill_flags
end
def set_up_skill
if @trainer
@skill = @trainer.skill_level
elsif Settings::SMARTER_WILD_LEGENDARY_POKEMON
# Give wild legendary/mythical Pokémon a higher skill
wild_battler = @ai.battle.battlers[@side]
sp_data = wild_battler.pokemon.species_data
if sp_data.has_flag?("Legendary") ||
sp_data.has_flag?("Mythical") ||
sp_data.has_flag?("UltraBeast")
@skill = 32 # Medium skill
end
end
end
def set_up_skill_flags
if @trainer
@trainer.flags.each { |flag| @skill_flags.push(flag) }
end
if @skill > 0
@skill_flags.push("PredictMoveFailure")
@skill_flags.push("ScoreMoves")
@skill_flags.push("PreferMultiTargetMoves")
end
if medium_skill?
@skill_flags.push("ConsiderSwitching")
@skill_flags.push("HPAware")
end
if !medium_skill?
@skill_flags.push("UsePokemonInOrder")
elsif best_skill?
@skill_flags.push("ReserveLastPokemon")
end
end
def sanitize_skill_flags
# NOTE: Any skill flag which is shorthand for multiple other skill flags
# should be "unpacked" here.
# Remove any skill flag "XYZ" if there is also an "AntiXYZ" skill flag
@skill_flags.each_with_index do |flag, i|
@skill_flags[i] = nil if @skill_flags.include?("Anti" + flag)
end
@skill_flags.compact!
end
def has_skill_flag?(flag)
return @skill_flags.include?(flag)
end
def medium_skill?
return @skill >= 32
end
def high_skill?
return @skill >= 48
end
def best_skill?
return @skill >= 100
end
end

View File

@@ -0,0 +1,595 @@
#===============================================================================
#
#===============================================================================
class Battle::AI::AIBattler
attr_reader :index, :side, :party_index
attr_reader :battler
def initialize(ai, index)
@ai = ai
@index = index
@side = (@ai.battle.opposes?(@index)) ? 1 : 0
refresh_battler
end
def refresh_battler
old_party_index = @party_index
@battler = @ai.battle.battlers[@index]
@party_index = battler.pokemonIndex
end
def pokemon; return battler.pokemon; end
def level; return battler.level; end
def hp; return battler.hp; end
def totalhp; return battler.totalhp; end
def fainted?; return battler.fainted?; end
def status; return battler.status; end
def statusCount; return battler.statusCount; end
def gender; return battler.gender; end
def turnCount; return battler.turnCount; end
def effects; return battler.effects; end
def stages; return battler.stages; end
def statStageAtMax?(stat); return battler.statStageAtMax?(stat); end
def statStageAtMin?(stat); return battler.statStageAtMin?(stat); end
def moves; return battler.moves; end
def wild?
return @ai.battle.wildBattle? && opposes?
end
def name
return sprintf("%s (%d)", battler.name, @index)
end
def opposes?(other = nil)
return @side == 1 if other.nil?
return other.side != @side
end
def idxOwnSide; return battler.idxOwnSide; end
def pbOwnSide; return battler.pbOwnSide; end
def idxOpposingSide; return battler.idxOpposingSide; end
def pbOpposingSide; return battler.pbOpposingSide; end
#-----------------------------------------------------------------------------
# Returns how much damage this battler will take at the end of this round.
def rough_end_of_round_damage
ret = 0
# Weather
weather = battler.effectiveWeather
if @ai.battle.field.weatherDuration == 1
weather = @ai.battle.field.defaultWeather
weather = :None if @ai.battle.allBattlers.any? { |b| b.hasActiveAbility?([:CLOUDNINE, :AIRLOCK]) }
weather = :None if [:Sun, :Rain, :HarshSun, :HeavyRain].include?(weather) && has_active_item?(:UTILITYUMBRELLA)
end
case weather
when :Sandstorm
ret += [self.totalhp / 16, 1].max if battler.takesSandstormDamage?
when :Hail
ret += [self.totalhp / 16, 1].max if battler.takesHailDamage?
when :ShadowSky
ret += [self.totalhp / 16, 1].max if battler.takesShadowSkyDamage?
end
case ability_id
when :DRYSKIN
ret += [self.totalhp / 8, 1].max if [:Sun, :HarshSun].include?(weather) && battler.takesIndirectDamage?
ret -= [self.totalhp / 8, 1].max if [:Rain, :HeavyRain].include?(weather) && battler.canHeal?
when :ICEBODY
ret -= [self.totalhp / 16, 1].max if weather == :Hail && battler.canHeal?
when :RAINDISH
ret -= [self.totalhp / 16, 1].max if [:Rain, :HeavyRain].include?(weather) && battler.canHeal?
when :SOLARPOWER
ret += [self.totalhp / 8, 1].max if [:Sun, :HarshSun].include?(weather) && battler.takesIndirectDamage?
end
# Future Sight/Doom Desire
# NOTE: Not worth estimating the damage from this.
# Wish
if @ai.battle.positions[@index].effects[PBEffects::Wish] == 1 && battler.canHeal?
ret -= @ai.battle.positions[@index].effects[PBEffects::WishAmount]
end
# Sea of Fire
if @ai.battle.sides[@side].effects[PBEffects::SeaOfFire] > 1 &&
battler.takesIndirectDamage? && !has_type?(:FIRE)
ret += [self.totalhp / 8, 1].max
end
# Grassy Terrain (healing)
if @ai.battle.field.terrain == :Grassy && battler.affectedByTerrain? && battler.canHeal?
ret -= [self.totalhp / 16, 1].max
end
# Leftovers/Black Sludge
if has_active_item?(:BLACKSLUDGE)
if has_type?(:POISON)
ret -= [self.totalhp / 16, 1].max if battler.canHeal?
else
ret += [self.totalhp / 8, 1].max if battler.takesIndirectDamage?
end
elsif has_active_item?(:LEFTOVERS)
ret -= [self.totalhp / 16, 1].max if battler.canHeal?
end
# Aqua Ring
if self.effects[PBEffects::AquaRing] && battler.canHeal?
amt = self.totalhp / 16
amt = (amt * 1.3).floor if has_active_item?(:BIGROOT)
ret -= [amt, 1].max
end
# Ingrain
if self.effects[PBEffects::Ingrain] && battler.canHeal?
amt = self.totalhp / 16
amt = (amt * 1.3).floor if has_active_item?(:BIGROOT)
ret -= [amt, 1].max
end
# Leech Seed
if self.effects[PBEffects::LeechSeed] >= 0
if battler.takesIndirectDamage?
ret += [self.totalhp / 8, 1].max if battler.takesIndirectDamage?
end
else
@ai.each_battler do |b, i|
next if i == @index || b.effects[PBEffects::LeechSeed] != @index
amt = [[b.totalhp / 8, b.hp].min, 1].max
amt = (amt * 1.3).floor if has_active_item?(:BIGROOT)
ret -= [amt, 1].max
end
end
# Hyper Mode (Shadow Pokémon)
if battler.inHyperMode?
ret += [self.totalhp / 24, 1].max
end
# Poison/burn/Nightmare
if self.status == :POISON
if has_active_ability?(:POISONHEAL)
ret -= [self.totalhp / 8, 1].max if battler.canHeal?
elsif battler.takesIndirectDamage?
mult = 2
mult = [self.effects[PBEffects::Toxic] + 1, 16].min if self.statusCount > 0 # Toxic
ret += [mult * self.totalhp / 16, 1].max
end
elsif self.status == :BURN
if battler.takesIndirectDamage?
amt = (Settings::MECHANICS_GENERATION >= 7) ? self.totalhp / 16 : self.totalhp / 8
amt = (amt / 2.0).round if has_active_ability?(:HEATPROOF)
ret += [amt, 1].max
end
elsif battler.asleep? && self.statusCount > 1 && self.effects[PBEffects::Nightmare]
ret += [self.totalhp / 4, 1].max if battler.takesIndirectDamage?
end
# Curse
if self.effects[PBEffects::Curse]
ret += [self.totalhp / 4, 1].max if battler.takesIndirectDamage?
end
# Trapping damage
if self.effects[PBEffects::Trapping] > 1 && battler.takesIndirectDamage?
amt = (Settings::MECHANICS_GENERATION >= 6) ? self.totalhp / 8 : self.totalhp / 16
if @ai.battlers[self.effects[PBEffects::TrappingUser]].has_active_item?(:BINDINGBAND)
amt = (Settings::MECHANICS_GENERATION >= 6) ? self.totalhp / 6 : self.totalhp / 8
end
ret += [amt, 1].max
end
# Perish Song
return 999_999 if self.effects[PBEffects::PerishSong] == 1
# Bad Dreams
if battler.asleep? && self.statusCount > 1 && battler.takesIndirectDamage?
@ai.each_battler do |b, i|
next if i == @index || !b.battler.near?(battler) || !b.has_active_ability?(:BADDREAMS)
ret += [self.totalhp / 8, 1].max
end
end
# Sticky Barb
if has_active_item?(:STICKYBARB) && battler.takesIndirectDamage?
ret += [self.totalhp / 8, 1].max
end
return ret
end
#-----------------------------------------------------------------------------
def base_stat(stat)
ret = 0
case stat
when :ATTACK then ret = battler.attack
when :DEFENSE then ret = battler.defense
when :SPECIAL_ATTACK then ret = battler.spatk
when :SPECIAL_DEFENSE then ret = battler.spdef
when :SPEED then ret = battler.speed
end
return ret
end
def rough_stat(stat)
return battler.pbSpeed if stat == :SPEED && @ai.trainer.high_skill?
stage_mul = Battle::Battler::STAT_STAGE_MULTIPLIERS
stage_div = Battle::Battler::STAT_STAGE_DIVISORS
if [:ACCURACY, :EVASION].include?(stat)
stage_mul = Battle::Battler::ACC_EVA_STAGE_MULTIPLIERS
stage_div = Battle::Battler::ACC_EVA_STAGE_DIVISORS
end
stage = battler.stages[stat] + Battle::Battler::STAT_STAGE_MAXIMUM
value = base_stat(stat)
return (value.to_f * stage_mul[stage] / stage_div[stage]).floor
end
def faster_than?(other)
return false if other.nil?
this_speed = rough_stat(:SPEED)
other_speed = other.rough_stat(:SPEED)
return (this_speed > other_speed) ^ (@ai.battle.field.effects[PBEffects::TrickRoom] > 0)
end
#-----------------------------------------------------------------------------
def types; return battler.types; end
def pbTypes(withExtraType = false); return battler.pbTypes(withExtraType); end
def has_type?(type)
return false if !type
active_types = pbTypes(true)
return active_types.include?(GameData::Type.get(type).id)
end
alias pbHasType? has_type?
def effectiveness_of_type_against_battler(type, user = nil, move = nil)
ret = Effectiveness::NORMAL_EFFECTIVE_MULTIPLIER
return ret if !type
return ret if type == :GROUND && has_type?(:FLYING) && has_active_item?(:IRONBALL)
# Get effectivenesses
if type == :SHADOW
if battler.shadowPokemon?
ret = Effectiveness::NOT_VERY_EFFECTIVE_MULTIPLIER
else
ret = Effectiveness::SUPER_EFFECTIVE_MULTIPLIER
end
else
battler.pbTypes(true).each do |defend_type|
mult = effectiveness_of_type_against_single_battler_type(type, defend_type, user)
if move
case move.function
when "HitsTargetInSkyGroundsTarget"
mult = Effectiveness::NORMAL_EFFECTIVE_MULTIPLIER if type == :GROUND && defend_type == :FLYING
when "FreezeTargetSuperEffectiveAgainstWater"
mult = Effectiveness::SUPER_EFFECTIVE_MULTIPLIER if defend_type == :WATER
end
end
ret *= mult
end
ret *= 2 if self.effects[PBEffects::TarShot] && type == :FIRE
end
return ret
end
#-----------------------------------------------------------------------------
def ability_id; return battler.ability_id; end
def ability; return battler.ability; end
def ability_active?
return battler.abilityActive?
end
def has_active_ability?(ability, ignore_fainted = false)
return battler.hasActiveAbility?(ability, ignore_fainted)
end
def has_mold_breaker?
return battler.hasMoldBreaker?
end
#-----------------------------------------------------------------------------
def item_id; return battler.item_id; end
def item; return battler.item; end
def item_active?
return battler.itemActive?
end
def has_active_item?(item)
return battler.hasActiveItem?(item)
end
#-----------------------------------------------------------------------------
def check_for_move
ret = false
battler.eachMove do |move|
next if move.pp == 0 && move.total_pp > 0
next unless yield move
ret = true
break
end
return ret
end
def has_damaging_move_of_type?(*types)
check_for_move do |m|
return true if m.damagingMove? && types.include?(m.pbCalcType(battler))
end
return false
end
def has_move_with_function?(*functions)
check_for_move { |m| return true if functions.include?(m.function) }
return false
end
#-----------------------------------------------------------------------------
def can_attack?
return false if self.effects[PBEffects::HyperBeam] > 0
return false if status == :SLEEP && statusCount > 1
return false if status == :FROZEN # Only 20% chance of unthawing; assune it won't
return false if self.effects[PBEffects::Truant] && has_active_ability?(:TRUANT)
return false if self.effects[PBEffects::Flinch]
# NOTE: Confusion/infatuation/paralysis have higher chances of allowing the
# attack, so the battler is treated as able to attack in those cases.
return true
end
def can_switch_lax?
return false if wild?
@ai.battle.eachInTeamFromBattlerIndex(@index) do |pkmn, i|
return true if @ai.battle.pbCanSwitchIn?(@index, i)
end
return false
end
# NOTE: This specifically means "is not currently trapped but can become
# trapped by an effect". Similar to def pbCanSwitchOut? but this returns
# false if any certain switching OR certain trapping applies.
def can_become_trapped?
return false if fainted?
# Ability/item effects that allow switching no matter what
if ability_active? && Battle::AbilityEffects.triggerCertainSwitching(ability, battler, @ai.battle)
return false
end
if item_active? && Battle::ItemEffects.triggerCertainSwitching(item, battler, @ai.battle)
return false
end
# Other certain switching effects
return false if Settings::MORE_TYPE_EFFECTS && has_type?(:GHOST)
# Other certain trapping effects
return false if battler.trappedInBattle?
# Trapping abilities/items
@ai.each_foe_battler(side) do |b, i|
if b.ability_active? &&
Battle::AbilityEffects.triggerTrappingByTarget(b.ability, battler, b.battler, @ai.battle)
return false
end
if b.item_active? &&
Battle::ItemEffects.triggerTrappingByTarget(b.item, battler, b.battler, @ai.battle)
return false
end
end
return true
end
#-----------------------------------------------------------------------------
def wants_status_problem?(new_status)
return true if new_status == :NONE
if ability_active?
case ability_id
when :GUTS
return true if ![:SLEEP, :FROZEN].include?(new_status) &&
@ai.stat_raise_worthwhile?(self, :ATTACK, true)
when :MARVELSCALE
return true if @ai.stat_raise_worthwhile?(self, :DEFENSE, true)
when :QUICKFEET
return true if ![:SLEEP, :FROZEN].include?(new_status) &&
@ai.stat_raise_worthwhile?(self, :SPEED, true)
when :FLAREBOOST
return true if new_status == :BURN && @ai.stat_raise_worthwhile?(self, :SPECIAL_ATTACK, true)
when :TOXICBOOST
return true if new_status == :POISON && @ai.stat_raise_worthwhile?(self, :ATTACK, true)
when :POISONHEAL
return true if new_status == :POISON
when :MAGICGUARD # Want a harmless status problem to prevent getting a harmful one
return true if new_status == :POISON ||
(new_status == :BURN && !@ai.stat_raise_worthwhile?(self, :ATTACK, true))
end
end
return true if new_status == :SLEEP && check_for_move { |m| m.usableWhenAsleep? }
if has_move_with_function?("DoublePowerIfUserPoisonedBurnedParalyzed")
return true if [:POISON, :BURN, :PARALYSIS].include?(new_status)
end
return false
end
#-----------------------------------------------------------------------------
# Returns a value indicating how beneficial the given ability will be to this
# battler if it has it.
# Return values are typically between -10 and +10. 0 is indifferent, positive
# values mean this battler benefits, negative values mean this battler suffers.
# NOTE: This method assumes the ability isn't being negated. The calculations
# that call this method separately check for it being negated, because
# they need to do something special in that case.
def wants_ability?(ability = :NONE)
ability = ability.id if !ability.is_a?(Symbol) && ability.respond_to?("id")
# Get the base ability rating
ret = 0
Battle::AI::BASE_ABILITY_RATINGS.each_pair do |val, abilities|
next if !abilities.include?(ability)
ret = val
break
end
# Modify the rating based on ability-specific contexts
if @ai.trainer.medium_skill?
ret = Battle::AI::Handlers.modify_ability_ranking(ability, ret, self, @ai)
end
return ret
end
#-----------------------------------------------------------------------------
# Returns a value indicating how beneficial the given item will be to this
# battler if it is holding it.
# Return values are typically between -10 and +10. 0 is indifferent, positive
# values mean this battler benefits, negative values mean this battler suffers.
# NOTE: This method assumes the item isn't being negated. The calculations
# that call this method separately check for it being negated, because
# they need to do something special in that case.
def wants_item?(item)
item = :NONE if !item
item = item.id if !item.is_a?(Symbol) && item.respond_to?("id")
# Get the base item rating
ret = 0
Battle::AI::BASE_ITEM_RATINGS.each_pair do |val, items|
next if !items.include?(item)
ret = val
break
end
# Modify the rating based on item-specific contexts
if @ai.trainer.medium_skill?
ret = Battle::AI::Handlers.modify_item_ranking(item, ret, self, @ai)
end
# Prefer if this battler knows Fling and it will do a lot of damage/have an
# additional (negative) effect when flung
if item != :NONE && has_move_with_function?("ThrowUserItemAtTarget")
GameData::Item.get(item).flags.each do |flag|
next if !flag[/^Fling_(\d+)$/i]
amt = $~[1].to_i
ret += 1 if amt >= 80
ret += 1 if amt >= 100
break
end
if [:FLAMEORB, :KINGSROCK, :LIGHTBALL, :POISONBARB, :RAZORFANG, :TOXICORB].include?(item)
ret += 1
end
end
# Don't prefer if this battler knows Acrobatics
if has_move_with_function?("DoublePowerIfUserHasNoItem")
ret += (item == :NONE) ? 1 : -1
end
return ret
end
#-----------------------------------------------------------------------------
# Items can be consumed by Stuff Cheeks, Teatime, Bug Bite/Pluck and Fling.
def get_score_change_for_consuming_item(item, try_preserving_item = false)
ret = 0
case item
when :ORANBERRY, :BERRYJUICE, :ENIGMABERRY, :SITRUSBERRY
# Healing
ret += (hp > totalhp * 0.75) ? -6 : 6
ret = ret * 3 / 2 if GameData::Item.get(item).is_berry? && has_active_ability?(:RIPEN)
when :AGUAVBERRY, :FIGYBERRY, :IAPAPABERRY, :MAGOBERRY, :WIKIBERRY
# Healing with confusion
fraction_to_heal = 8 # Gens 6 and lower
if Settings::MECHANICS_GENERATION == 7
fraction_to_heal = 2
elsif Settings::MECHANICS_GENERATION >= 8
fraction_to_heal = 3
end
ret += (hp > totalhp * (1 - (1.0 / fraction_to_heal))) ? -6 : 6
ret = ret * 3 / 2 if GameData::Item.get(item).is_berry? && has_active_ability?(:RIPEN)
if @ai.trainer.high_skill?
flavor_stat = {
:AGUAVBERRY => :SPECIAL_DEFENSE,
:FIGYBERRY => :ATTACK,
:IAPAPABERRY => :DEFENSE,
:MAGOBERRY => :SPEED,
:WIKIBERRY => :SPECIAL_ATTACK
}[item]
if @battler.nature.stat_changes.any? { |val| val[0] == flavor_stat && val[1] < 0 }
ret -= 3 if @battler.pbCanConfuseSelf?(false)
end
end
when :ASPEARBERRY, :CHERIBERRY, :CHESTOBERRY, :PECHABERRY, :RAWSTBERRY
# Status cure
cured_status = {
:ASPEAR => :FROZEN,
:CHERIBERRY => :PARALYSIS,
:CHESTOBERRY => :SLEEP,
:PECHABERRY => :POISON,
:RAWSTBERRY => :BURN
}[item]
ret += (cured_status && status == cured_status) ? 6 : -6
when :PERSIMBERRY
# Confusion cure
ret += (self.effects[PBEffects::Confusion] > 1) ? 6 : -6
when :LUMBERRY
# Any status/confusion cure
ret += (status != :NONE || self.effects[PBEffects::Confusion] > 1) ? 6 : -6
when :MENTALHERB
# Cure mental effects
if self.effects[PBEffects::Attract] >= 0 ||
self.effects[PBEffects::Taunt] > 1 ||
self.effects[PBEffects::Encore] > 1 ||
self.effects[PBEffects::Torment] ||
self.effects[PBEffects::Disable] > 1 ||
self.effects[PBEffects::HealBlock] > 1
ret += 6
else
ret -= 6
end
when :APICOTBERRY, :GANLONBERRY, :LIECHIBERRY, :PETAYABERRY, :SALACBERRY,
:KEEBERRY, :MARANGABERRY
# Stat raise
stat = {
:APICOTBERRY => :SPECIAL_DEFENSE,
:GANLONBERRY => :DEFENSE,
:LIECHIBERRY => :ATTACK,
:PETAYABERRY => :SPECIAL_ATTACK,
:SALACBERRY => :SPEED,
:KEEBERRY => :DEFENSE,
:MARANGABERRY => :SPECIAL_DEFENSE
}[item]
ret += (stat && @ai.stat_raise_worthwhile?(self, stat)) ? 8 : -8
ret = ret * 3 / 2 if GameData::Item.get(item).is_berry? && has_active_ability?(:RIPEN)
when :STARFBERRY
# Random stat raise
ret += 8
ret = ret * 3 / 2 if GameData::Item.get(item).is_berry? && has_active_ability?(:RIPEN)
when :WHITEHERB
# Resets lowered stats
ret += (battler.hasLoweredStatStages?) ? 8 : -8
when :MICLEBERRY
# Raises accuracy of next move
ret += (@ai.stat_raise_worthwhile?(self, :ACCURACY, true)) ? 6 : -6
when :LANSATBERRY
# Focus energy
ret += (self.effects[PBEffects::FocusEnergy] < 2) ? 6 : -6
when :LEPPABERRY
# Restore PP
ret += 6
ret = ret * 3 / 2 if GameData::Item.get(item).is_berry? && has_active_ability?(:RIPEN)
end
ret = 0 if ret < 0 && !try_preserving_item
return ret
end
#-----------------------------------------------------------------------------
private
def effectiveness_of_type_against_single_battler_type(type, defend_type, user = nil)
ret = Effectiveness.calculate(type, defend_type)
if Effectiveness.ineffective_type?(type, defend_type)
# Ring Target
if has_active_item?(:RINGTARGET)
ret = Effectiveness::NORMAL_EFFECTIVE_MULTIPLIER
end
# Foresight
if (user&.has_active_ability?(:SCRAPPY) || self.effects[PBEffects::Foresight]) &&
defend_type == :GHOST
ret = Effectiveness::NORMAL_EFFECTIVE_MULTIPLIER
end
# Miracle Eye
if self.effects[PBEffects::MiracleEye] && defend_type == :DARK
ret = Effectiveness::NORMAL_EFFECTIVE_MULTIPLIER
end
elsif Effectiveness.super_effective_type?(type, defend_type)
# Delta Stream's weather
if battler.effectiveWeather == :StrongWinds && defend_type == :FLYING
ret = Effectiveness::NORMAL_EFFECTIVE_MULTIPLIER
end
end
# Grounded Flying-type Pokémon become susceptible to Ground moves
if !battler.airborne? && defend_type == :FLYING && type == :GROUND
ret = Effectiveness::NORMAL_EFFECTIVE_MULTIPLIER
end
return ret
end
end

View File

@@ -0,0 +1,562 @@
#===============================================================================
#
#===============================================================================
class Battle::AI::AIMove
attr_reader :move
def initialize(ai)
@ai = ai
end
def set_up(move)
@move = move
@move.calcType = rough_type
@ai.battle.moldBreaker ||= ["IgnoreTargetAbility",
"CategoryDependsOnHigherDamageIgnoreTargetAbility"].include?(function)
end
#-----------------------------------------------------------------------------
def id; return @move.id; end
def name; return @move.name; end
def physicalMove?(thisType = nil); return @move.physicalMove?(thisType); end
def specialMove?(thisType = nil); return @move.specialMove?(thisType); end
def damagingMove?; return @move.damagingMove?; end
def statusMove?; return @move.statusMove?; end
def function; return @move.function; end
#-----------------------------------------------------------------------------
def type; return @move.type; end
def rough_type
return @move.pbCalcType(@ai.user.battler) if @ai.trainer.medium_skill?
return @move.type
end
#-----------------------------------------------------------------------------
def pbTarget(user)
return @move.pbTarget((user.is_a?(Battle::AI::AIBattler)) ? user.battler : user)
end
# Returns whether this move targets multiple battlers.
def targets_multiple_battlers?
user_battler = @ai.user.battler
target_data = pbTarget(user_battler)
return false if target_data.num_targets <= 1
num_targets = 0
case target_data.id
when :AllAllies
@ai.battle.allSameSideBattlers(user_battler).each { |b| num_targets += 1 if b.index != user_battler.index }
when :UserAndAllies
@ai.battle.allSameSideBattlers(user_battler).each { |_b| num_targets += 1 }
when :AllNearFoes
@ai.battle.allOtherSideBattlers(user_battler).each { |b| num_targets += 1 if b.near?(user_battler) }
when :AllFoes
@ai.battle.allOtherSideBattlers(user_battler).each { |_b| num_targets += 1 }
when :AllNearOthers
@ai.battle.allBattlers.each { |b| num_targets += 1 if b.near?(user_battler) }
when :AllBattlers
@ai.battle.allBattlers.each { |_b| num_targets += 1 }
end
return num_targets > 1
end
#-----------------------------------------------------------------------------
def rough_priority(user)
ret = @move.pbPriority(user.battler)
if user.ability_active?
ret = Battle::AbilityEffects.triggerPriorityChange(user.ability, user.battler, @move, ret)
user.battler.effects[PBEffects::Prankster] = false # Untrigger this
end
return ret
end
#-----------------------------------------------------------------------------
# Returns this move's base power, taking into account various effects that
# modify it.
def base_power
ret = @move.power
ret = 60 if ret == 1
return ret if !@ai.trainer.medium_skill?
return Battle::AI::Handlers.get_base_power(function,
ret, self, @ai.user, @ai.target, @ai, @ai.battle)
end
# Full damage calculation.
def rough_damage
base_dmg = base_power
return base_dmg if @move.is_a?(Battle::Move::FixedDamageMove)
max_stage = Battle::Battler::STAT_STAGE_MAXIMUM
stage_mul = Battle::Battler::STAT_STAGE_MULTIPLIERS
stage_div = Battle::Battler::STAT_STAGE_DIVISORS
# Get the user and target of this move
user = @ai.user
user_battler = user.battler
target = @ai.target
target_battler = target.battler
# Get the move's type
calc_type = rough_type
# Decide whether the move has 50% chance of higher of being a critical hit
crit_stage = rough_critical_hit_stage
is_critical = crit_stage >= Battle::Move::CRITICAL_HIT_RATIOS.length ||
Battle::Move::CRITICAL_HIT_RATIOS[crit_stage] <= 2
##### Calculate user's attack stat #####
if ["CategoryDependsOnHigherDamagePoisonTarget",
"CategoryDependsOnHigherDamageIgnoreTargetAbility"].include?(function)
@move.pbOnStartUse(user.battler, [target.battler]) # Calculate category
end
atk, atk_stage = @move.pbGetAttackStats(user.battler, target.battler)
if !target.has_active_ability?(:UNAWARE) || @ai.battle.moldBreaker
atk_stage = max_stage if is_critical && atk_stage < max_stage
atk = (atk.to_f * stage_mul[atk_stage] / stage_div[atk_stage]).floor
end
##### Calculate target's defense stat #####
defense, def_stage = @move.pbGetDefenseStats(user.battler, target.battler)
if !user.has_active_ability?(:UNAWARE) || @ai.battle.moldBreaker
def_stage = max_stage if is_critical && def_stage > max_stage
defense = (defense.to_f * stage_mul[def_stage] / stage_div[def_stage]).floor
end
##### Calculate all multiplier effects #####
multipliers = {
:power_multiplier => 1.0,
:attack_multiplier => 1.0,
:defense_multiplier => 1.0,
:final_damage_multiplier => 1.0
}
# Global abilities
if @ai.trainer.medium_skill? &&
((@ai.battle.pbCheckGlobalAbility(:DARKAURA) && calc_type == :DARK) ||
(@ai.battle.pbCheckGlobalAbility(:FAIRYAURA) && calc_type == :FAIRY))
if @ai.battle.pbCheckGlobalAbility(:AURABREAK)
multipliers[:power_multiplier] *= 3 / 4.0
else
multipliers[:power_multiplier] *= 4 / 3.0
end
end
# Ability effects that alter damage
if user.ability_active?
case user.ability_id
when :AERILATE, :GALVANIZE, :PIXILATE, :REFRIGERATE
multipliers[:power_multiplier] *= 1.2 if type == :NORMAL # NOTE: Not calc_type.
when :ANALYTIC
if rough_priority(user) <= 0
user_faster = false
@ai.each_battler do |b, i|
user_faster = (i != user.index && user.faster_than?(b))
break if user_faster
end
multipliers[:power_multiplier] *= 1.3 if !user_faster
end
when :NEUROFORCE
if Effectiveness.super_effective_type?(calc_type, *target.pbTypes(true))
multipliers[:final_damage_multiplier] *= 1.25
end
when :NORMALIZE
multipliers[:power_multiplier] *= 1.2 if Settings::MECHANICS_GENERATION >= 7
when :SNIPER
multipliers[:final_damage_multiplier] *= 1.5 if is_critical
when :STAKEOUT
# NOTE: Can't predict whether the target will switch out this round.
when :TINTEDLENS
if Effectiveness.resistant_type?(calc_type, *target.pbTypes(true))
multipliers[:final_damage_multiplier] *= 2
end
else
Battle::AbilityEffects.triggerDamageCalcFromUser(
user.ability, user_battler, target_battler, @move, multipliers, base_dmg, calc_type
)
end
end
if !@ai.battle.moldBreaker
user_battler.allAllies.each do |b|
next if !b.abilityActive?
Battle::AbilityEffects.triggerDamageCalcFromAlly(
b.ability, user_battler, target_battler, @move, multipliers, base_dmg, calc_type
)
end
if target.ability_active?
case target.ability_id
when :FILTER, :SOLIDROCK
if Effectiveness.super_effective_type?(calc_type, *target.pbTypes(true))
multipliers[:final_damage_multiplier] *= 0.75
end
else
Battle::AbilityEffects.triggerDamageCalcFromTarget(
target.ability, user_battler, target_battler, @move, multipliers, base_dmg, calc_type
)
end
end
end
if target.ability_active?
Battle::AbilityEffects.triggerDamageCalcFromTargetNonIgnorable(
target.ability, user_battler, target_battler, @move, multipliers, base_dmg, calc_type
)
end
if !@ai.battle.moldBreaker
target_battler.allAllies.each do |b|
next if !b.abilityActive?
Battle::AbilityEffects.triggerDamageCalcFromTargetAlly(
b.ability, user_battler, target_battler, @move, multipliers, base_dmg, calc_type
)
end
end
# Item effects that alter damage
if user.item_active?
case user.item_id
when :EXPERTBELT
if Effectiveness.super_effective_type?(calc_type, *target.pbTypes(true))
multipliers[:final_damage_multiplier] *= 1.2
end
when :LIFEORB
multipliers[:final_damage_multiplier] *= 1.3
else
Battle::ItemEffects.triggerDamageCalcFromUser(
user.item, user_battler, target_battler, @move, multipliers, base_dmg, calc_type
)
user.effects[PBEffects::GemConsumed] = nil # Untrigger consuming of Gems
end
end
if target.item_active? && target.item && !target.item.is_berry?
Battle::ItemEffects.triggerDamageCalcFromTarget(
target.item, user_battler, target_battler, @move, multipliers, base_dmg, calc_type
)
end
# Parental Bond
if user.has_active_ability?(:PARENTALBOND)
multipliers[:power_multiplier] *= (Settings::MECHANICS_GENERATION >= 7) ? 1.25 : 1.5
end
# Me First - n/a because can't predict the move Me First will use
# Helping Hand - n/a
# Charge
if @ai.trainer.medium_skill? &&
user.effects[PBEffects::Charge] > 0 && calc_type == :ELECTRIC
multipliers[:power_multiplier] *= 2
end
# Mud Sport and Water Sport
if @ai.trainer.medium_skill?
if calc_type == :ELECTRIC
if @ai.battle.allBattlers.any? { |b| b.effects[PBEffects::MudSport] }
multipliers[:power_multiplier] /= 3
end
if @ai.battle.field.effects[PBEffects::MudSportField] > 0
multipliers[:power_multiplier] /= 3
end
elsif calc_type == :FIRE
if @ai.battle.allBattlers.any? { |b| b.effects[PBEffects::WaterSport] }
multipliers[:power_multiplier] /= 3
end
if @ai.battle.field.effects[PBEffects::WaterSportField] > 0
multipliers[:power_multiplier] /= 3
end
end
end
# Terrain moves
if @ai.trainer.medium_skill?
terrain_multiplier = (Settings::MECHANICS_GENERATION >= 8) ? 1.3 : 1.5
case @ai.battle.field.terrain
when :Electric
multipliers[:power_multiplier] *= terrain_multiplier if calc_type == :ELECTRIC && user_battler.affectedByTerrain?
when :Grassy
multipliers[:power_multiplier] *= terrain_multiplier if calc_type == :GRASS && user_battler.affectedByTerrain?
when :Psychic
multipliers[:power_multiplier] *= terrain_multiplier if calc_type == :PSYCHIC && user_battler.affectedByTerrain?
when :Misty
multipliers[:power_multiplier] /= 2 if calc_type == :DRAGON && target_battler.affectedByTerrain?
end
end
# Badge multipliers
if @ai.trainer.high_skill? && @ai.battle.internalBattle && target_battler.pbOwnedByPlayer?
# Don't need to check the Atk/Sp Atk-boosting badges because the AI
# won't control the player's Pokémon.
if physicalMove?(calc_type) && @ai.battle.pbPlayer.badge_count >= Settings::NUM_BADGES_BOOST_DEFENSE
multipliers[:defense_multiplier] *= 1.1
elsif specialMove?(calc_type) && @ai.battle.pbPlayer.badge_count >= Settings::NUM_BADGES_BOOST_SPDEF
multipliers[:defense_multiplier] *= 1.1
end
end
# Multi-targeting attacks
if @ai.trainer.high_skill? && targets_multiple_battlers?
multipliers[:final_damage_multiplier] *= 0.75
end
# Weather
if @ai.trainer.medium_skill?
case user_battler.effectiveWeather
when :Sun, :HarshSun
case calc_type
when :FIRE
multipliers[:final_damage_multiplier] *= 1.5
when :WATER
multipliers[:final_damage_multiplier] /= 2
end
when :Rain, :HeavyRain
case calc_type
when :FIRE
multipliers[:final_damage_multiplier] /= 2
when :WATER
multipliers[:final_damage_multiplier] *= 1.5
end
when :Sandstorm
if target.has_type?(:ROCK) && specialMove?(calc_type) &&
function != "UseTargetDefenseInsteadOfTargetSpDef" # Psyshock
multipliers[:defense_multiplier] *= 1.5
end
end
end
# Critical hits
if is_critical
if Settings::NEW_CRITICAL_HIT_RATE_MECHANICS
multipliers[:final_damage_multiplier] *= 1.5
else
multipliers[:final_damage_multiplier] *= 2
end
end
# Random variance - n/a
# STAB
if calc_type && user.has_type?(calc_type)
if user.has_active_ability?(:ADAPTABILITY)
multipliers[:final_damage_multiplier] *= 2
else
multipliers[:final_damage_multiplier] *= 1.5
end
end
# Type effectiveness
typemod = target.effectiveness_of_type_against_battler(calc_type, user, @move)
multipliers[:final_damage_multiplier] *= typemod
# Burn
if @ai.trainer.high_skill? && user.status == :BURN && physicalMove?(calc_type) &&
@move.damageReducedByBurn? && !user.has_active_ability?(:GUTS)
multipliers[:final_damage_multiplier] /= 2
end
# Aurora Veil, Reflect, Light Screen
if @ai.trainer.medium_skill? && !@move.ignoresReflect? && !is_critical &&
!user.has_active_ability?(:INFILTRATOR)
if target.pbOwnSide.effects[PBEffects::AuroraVeil] > 0
if @ai.battle.pbSideBattlerCount(target_battler) > 1
multipliers[:final_damage_multiplier] *= 2 / 3.0
else
multipliers[:final_damage_multiplier] /= 2
end
elsif target.pbOwnSide.effects[PBEffects::Reflect] > 0 && physicalMove?(calc_type)
if @ai.battle.pbSideBattlerCount(target_battler) > 1
multipliers[:final_damage_multiplier] *= 2 / 3.0
else
multipliers[:final_damage_multiplier] /= 2
end
elsif target.pbOwnSide.effects[PBEffects::LightScreen] > 0 && specialMove?(calc_type)
if @ai.battle.pbSideBattlerCount(target_battler) > 1
multipliers[:final_damage_multiplier] *= 2 / 3.0
else
multipliers[:final_damage_multiplier] /= 2
end
end
end
# Minimize
if @ai.trainer.medium_skill? && target.effects[PBEffects::Minimize] && @move.tramplesMinimize?
multipliers[:final_damage_multiplier] *= 2
end
# NOTE: No need to check pbBaseDamageMultiplier, as it's already accounted
# for in an AI's MoveBasePower handler or can't be checked now anyway.
# NOTE: No need to check pbModifyDamage, as it's already accounted for in an
# AI's MoveBasePower handler.
##### Main damage calculation #####
base_dmg = [(base_dmg * multipliers[:power_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 * base_dmg * atk / defense).floor / 50).floor + 2
damage = [(damage * multipliers[:final_damage_multiplier]).round, 1].max
ret = damage.floor
ret = target.hp - 1 if @move.nonLethal?(user_battler, target_battler) && ret >= target.hp
return ret
end
#-----------------------------------------------------------------------------
def accuracy
return @move.pbBaseAccuracy(@ai.user.battler, @ai.target.battler) if @ai.trainer.medium_skill?
return @move.accuracy
end
# Full accuracy calculation.
def rough_accuracy
# Determine user and target
user = @ai.user
user_battler = user.battler
target = @ai.target
target_battler = target.battler
# OHKO move accuracy
if @move.is_a?(Battle::Move::OHKO)
ret = self.accuracy + user.level - target.level
ret -= 10 if function == "OHKOIce" && !user.has_type?(:ICE)
return [ret, 0].max
end
# "Always hit" effects and "always hit" accuracy
if @ai.trainer.medium_skill?
return 100 if target.effects[PBEffects::Telekinesis] > 0
return 100 if target.effects[PBEffects::Minimize] && @move.tramplesMinimize? &&
Settings::MECHANICS_GENERATION >= 6
end
# Get base accuracy
baseAcc = self.accuracy
return 100 if baseAcc == 0
# Get the move's type
type = rough_type
# Calculate all modifier effects
modifiers = {}
modifiers[:base_accuracy] = baseAcc
modifiers[:accuracy_stage] = user.stages[:ACCURACY]
modifiers[:evasion_stage] = target.stages[:EVASION]
modifiers[:accuracy_multiplier] = 1.0
modifiers[:evasion_multiplier] = 1.0
apply_rough_accuracy_modifiers(user, target, type, modifiers)
# Check if move certainly misses/can't miss
return 0 if modifiers[:base_accuracy] < 0
return 100 if modifiers[:base_accuracy] == 0
# Calculation
max_stage = Battle::Battler::STAT_STAGE_MAXIMUM
accStage = [[modifiers[:accuracy_stage], -max_stage].max, max_stage].min + max_stage
evaStage = [[modifiers[:evasion_stage], -max_stage].max, max_stage].min + max_stage
stageMul = Battle::Battler::ACC_EVA_STAGE_MULTIPLIERS
stageDiv = Battle::Battler::ACC_EVA_STAGE_DIVISORS
accuracy = 100.0 * stageMul[accStage] / stageDiv[accStage]
evasion = 100.0 * stageMul[evaStage] / stageDiv[evaStage]
accuracy = (accuracy * modifiers[:accuracy_multiplier]).round
evasion = (evasion * modifiers[:evasion_multiplier]).round
evasion = 1 if evasion < 1
return modifiers[:base_accuracy] * accuracy / evasion
end
def apply_rough_accuracy_modifiers(user, target, calc_type, modifiers)
user_battler = user.battler
target_battler = target.battler
# Ability effects that alter accuracy calculation
if user.ability_active?
Battle::AbilityEffects.triggerAccuracyCalcFromUser(
user.ability, modifiers, user_battler, target_battler, @move, calc_type
)
end
user_battler.allAllies.each do |b|
next if !b.abilityActive?
Battle::AbilityEffects.triggerAccuracyCalcFromAlly(
b.ability, modifiers, user_battler, target_battler, @move, calc_type
)
end
if !@ai.battle.moldBreaker && target.ability_active?
Battle::AbilityEffects.triggerAccuracyCalcFromTarget(
target.ability, modifiers, user_battler, target_battler, @move, calc_type
)
end
# Item effects that alter accuracy calculation
if user.item_active?
if user.item == :ZOOMLENS
if rough_priority(user) <= 0
mods[:accuracy_multiplier] *= 1.2 if target.faster_than?(user)
end
else
Battle::ItemEffects.triggerAccuracyCalcFromUser(
user.item, modifiers, user_battler, target_battler, @move, calc_type
)
end
end
if target.item_active?
Battle::ItemEffects.triggerAccuracyCalcFromTarget(
target.item, modifiers, user_battler, target_battler, @move, calc_type
)
end
# Other effects, inc. ones that set accuracy_multiplier or evasion_stage to specific values
if @ai.battle.field.effects[PBEffects::Gravity] > 0
modifiers[:accuracy_multiplier] *= 5 / 3.0
end
if @ai.trainer.medium_skill?
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"
modifiers[:evasion_stage] = 0 if function == "IgnoreTargetDefSpDefEvaStatStages" # Chip Away
if @ai.trainer.medium_skill?
modifiers[:base_accuracy] = 0 if user.effects[PBEffects::LockOn] > 0 &&
user.effects[PBEffects::LockOnPos] == target.index
end
if @ai.trainer.medium_skill?
case function
when "BadPoisonTarget"
modifiers[:base_accuracy] = 0 if Settings::MORE_TYPE_EFFECTS &&
@move.statusMove? && user.has_type?(:POISON)
end
end
end
#-----------------------------------------------------------------------------
# Full critical hit chance calculation (returns the determined critical hit
# stage).
def rough_critical_hit_stage
user = @ai.user
user_battler = user.battler
target = @ai.target
target_battler = target.battler
return -1 if target_battler.pbOwnSide.effects[PBEffects::LuckyChant] > 0
crit_stage = 0
# Ability effects that alter critical hit rate
if user.ability_active?
crit_stage = Battle::AbilityEffects.triggerCriticalCalcFromUser(user_battler.ability,
user_battler, target_battler, crit_stage)
return -1 if crit_stage < 0
end
if !@ai.battle.moldBreaker && target.ability_active?
crit_stage = Battle::AbilityEffects.triggerCriticalCalcFromTarget(target_battler.ability,
user_battler, target_battler, crit_stage)
return -1 if crit_stage < 0
end
# Item effects that alter critical hit rate
if user.item_active?
crit_stage = Battle::ItemEffects.triggerCriticalCalcFromUser(user_battler.item,
user_battler, target_battler, crit_stage)
return -1 if crit_stage < 0
end
if target.item_active?
crit_stage = Battle::ItemEffects.triggerCriticalCalcFromTarget(user_battler.item,
user_battler, target_battler, crit_stage)
return -1 if crit_stage < 0
end
# Other effects
case @move.pbCritialOverride(user_battler, target_battler)
when 1 then return 99
when -1 then return -1
end
return 99 if crit_stage > 50 # Merciless
return 99 if user_battler.effects[PBEffects::LaserFocus] > 0
crit_stage += 1 if @move.highCriticalRate?
crit_stage += user_battler.effects[PBEffects::FocusEnergy]
crit_stage += 1 if user_battler.inHyperMode? && @move.type == :SHADOW
crit_stage = [crit_stage, Battle::Move::CRITICAL_HIT_RATIOS.length - 1].min
return crit_stage
end
#-----------------------------------------------------------------------------
# Return values:
# 0: Regular additional effect chance or isn't an additional effect
# -999: Additional effect will be negated
# Other: Amount to add to a move's score
def get_score_change_for_additional_effect(user, target = nil)
# Doesn't have an additional effect
return 0 if @move.addlEffect == 0
# Additional effect will be negated
return -999 if user.has_active_ability?(:SHEERFORCE)
return -999 if target && user.index != target.index &&
target.has_active_ability?(:SHIELDDUST) && !@ai.battle.moldBreaker
# Prefer if the additional effect will have an increased chance of working
return 5 if @move.addlEffect < 100 &&
(Settings::MECHANICS_GENERATION >= 6 || self.function != "EffectDependsOnEnvironment") &&
(user.has_active_ability?(:SERENEGRACE) || user.pbOwnSide.effects[PBEffects::Rainbow] > 0)
# No change to score
return 0
end
end

View File

@@ -0,0 +1,708 @@
#===============================================================================
#
#===============================================================================
# None
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveEffectScore.add("DoesNothingCongratulations",
proc { |score, move, user, ai, battle|
next Battle::AI::MOVE_USELESS_SCORE
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveEffectScore.copy("DoesNothingCongratulations",
"DoesNothingFailsIfNoAlly")
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveEffectScore.copy("DoesNothingCongratulations",
"DoesNothingUnusableInGravity")
#===============================================================================
#
#===============================================================================
# AddMoneyGainedFromBattle
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveEffectScore.copy("DoesNothingCongratulations",
"DoubleMoneyGainedFromBattle")
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("FailsIfNotUserFirstTurn",
proc { |move, user, ai, battle|
next user.turnCount > 0
}
)
Battle::AI::Handlers::MoveEffectScore.add("FailsIfNotUserFirstTurn",
proc { |score, move, user, ai, battle|
next score + 25 # Use it or lose it
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("FailsIfUserHasUnusedMove",
proc { |move, user, ai, battle|
has_another_move = false
has_unused_move = false
user.battler.eachMove do |m|
next if m.id == move.id
has_another_move = true
next if user.battler.movesUsed.include?(m.id)
has_unused_move = true
break
end
next !has_another_move || has_unused_move
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("FailsIfUserNotConsumedBerry",
proc { |move, user, ai, battle|
next !user.battler.belched?
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("FailsIfTargetHasNoItem",
proc { |move, user, target, ai, battle|
next !target.item || !target.item_active?
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("FailsUnlessTargetSharesTypeWithUser",
proc { |move, user, target, ai, battle|
user_types = user.pbTypes(true)
target_types = target.pbTypes(true)
next (user_types & target_types).empty?
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("FailsIfUserDamagedThisTurn",
proc { |score, move, user, target, ai, battle|
# Check whether user is faster than its foe(s) and could use this move
user_faster_count = 0
foe_faster_count = 0
ai.each_foe_battler(user.side) do |b, i|
if user.faster_than?(b)
user_faster_count += 1
else
foe_faster_count += 1
end
end
next Battle::AI::MOVE_USELESS_SCORE if user_faster_count == 0
score += 15 if foe_faster_count == 0
# Effects that make the target unlikely to act before the user
if ai.trainer.high_skill?
if !target.can_attack?
score += 15
elsif target.effects[PBEffects::Confusion] > 1 ||
target.effects[PBEffects::Attract] == user.index
score += 10
elsif target.battler.paralyzed?
score += 5
end
end
# Don't risk using this move if target is weak
if ai.trainer.has_skill_flag?("HPAware")
score -= 10 if target.hp <= target.totalhp / 2
score -= 10 if target.hp <= target.totalhp / 4
end
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("FailsIfTargetActed",
proc { |score, move, user, target, ai, battle|
# Check whether user is faster than the target and could use this move
next Battle::AI::MOVE_USELESS_SCORE if target.faster_than?(user)
# Check whether the target has any damaging moves it could use
next Battle::AI::MOVE_USELESS_SCORE if !target.check_for_move { |m| m.damagingMove? }
# Don't risk using this move if target is weak
if ai.trainer.has_skill_flag?("HPAware")
score -= 10 if target.hp <= target.totalhp / 2
score -= 10 if target.hp <= target.totalhp / 4
end
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("CrashDamageIfFailsUnusableInGravity",
proc { |score, move, user, target, ai, battle|
if user.battler.takesIndirectDamage?
score -= (0.6 * (100 - move.rough_accuracy)).to_i # -0 (100%) to -60 (1%)
end
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("StartSunWeather",
proc { |move, user, ai, battle|
next [:HarshSun, :HeavyRain, :StrongWinds, move.move.weatherType].include?(battle.field.weather)
}
)
Battle::AI::Handlers::MoveEffectScore.add("StartSunWeather",
proc { |score, move, user, ai, battle|
next Battle::AI::MOVE_USELESS_SCORE if battle.pbCheckGlobalAbility(:AIRLOCK) ||
battle.pbCheckGlobalAbility(:CLOUDNINE)
# Not worth it at lower HP
if ai.trainer.has_skill_flag?("HPAware")
score -= 10 if user.hp < user.totalhp / 2
end
if ai.trainer.high_skill? && battle.field.weather != :None
score -= ai.get_score_for_weather(battle.field.weather, user)
end
score += ai.get_score_for_weather(:Sun, user, true)
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.copy("StartSunWeather",
"StartRainWeather")
Battle::AI::Handlers::MoveEffectScore.add("StartRainWeather",
proc { |score, move, user, ai, battle|
next Battle::AI::MOVE_USELESS_SCORE if battle.pbCheckGlobalAbility(:AIRLOCK) ||
battle.pbCheckGlobalAbility(:CLOUDNINE)
# Not worth it at lower HP
if ai.trainer.has_skill_flag?("HPAware")
score -= 10 if user.hp < user.totalhp / 2
end
if ai.trainer.high_skill? && battle.field.weather != :None
score -= ai.get_score_for_weather(battle.field.weather, user)
end
score += ai.get_score_for_weather(:Rain, user, true)
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.copy("StartSunWeather",
"StartSandstormWeather")
Battle::AI::Handlers::MoveEffectScore.add("StartSandstormWeather",
proc { |score, move, user, ai, battle|
next Battle::AI::MOVE_USELESS_SCORE if battle.pbCheckGlobalAbility(:AIRLOCK) ||
battle.pbCheckGlobalAbility(:CLOUDNINE)
# Not worth it at lower HP
if ai.trainer.has_skill_flag?("HPAware")
score -= 10 if user.hp < user.totalhp / 2
end
if ai.trainer.high_skill? && battle.field.weather != :None
score -= ai.get_score_for_weather(battle.field.weather, user)
end
score += ai.get_score_for_weather(:Sandstorm, user, true)
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.copy("StartSunWeather",
"StartHailWeather")
Battle::AI::Handlers::MoveEffectScore.add("StartHailWeather",
proc { |score, move, user, ai, battle|
next Battle::AI::MOVE_USELESS_SCORE if battle.pbCheckGlobalAbility(:AIRLOCK) ||
battle.pbCheckGlobalAbility(:CLOUDNINE)
# Not worth it at lower HP
if ai.trainer.has_skill_flag?("HPAware")
score -= 10 if user.hp < user.totalhp / 2
end
if ai.trainer.high_skill? && battle.field.weather != :None
score -= ai.get_score_for_weather(battle.field.weather, user)
end
score += ai.get_score_for_weather(:Hail, user, true)
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("StartElectricTerrain",
proc { |move, user, ai, battle|
next battle.field.terrain == :Electric
}
)
Battle::AI::Handlers::MoveEffectScore.add("StartElectricTerrain",
proc { |score, move, user, ai, battle|
# Not worth it at lower HP
if ai.trainer.has_skill_flag?("HPAware")
score -= 10 if user.hp < user.totalhp / 2
end
if ai.trainer.high_skill? && battle.field.terrain != :None
score -= ai.get_score_for_terrain(battle.field.terrain, user)
end
score += ai.get_score_for_terrain(:Electric, user, true)
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("StartGrassyTerrain",
proc { |move, user, ai, battle|
next battle.field.terrain == :Grassy
}
)
Battle::AI::Handlers::MoveEffectScore.add("StartGrassyTerrain",
proc { |score, move, user, ai, battle|
# Not worth it at lower HP
if ai.trainer.has_skill_flag?("HPAware")
score -= 10 if user.hp < user.totalhp / 2
end
if ai.trainer.high_skill? && battle.field.terrain != :None
score -= ai.get_score_for_terrain(battle.field.terrain, user)
end
score += ai.get_score_for_terrain(:Grassy, user, true)
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("StartMistyTerrain",
proc { |move, user, ai, battle|
next battle.field.terrain == :Misty
}
)
Battle::AI::Handlers::MoveEffectScore.add("StartMistyTerrain",
proc { |score, move, user, ai, battle|
# Not worth it at lower HP
if ai.trainer.has_skill_flag?("HPAware")
score -= 10 if user.hp < user.totalhp / 2
end
if ai.trainer.high_skill? && battle.field.terrain != :None
score -= ai.get_score_for_terrain(battle.field.terrain, user)
end
score += ai.get_score_for_terrain(:Misty, user, true)
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("StartPsychicTerrain",
proc { |move, user, ai, battle|
next battle.field.terrain == :Psychic
}
)
Battle::AI::Handlers::MoveEffectScore.add("StartPsychicTerrain",
proc { |score, move, user, ai, battle|
# Not worth it at lower HP
if ai.trainer.has_skill_flag?("HPAware")
score -= 10 if user.hp < user.totalhp / 2
end
if ai.trainer.high_skill? && battle.field.terrain != :None
score -= ai.get_score_for_terrain(battle.field.terrain, user)
end
score += ai.get_score_for_terrain(:Psychic, user, true)
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("RemoveTerrain",
proc { |move, user, ai, battle|
next battle.field.terrain == :None
}
)
Battle::AI::Handlers::MoveEffectScore.add("RemoveTerrain",
proc { |score, move, user, ai, battle|
next score - ai.get_score_for_terrain(battle.field.terrain, user)
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("AddSpikesToFoeSide",
proc { |move, user, ai, battle|
next user.pbOpposingSide.effects[PBEffects::Spikes] >= 3
}
)
Battle::AI::Handlers::MoveEffectScore.add("AddSpikesToFoeSide",
proc { |score, move, user, ai, battle|
inBattleIndices = battle.allSameSideBattlers(user.idxOpposingSide).map { |b| b.pokemonIndex }
foe_reserves = []
battle.pbParty(user.idxOpposingSide).each_with_index do |pkmn, idxParty|
next if !pkmn || !pkmn.able? || inBattleIndices.include?(idxParty)
if ai.trainer.medium_skill?
next if pkmn.hasItem?(:HEAVYDUTYBOOTS)
next if ai.pokemon_airborne?(pkmn)
next if pkmn.hasAbility?(:MAGICGUARD)
end
foe_reserves.push(pkmn) # pkmn will be affected by Spikes
end
next Battle::AI::MOVE_USELESS_SCORE if foe_reserves.empty?
multiplier = [10, 7, 5][user.pbOpposingSide.effects[PBEffects::Spikes]]
score += [multiplier * foe_reserves.length, 30].min
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("AddToxicSpikesToFoeSide",
proc { |move, user, ai, battle|
next user.pbOpposingSide.effects[PBEffects::ToxicSpikes] >= 2
}
)
Battle::AI::Handlers::MoveEffectScore.add("AddToxicSpikesToFoeSide",
proc { |score, move, user, ai, battle|
inBattleIndices = battle.allSameSideBattlers(user.idxOpposingSide).map { |b| b.pokemonIndex }
foe_reserves = []
battle.pbParty(user.idxOpposingSide).each_with_index do |pkmn, idxParty|
next if !pkmn || !pkmn.able? || inBattleIndices.include?(idxParty)
if ai.trainer.medium_skill?
next if pkmn.hasItem?(:HEAVYDUTYBOOTS)
next if ai.pokemon_airborne?(pkmn)
next if !ai.pokemon_can_be_poisoned?(pkmn)
end
foe_reserves.push(pkmn) # pkmn will be affected by Toxic Spikes
end
next Battle::AI::MOVE_USELESS_SCORE if foe_reserves.empty?
multiplier = [8, 5][user.pbOpposingSide.effects[PBEffects::ToxicSpikes]]
score += [multiplier * foe_reserves.length, 30].min
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("AddStealthRocksToFoeSide",
proc { |move, user, ai, battle|
next user.pbOpposingSide.effects[PBEffects::StealthRock]
}
)
Battle::AI::Handlers::MoveEffectScore.add("AddStealthRocksToFoeSide",
proc { |score, move, user, ai, battle|
inBattleIndices = battle.allSameSideBattlers(user.idxOpposingSide).map { |b| b.pokemonIndex }
foe_reserves = []
battle.pbParty(user.idxOpposingSide).each_with_index do |pkmn, idxParty|
next if !pkmn || !pkmn.able? || inBattleIndices.include?(idxParty)
if ai.trainer.medium_skill?
next if pkmn.hasItem?(:HEAVYDUTYBOOTS)
next if pkmn.hasAbility?(:MAGICGUARD)
end
foe_reserves.push(pkmn) # pkmn will be affected by Stealth Rock
end
next Battle::AI::MOVE_USELESS_SCORE if foe_reserves.empty?
score += [10 * foe_reserves.length, 30].min
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("AddStickyWebToFoeSide",
proc { |move, user, ai, battle|
next user.pbOpposingSide.effects[PBEffects::StickyWeb]
}
)
Battle::AI::Handlers::MoveEffectScore.add("AddStickyWebToFoeSide",
proc { |score, move, user, ai, battle|
inBattleIndices = battle.allSameSideBattlers(user.idxOpposingSide).map { |b| b.pokemonIndex }
foe_reserves = []
battle.pbParty(user.idxOpposingSide).each_with_index do |pkmn, idxParty|
next if !pkmn || !pkmn.able? || inBattleIndices.include?(idxParty)
if ai.trainer.medium_skill?
next if pkmn.hasItem?(:HEAVYDUTYBOOTS)
next if ai.pokemon_airborne?(pkmn)
end
foe_reserves.push(pkmn) # pkmn will be affected by Sticky Web
end
next Battle::AI::MOVE_USELESS_SCORE if foe_reserves.empty?
score += [8 * foe_reserves.length, 30].min
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("SwapSideEffects",
proc { |move, user, ai, battle|
has_effect = false
2.times do |side|
effects = battle.sides[side].effects
move.move.number_effects.each do |e|
next if effects[e] == 0
has_effect = true
break
end
break if has_effect
move.move.boolean_effects.each do |e|
next if !effects[e]
has_effect = true
break
end
break if has_effect
end
next !has_effect
}
)
Battle::AI::Handlers::MoveEffectScore.add("SwapSideEffects",
proc { |score, move, user, ai, battle|
if ai.trainer.medium_skill?
good_effects = [:AuroraVeil, :LightScreen, :Mist, :Rainbow, :Reflect,
:Safeguard, :SeaOfFire, :Swamp, :Tailwind].map! { |e| PBEffects.const_get(e) }
bad_effects = [:Spikes, :StealthRock, :StickyWeb, :ToxicSpikes].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.trainer.high_skill?
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
end
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("UserMakeSubstitute",
proc { |move, user, ai, battle|
next true if user.effects[PBEffects::Substitute] > 0
next user.hp <= [user.totalhp / 4, 1].max
}
)
Battle::AI::Handlers::MoveEffectScore.add("UserMakeSubstitute",
proc { |score, move, user, ai, battle|
# Prefer more the higher the user's HP
if ai.trainer.has_skill_flag?("HPAware")
score += (10 * user.hp.to_f / user.totalhp).round
end
# Prefer if foes don't know any moves that can bypass a substitute
ai.each_foe_battler(user.side) do |b, i|
score += 5 if !b.check_for_move { |m| m.ignoresSubstitute?(b.battler) }
end
# Prefer if the user lost more than a Substitute's worth of HP from the last
# attack against it
score += 7 if user.battler.lastHPLost >= user.totalhp / 4
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveEffectScore.add("RemoveUserBindingAndEntryHazards",
proc { |score, move, user, ai, battle|
# Score for raising user's Speed
if Settings::MECHANICS_GENERATION >= 8
score = Battle::AI::Handlers.apply_move_effect_score("RaiseUserSpeed1",
score, move, user, ai, battle)
end
# Score for removing various effects
score += 10 if user.effects[PBEffects::Trapping] > 0
score += 15 if user.effects[PBEffects::LeechSeed] >= 0
if battle.pbAbleNonActiveCount(user.idxOwnSide) > 0
score += 15 if user.pbOwnSide.effects[PBEffects::Spikes] > 0
score += 15 if user.pbOwnSide.effects[PBEffects::ToxicSpikes] > 0
score += 20 if user.pbOwnSide.effects[PBEffects::StealthRock]
score += 15 if user.pbOwnSide.effects[PBEffects::StickyWeb]
end
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("AttackTwoTurnsLater",
proc { |move, user, target, ai, battle|
next battle.positions[target.index].effects[PBEffects::FutureSightCounter] > 0
}
)
Battle::AI::Handlers::MoveEffectScore.add("AttackTwoTurnsLater",
proc { |score, move, user, ai, battle|
# Future Sight tends to be wasteful if down to last Pokémon
score -= 20 if battle.pbAbleNonActiveCount(user.idxOwnSide) == 0
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("UserSwapsPositionsWithAlly",
proc { |move, user, ai, battle|
num_targets = 0
idxUserOwner = battle.pbGetOwnerIndexFromBattlerIndex(user.index)
ai.each_ally(user.side) do |b, i|
next if battle.pbGetOwnerIndexFromBattlerIndex(b.index) != idxUserOwner
next if !b.battler.near?(user.battler)
num_targets += 1
end
next num_targets != 1
}
)
Battle::AI::Handlers::MoveEffectScore.add("UserSwapsPositionsWithAlly",
proc { |score, move, user, ai, battle|
next score - 30 # Usually no point in using this
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveEffectScore.add("BurnAttackerBeforeUserActs",
proc { |score, move, user, ai, battle|
ai.each_foe_battler(user.side) do |b|
next if !b.battler.affectedByContactEffect?
next if !b.battler.pbCanBurn?(user.battler, false, move.move)
if ai.trainer.high_skill?
next if !b.check_for_move { |m| m.pbContactMove?(b.battler) }
end
score += 10 # Possible to burn
end
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveEffectScore.add("AllBattlersLoseHalfHPUserSkipsNextTurn",
proc { |score, move, user, ai, battle|
# HP halving
foe_hp_lost = 0
ally_hp_lost = 0
ai.each_battler do |b, i|
next if b.hp == 1
if b.battler.opposes?(user.battler)
foe_hp_lost += b.hp / 2
else
ally_hp_lost += b.hp / 2
end
end
score += 20 * foe_hp_lost / ally_hp_lost
score -= 20 * ally_hp_lost / foe_hp_lost
# Recharging
score = Battle::AI::Handlers.apply_move_effect_score("AttackAndSkipNextTurn",
score, move, user, ai, battle)
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveEffectScore.add("UserLosesHalfHP",
proc { |score, move, user, ai, battle|
score = Battle::AI::Handlers.apply_move_effect_score("UserLosesHalfOfTotalHP",
score, move, user, ai, battle)
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.copy("StartSunWeather",
"StartShadowSkyWeather")
Battle::AI::Handlers::MoveEffectScore.add("StartShadowSkyWeather",
proc { |score, move, user, ai, battle|
next Battle::AI::MOVE_USELESS_SCORE if battle.pbCheckGlobalAbility(:AIRLOCK) ||
battle.pbCheckGlobalAbility(:CLOUDNINE)
# Not worth it at lower HP
if ai.trainer.has_skill_flag?("HPAware")
score -= 15 if user.hp < user.totalhp / 2
end
if ai.trainer.high_skill? && battle.field.weather != :None
score -= ai.get_score_for_weather(battle.field.weather, user)
end
score += ai.get_score_for_weather(:ShadowSky, user, true)
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("RemoveAllScreensAndSafeguard",
proc { |move, user, ai, battle|
will_fail = true
battle.sides.each do |side|
will_fail = false if side.effects[PBEffects::AuroraVeil] > 0 ||
side.effects[PBEffects::LightScreen] > 0 ||
side.effects[PBEffects::Reflect] > 0 ||
side.effects[PBEffects::Safeguard] > 0
end
next will_fail
}
)
Battle::AI::Handlers::MoveEffectScore.add("RemoveAllScreensAndSafeguard",
proc { |score, move, user, ai, battle|
foe_side = user.pbOpposingSide
# Useless if the foe's side has no screens/Safeguard to remove, or if
# they'll end this round anyway
if foe_side.effects[PBEffects::AuroraVeil] <= 1 &&
foe_side.effects[PBEffects::LightScreen] <= 1 &&
foe_side.effects[PBEffects::Reflect] <= 1 &&
foe_side.effects[PBEffects::Safeguard] <= 1
next Battle::AI::MOVE_USELESS_SCORE
end
# Prefer removing opposing screens
score = Battle::AI::Handlers.apply_move_effect_score("RemoveScreens",
score, move, user, ai, battle)
# Don't prefer removing same side screens
ai.each_foe_battler(user.side) do |b, i|
score -= Battle::AI::Handlers.apply_move_effect_score("RemoveScreens",
0, move, b, ai, battle)
break
end
# Safeguard
score += 10 if foe_side.effects[PBEffects::Safeguard] > 0
score -= 10 if user.pbOwnSide.effects[PBEffects::Safeguard] > 0
next score
}
)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,531 @@
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveBasePower.add("HitTwoTimes",
proc { |power, move, user, target, ai, battle|
next power * move.move.pbNumHits(user.battler, [target.battler])
}
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("HitTwoTimes",
proc { |score, move, user, target, ai, battle|
# Prefer if the target has a Substitute and this move can break it before
# the last hit
if target.effects[PBEffects::Substitute] > 0 && !move.move.ignoresSubstitute?(user.battler)
dmg = move.rough_damage
num_hits = move.move.pbNumHits(user.battler, [target.battler])
score += 10 if target.effects[PBEffects::Substitute] < dmg * (num_hits - 1) / num_hits
end
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveBasePower.copy("HitTwoTimes",
"HitTwoTimesPoisonTarget")
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("HitTwoTimesPoisonTarget",
proc { |score, move, user, target, ai, battle|
# Score for hitting multiple times
score = Battle::AI::Handlers.apply_move_effect_against_target_score("HitTwoTimes",
score, move, user, target, ai, battle)
# Score for poisoning
poison_score = Battle::AI::Handlers.apply_move_effect_against_target_score("PoisonTarget",
0, move, user, target, ai, battle)
score += poison_score if poison_score != Battle::AI::MOVE_USELESS_SCORE
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveBasePower.copy("HitTwoTimes",
"HitTwoTimesFlinchTarget")
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("HitTwoTimesFlinchTarget",
proc { |score, move, user, target, ai, battle|
# Score for hitting multiple times
score = Battle::AI::Handlers.apply_move_effect_against_target_score("HitTwoTimes",
score, move, user, target, ai, battle)
# Score for flinching
score = Battle::AI::Handlers.apply_move_effect_against_target_score("FlinchTarget",
score, move, user, target, ai, battle)
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveBasePower.add("HitTwoTimesTargetThenTargetAlly",
proc { |power, move, user, target, ai, battle|
next power * 2
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveBasePower.add("HitThreeTimesPowersUpWithEachHit",
proc { |power, move, user, target, ai, battle|
next power * 6 # Hits do x1, x2, x3 ret in turn, for x6 in total
}
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("HitThreeTimesPowersUpWithEachHit",
proc { |score, move, user, target, ai, battle|
# Prefer if the target has a Substitute and this move can break it before
# the last hit
if target.effects[PBEffects::Substitute] > 0 && !move.move.ignoresSubstitute?(user.battler)
dmg = move.rough_damage
score += 10 if target.effects[PBEffects::Substitute] < dmg / 2
end
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveBasePower.copy("HitTwoTimes",
"HitThreeTimesAlwaysCriticalHit")
Battle::AI::Handlers::MoveEffectAgainstTargetScore.copy("HitTwoTimes",
"HitThreeTimesAlwaysCriticalHit")
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveBasePower.add("HitTwoToFiveTimes",
proc { |power, move, user, target, ai, battle|
next power * 5 if user.has_active_ability?(:SKILLLINK)
next power * 31 / 10 # Average damage dealt
}
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("HitTwoToFiveTimes",
proc { |score, move, user, target, ai, battle|
# Prefer if the target has a Substitute and this move can break it before
# the last/third hit
if target.effects[PBEffects::Substitute] > 0 && !move.move.ignoresSubstitute?(user.battler)
dmg = move.rough_damage
num_hits = (user.has_active_ability?(:SKILLLINK)) ? 5 : 3 # 3 is about average
score += 10 if target.effects[PBEffects::Substitute] < dmg * (num_hits - 1) / num_hits
end
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveBasePower.add("HitTwoToFiveTimesOrThreeForAshGreninja",
proc { |power, move, user, target, ai, battle|
if user.battler.isSpecies?(:GRENINJA) && user.battler.form == 2
next move.move.pbBaseDamage(power, user.battler, target.battler) * move.move.pbNumHits(user.battler, [target.battler])
end
next power * 5 if user.has_active_ability?(:SKILLLINK)
next power * 31 / 10 # Average damage dealt
}
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.copy("HitTwoToFiveTimes",
"HitTwoToFiveTimesOrThreeForAshGreninja")
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveBasePower.copy("HitTwoToFiveTimes",
"HitTwoToFiveTimesRaiseUserSpd1LowerUserDef1")
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("HitTwoToFiveTimesRaiseUserSpd1LowerUserDef1",
proc { |score, move, user, target, ai, battle|
# Score for being a multi-hit attack
score = Battle::AI::Handlers.apply_move_effect_against_target_score("HitTwoToFiveTimes",
score, move, user, target, ai, battle)
# Score for user's stat changes
score = ai.get_score_for_target_stat_raise(score, user, [:SPEED, 1], false)
score = ai.get_score_for_target_stat_drop(score, user, [:DEFENSE, 1], false)
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("HitOncePerUserTeamMember",
proc { |move, user, ai, battle|
will_fail = true
battle.eachInTeamFromBattlerIndex(user.index) do |pkmn, i|
next if !pkmn.able? || pkmn.status != :NONE
will_fail = false
break
end
next will_fail
}
)
Battle::AI::Handlers::MoveBasePower.add("HitOncePerUserTeamMember",
proc { |power, move, user, target, ai, battle|
ret = 0
battle.eachInTeamFromBattlerIndex(user.index) do |pkmn, _i|
ret += 5 + (pkmn.baseStats[:ATTACK] / 10) if pkmn.able? && pkmn.status == :NONE
end
next ret
}
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("HitOncePerUserTeamMember",
proc { |score, move, user, target, ai, battle|
# Prefer if the target has a Substitute and this move can break it before
# the last hit
if target.effects[PBEffects::Substitute] > 0 && !move.move.ignoresSubstitute?(user.battler)
dmg = move.rough_damage
num_hits = 0
battle.eachInTeamFromBattlerIndex(user.index) do |pkmn, _i|
num_hits += 1 if pkmn.able? && pkmn.status == :NONE
end
score += 10 if target.effects[PBEffects::Substitute] < dmg * (num_hits - 1) / num_hits
end
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveEffectScore.add("AttackAndSkipNextTurn",
proc { |score, move, user, ai, battle|
# Don't prefer if user is at a high HP (treat this move as a last resort)
if ai.trainer.has_skill_flag?("HPAware")
score -= 10 if user.hp >= user.totalhp / 2
end
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TwoTurnAttack",
proc { |score, move, user, target, ai, battle|
# Power Herb makes this a 1 turn move, the same as a move with no effect
next score if user.has_active_item?(:POWERHERB)
# Treat as a failure if user has Truant (the charging turn has no effect)
next Battle::AI::MOVE_USELESS_SCORE if user.has_active_ability?(:TRUANT)
# Useless if user will faint from EoR damage before finishing this attack
next Battle::AI::MOVE_USELESS_SCORE if user.rough_end_of_round_damage >= user.hp
# Don't prefer because it uses up two turns
score -= 10
# Don't prefer if user is at a low HP (time is better spent on quicker moves)
if ai.trainer.has_skill_flag?("HPAware")
score -= 10 if user.hp < user.totalhp / 2
end
# Don't prefer if target has a protecting move
if ai.trainer.high_skill? && !(user.has_active_ability?(:UNSEENFIST) && move.move.contactMove?)
has_protect_move = false
if move.pbTarget(user).num_targets > 1 &&
(Settings::MECHANICS_GENERATION >= 7 || move.damagingMove?)
if target.has_move_with_function?("ProtectUserSideFromMultiTargetDamagingMoves")
has_protect_move = true
end
end
if move.move.canProtectAgainst?
if target.has_move_with_function?("ProtectUser",
"ProtectUserFromTargetingMovesSpikyShield",
"ProtectUserBanefulBunker")
has_protect_move = true
end
if move.damagingMove?
# NOTE: Doesn't check for Mat Block because it only works on its
# user's first turn in battle, so it can't be used in response
# to this move charging up.
if target.has_move_with_function?("ProtectUserFromDamagingMovesKingsShield",
"ProtectUserFromDamagingMovesObstruct")
has_protect_move = true
end
end
if move.rough_priority(user) > 0
if target.has_move_with_function?("ProtectUserSideFromPriorityMoves")
has_protect_move = true
end
end
end
score -= 20 if has_protect_move
end
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveBasePower.add("TwoTurnAttackOneTurnInSun",
proc { |power, move, user, target, ai, battle|
next move.move.pbBaseDamageMultiplier(power, user.battler, target.battler)
}
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TwoTurnAttackOneTurnInSun",
proc { |score, move, user, target, ai, battle|
# In sunny weather this a 1 turn move, the same as a move with no effect
next score if [:Sun, :HarshSun].include?(user.battler.effectiveWeather)
# Score for being a two turn attack
next Battle::AI::Handlers.apply_move_effect_against_target_score("TwoTurnAttack",
score, move, user, target, ai, battle)
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TwoTurnAttackParalyzeTarget",
proc { |score, move, user, target, ai, battle|
# Score for being a two turn attack
score = Battle::AI::Handlers.apply_move_effect_against_target_score("TwoTurnAttack",
score, move, user, target, ai, battle)
next score if score == Battle::AI::MOVE_USELESS_SCORE
# Score for paralysing
score = Battle::AI::Handlers.apply_move_effect_against_target_score("ParalyzeTarget",
score, move, user, target, ai, battle)
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TwoTurnAttackBurnTarget",
proc { |score, move, user, target, ai, battle|
# Score for being a two turn attack
score = Battle::AI::Handlers.apply_move_effect_against_target_score("TwoTurnAttack",
score, move, user, target, ai, battle)
next score if score == Battle::AI::MOVE_USELESS_SCORE
# Score for burning
score = Battle::AI::Handlers.apply_move_effect_against_target_score("BurnTarget",
score, move, user, target, ai, battle)
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TwoTurnAttackFlinchTarget",
proc { |score, move, user, target, ai, battle|
# Score for being a two turn attack
score = Battle::AI::Handlers.apply_move_effect_against_target_score("TwoTurnAttack",
score, move, user, target, ai, battle)
next score if score == Battle::AI::MOVE_USELESS_SCORE
# Score for flinching
score = Battle::AI::Handlers.apply_move_effect_against_target_score("FlinchTarget",
score, move, user, target, ai, battle)
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.copy("RaiseUserAtkDef1",
"TwoTurnAttackRaiseUserSpAtkSpDefSpd2")
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TwoTurnAttackRaiseUserSpAtkSpDefSpd2",
proc { |score, move, user, target, ai, battle|
# Score for being a two turn attack
score = Battle::AI::Handlers.apply_move_effect_against_target_score("TwoTurnAttack",
score, move, user, target, ai, battle)
next score if score == Battle::AI::MOVE_USELESS_SCORE
# Score for raising user's stats
score = ai.get_score_for_target_stat_raise(score, user, move.move.statUp)
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TwoTurnAttackChargeRaiseUserDefense1",
proc { |score, move, user, target, ai, battle|
# Score for being a two turn attack
score = Battle::AI::Handlers.apply_move_effect_against_target_score("TwoTurnAttack",
score, move, user, target, ai, battle)
next score if score == Battle::AI::MOVE_USELESS_SCORE
# Score for raising the user's stat
score = Battle::AI::Handlers.apply_move_effect_score("RaiseUserDefense1",
score, move, user, ai, battle)
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TwoTurnAttackChargeRaiseUserSpAtk1",
proc { |score, move, user, target, ai, battle|
# Score for being a two turn attack
score = Battle::AI::Handlers.apply_move_effect_against_target_score("TwoTurnAttack",
score, move, user, target, ai, battle)
next score if score == Battle::AI::MOVE_USELESS_SCORE
# Score for raising the user's stat
score = Battle::AI::Handlers.apply_move_effect_score("RaiseUserSpAtk1",
score, move, user, ai, battle)
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TwoTurnAttackInvulnerableUnderground",
proc { |score, move, user, target, ai, battle|
# Score for being a two turn attack
score = Battle::AI::Handlers.apply_move_effect_against_target_score("TwoTurnAttack",
score, move, user, target, ai, battle)
next score if score == Battle::AI::MOVE_USELESS_SCORE
# Score for being semi-invulnerable underground
ai.each_foe_battler(user.side) do |b, i|
if b.check_for_move { |m| m.hitsDiggingTargets? }
score -= 10
else
score += 8
end
end
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TwoTurnAttackInvulnerableUnderwater",
proc { |score, move, user, target, ai, battle|
# Score for being a two turn attack
score = Battle::AI::Handlers.apply_move_effect_against_target_score("TwoTurnAttack",
score, move, user, target, ai, battle)
next score if score == Battle::AI::MOVE_USELESS_SCORE
# Score for being semi-invulnerable underwater
ai.each_foe_battler(user.side) do |b, i|
if b.check_for_move { |m| m.hitsDivingTargets? }
score -= 10
else
score += 8
end
end
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TwoTurnAttackInvulnerableInSky",
proc { |score, move, user, target, ai, battle|
# Score for being a two turn attack
score = Battle::AI::Handlers.apply_move_effect_against_target_score("TwoTurnAttack",
score, move, user, target, ai, battle)
next score if score == Battle::AI::MOVE_USELESS_SCORE
# Score for being semi-invulnerable in the sky
ai.each_foe_battler(user.side) do |b, i|
if b.check_for_move { |m| m.hitsFlyingTargets? }
score -= 10
else
score += 8
end
end
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TwoTurnAttackInvulnerableInSkyParalyzeTarget",
proc { |score, move, user, target, ai, battle|
# Score for being a two turn attack and semi-invulnerable in the sky
score = Battle::AI::Handlers.apply_move_effect_against_target_score("TwoTurnAttackInvulnerableInSky",
score, move, user, target, ai, battle)
next score if score == Battle::AI::MOVE_USELESS_SCORE
# Score for paralyzing the target
score = Battle::AI::Handlers.apply_move_effect_against_target_score("ParalyzeTarget",
score, move, user, target, ai, battle)
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("TwoTurnAttackInvulnerableInSkyTargetCannotAct",
proc { |move, user, target, ai, battle|
next true if !target.opposes?(user)
next true if target.effects[PBEffects::Substitute] > 0 && !move.move.ignoresSubstitute?(user.battler)
next true if target.has_type?(:FLYING)
next true if Settings::MECHANICS_GENERATION >= 6 && target.battler.pbWeight >= 2000 # 200.0kg
next true if target.battler.semiInvulnerable? || target.effects[PBEffects::SkyDrop] >= 0
next false
}
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.copy("TwoTurnAttackInvulnerableInSky",
"TwoTurnAttackInvulnerableInSkyTargetCannotAct")
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TwoTurnAttackInvulnerableRemoveProtections",
proc { |score, move, user, target, ai, battle|
# Score for being a two turn attack
score = Battle::AI::Handlers.apply_move_effect_against_target_score("TwoTurnAttack",
score, move, user, target, ai, battle)
next score if score == Battle::AI::MOVE_USELESS_SCORE
# Score for being invulnerable
score += 8
# Score for removing protections
score = Battle::AI::Handlers.apply_move_effect_against_target_score("RemoveProtections",
score, move, user, target, ai, battle)
next score
}
)
#===============================================================================
#
#===============================================================================
# MultiTurnAttackPreventSleeping
#===============================================================================
#
#===============================================================================
# MultiTurnAttackConfuseUserAtEnd
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveBasePower.add("MultiTurnAttackPowersUpEachTurn",
proc { |power, move, user, target, ai, battle|
# NOTE: The * 2 (roughly) incorporates the higher damage done in subsequent
# rounds. It is nearly the average damage this move will do per round,
# assuming it hits for 3 rounds (hoping for hits in all 5 rounds is
# optimistic).
next move.move.pbBaseDamage(power, user.battler, target.battler) * 2
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveBasePower.add("MultiTurnAttackBideThenReturnDoubleDamage",
proc { |power, move, user, target, ai, battle|
next 40 # Representative value
}
)
Battle::AI::Handlers::MoveEffectScore.add("MultiTurnAttackBideThenReturnDoubleDamage",
proc { |score, move, user, ai, battle|
# Useless if no foe has any damaging moves
has_damaging_move = false
ai.each_foe_battler(user.side) do |b, i|
next if b.status == :SLEEP && b.statusCount > 2
next if b.status == :FROZEN
has_damaging_move = true if b.check_for_move { |m| m.damagingMove? }
break if has_damaging_move
end
next Battle::AI::MOVE_USELESS_SCORE if !has_damaging_move
# Don't prefer if the user isn't at high HP
if ai.trainer.has_skill_flag?("HPAware")
next Battle::AI::MOVE_USELESS_SCORE if user.hp <= user.totalhp / 4
score -= 15 if user.hp <= user.totalhp / 2
score -= 8 if user.hp <= user.totalhp * 3 / 4
end
next score
}
)

View File

@@ -0,0 +1,683 @@
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("HealUserFullyAndFallAsleep",
proc { |move, user, ai, battle|
next true if !user.battler.canHeal?
next true if user.battler.asleep?
next true if !user.battler.pbCanSleep?(user.battler, false, move.move, true)
next false
}
)
Battle::AI::Handlers::MoveEffectScore.add("HealUserFullyAndFallAsleep",
proc { |score, move, user, ai, battle|
# Consider how much HP will be restored
if ai.trainer.has_skill_flag?("HPAware")
if user.hp >= user.totalhp * 0.5
score -= 10
else
score += 30 * (user.totalhp - user.hp) / user.totalhp # +15 to +30
end
end
# Check whether an existing status problem will be removed
if user.status != :NONE
score += (user.wants_status_problem?(user.status)) ? -10 : 8
end
# Check if user is happy to be asleep, e.g. can use moves while asleep
if ai.trainer.medium_skill?
score += (user.wants_status_problem?(:SLEEP)) ? 10 : -8
end
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("HealUserHalfOfTotalHP",
proc { |move, user, ai, battle|
next !user.battler.canHeal?
}
)
Battle::AI::Handlers::MoveEffectScore.add("HealUserHalfOfTotalHP",
proc { |score, move, user, ai, battle|
# Consider how much HP will be restored
if ai.trainer.has_skill_flag?("HPAware")
next score - 10 if user.hp >= user.totalhp * 0.5
score += 30 * (user.totalhp - user.hp) / user.totalhp # +15 to +30
end
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.copy("HealUserHalfOfTotalHP",
"HealUserDependingOnWeather")
Battle::AI::Handlers::MoveEffectScore.add("HealUserDependingOnWeather",
proc { |score, move, user, ai, battle|
# Consider how much HP will be restored
score = Battle::AI::Handlers.apply_move_effect_score("HealUserHalfOfTotalHP",
score, move, user, ai, battle)
case user.battler.effectiveWeather
when :Sun, :HarshSun
score += 5
when :None, :StrongWinds
else
score -= 10
end
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.copy("HealUserHalfOfTotalHP",
"HealUserDependingOnSandstorm")
Battle::AI::Handlers::MoveEffectScore.add("HealUserDependingOnSandstorm",
proc { |score, move, user, ai, battle|
# Consider how much HP will be restored
score = Battle::AI::Handlers.apply_move_effect_score("HealUserHalfOfTotalHP",
score, move, user, ai, battle)
score += 5 if user.battler.effectiveWeather == :Sandstorm
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.copy("HealUserHalfOfTotalHP",
"HealUserHalfOfTotalHPLoseFlyingTypeThisTurn")
Battle::AI::Handlers::MoveEffectScore.add("HealUserHalfOfTotalHPLoseFlyingTypeThisTurn",
proc { |score, move, user, ai, battle|
# Consider how much HP will be restored
score = Battle::AI::Handlers.apply_move_effect_score("HealUserHalfOfTotalHP",
score, move, user, ai, battle)
# User loses the Flying type this round
# NOTE: Not worth considering and scoring for.
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("CureTargetStatusHealUserHalfOfTotalHP",
proc { |move, user, target, ai, battle|
next !user.battler.canHeal? || target.status == :NONE
}
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("CureTargetStatusHealUserHalfOfTotalHP",
proc { |score, move, user, target, ai, battle|
# Consider how much HP will be restored
score = Battle::AI::Handlers.apply_move_effect_score("HealUserHalfOfTotalHP",
score, move, user, ai, battle)
# Will cure target's status
score += (target.wants_status_problem?(target.status)) ? 10 : -8
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("HealUserByTargetAttackLowerTargetAttack1",
proc { |move, user, target, ai, battle|
if !battle.moldBreaker && target.has_active_ability?(:CONTRARY)
next target.statStageAtMax?(:ATTACK)
end
next target.statStageAtMin?(:ATTACK)
}
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("HealUserByTargetAttackLowerTargetAttack1",
proc { |score, move, user, target, ai, battle|
# Check whether lowering the target's Attack will have any impact
if ai.trainer.medium_skill?
score = ai.get_score_for_target_stat_drop(score, target, move.move.statDown)
end
# Healing the user
if target.has_active_ability?(:LIQUIDOOZE)
score -= 20
elsif user.battler.canHeal?
score += 5 if user.has_active_item?(:BIGROOT)
if ai.trainer.has_skill_flag?("HPAware")
# Consider how much HP will be restored
heal_amt = target.rough_stat(:ATTACK)
heal_amt *= 1.3 if user.has_active_item?(:BIGROOT)
heal_amt = [heal_amt, user.totalhp - user.hp].min
if heal_amt > user.totalhp * 0.3 # Only modify the score if it'll heal a decent amount
if user.hp < user.totalhp * 0.5
score += 20 * (user.totalhp - user.hp) / user.totalhp # +10 to +20
end
score += 20 * heal_amt / user.totalhp # +6 to +20
end
end
end
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("HealUserByHalfOfDamageDone",
proc { |score, move, user, target, ai, battle|
rough_dmg = move.rough_damage
if target.has_active_ability?(:LIQUIDOOZE)
score -= 20 if rough_dmg < target.hp
elsif user.battler.canHeal?
score += 5 if user.has_active_item?(:BIGROOT)
if ai.trainer.has_skill_flag?("HPAware")
# Consider how much HP will be restored
heal_amt = rough_dmg / 2
heal_amt *= 1.3 if user.has_active_item?(:BIGROOT)
heal_amt = [heal_amt, user.totalhp - user.hp].min
if heal_amt > user.totalhp * 0.3 # Only modify the score if it'll heal a decent amount
if user.hp < user.totalhp * 0.5
score += 20 * (user.totalhp - user.hp) / user.totalhp # +10 to +20
end
score += 20 * heal_amt / user.totalhp # +6 to +20
end
end
end
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("HealUserByHalfOfDamageDoneIfTargetAsleep",
proc { |move, user, target, ai, battle|
next !target.battler.asleep?
}
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.copy("HealUserByHalfOfDamageDone",
"HealUserByHalfOfDamageDoneIfTargetAsleep")
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("HealUserByThreeQuartersOfDamageDone",
proc { |score, move, user, target, ai, battle|
rough_dmg = move.rough_damage
if target.has_active_ability?(:LIQUIDOOZE)
score -= 20 if rough_dmg < target.hp
elsif user.battler.canHeal?
score += 5 if user.has_active_item?(:BIGROOT)
if ai.trainer.has_skill_flag?("HPAware")
# Consider how much HP will be restored
heal_amt = rough_dmg * 0.75
heal_amt *= 1.3 if user.has_active_item?(:BIGROOT)
heal_amt = [heal_amt, user.totalhp - user.hp].min
if heal_amt > user.totalhp * 0.3 # Only modify the score if it'll heal a decent amount
if user.hp < user.totalhp * 0.5
score += 20 * (user.totalhp - user.hp) / user.totalhp # +10 to +20
end
score += 20 * heal_amt / user.totalhp # +6 to +20
end
end
end
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("HealUserAndAlliesQuarterOfTotalHP",
proc { |move, user, target, ai, battle|
next !target.battler.canHeal?
}
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("HealUserAndAlliesQuarterOfTotalHP",
proc { |score, move, user, target, ai, battle|
next score if !target.battler.canHeal?
# Consider how much HP will be restored
if ai.trainer.has_skill_flag?("HPAware")
if target.hp >= target.totalhp * 0.75
score -= 5
else
score += 15 * (target.totalhp - target.hp) / target.totalhp # +3 to +15
end
end
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("HealUserAndAlliesQuarterOfTotalHPCureStatus",
proc { |move, user, target, ai, battle|
next !target.battler.canHeal? && target.status == :NONE
}
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("HealUserAndAlliesQuarterOfTotalHPCureStatus",
proc { |score, move, user, target, ai, battle|
# Consider how much HP will be restored
score = Battle::AI::Handlers.apply_move_effect_score("HealUserAndAlliesQuarterOfTotalHP",
score, move, user, ai, battle)
# Check whether an existing status problem will be removed
if target.status != :NONE
score += (target.wants_status_problem?(target.status)) ? -10 : 10
end
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("HealTargetHalfOfTotalHP",
proc { |move, user, target, ai, battle|
next !target.battler.canHeal?
}
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("HealTargetHalfOfTotalHP",
proc { |score, move, user, target, ai, battle|
next Battle::AI::MOVE_USELESS_SCORE if target.opposes?(user)
# Consider how much HP will be restored
if ai.trainer.has_skill_flag?("HPAware")
if target.hp >= target.totalhp * 0.5
score -= 10
else
heal_amt = target.totalhp * 0.5
heal_amt = target.totalhp * 0.75 if move.move.pulseMove? &&
user.has_active_ability?(:MEGALAUNCHER)
heal_amt = [heal_amt, target.totalhp - target.hp].min
score += 20 * (target.totalhp - target.hp) / target.totalhp # +10 to +20
score += 20 * heal_amt / target.totalhp # +10 or +15
end
end
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.copy("HealTargetHalfOfTotalHP",
"HealTargetDependingOnGrassyTerrain")
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("HealTargetDependingOnGrassyTerrain",
proc { |score, move, user, target, ai, battle|
next Battle::AI::MOVE_USELESS_SCORE if user.opposes?(target)
# Consider how much HP will be restored
if ai.trainer.has_skill_flag?("HPAware")
if target.hp >= target.totalhp * 0.5
score -= 10
else
heal_amt = target.totalhp * 0.5
heal_amt = (target.totalhp * 2 / 3.0).round if battle.field.terrain == :Grassy
heal_amt = [heal_amt, target.totalhp - target.hp].min
score += 20 * (target.totalhp - target.hp) / target.totalhp # +10 to +20
score += 20 * heal_amt / target.totalhp # +10 or +13
end
end
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("HealUserPositionNextTurn",
proc { |move, user, ai, battle|
next battle.positions[user.index].effects[PBEffects::Wish] > 0
}
)
Battle::AI::Handlers::MoveEffectScore.add("HealUserPositionNextTurn",
proc { |score, move, user, ai, battle|
# Consider how much HP will be restored
if ai.trainer.has_skill_flag?("HPAware")
if user.hp >= user.totalhp * 0.5
score -= 10
else
score += 20 * (user.totalhp - user.hp) / user.totalhp # +10 to +20
end
end
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("StartHealUserEachTurn",
proc { |move, user, ai, battle|
next user.effects[PBEffects::AquaRing]
}
)
Battle::AI::Handlers::MoveEffectScore.add("StartHealUserEachTurn",
proc { |score, move, user, ai, battle|
score += 15
score += 5 if user.has_active_item?(:BIGROOT)
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("StartHealUserEachTurnTrapUserInBattle",
proc { |move, user, ai, battle|
next user.effects[PBEffects::Ingrain]
}
)
Battle::AI::Handlers::MoveEffectScore.add("StartHealUserEachTurnTrapUserInBattle",
proc { |score, move, user, ai, battle|
score += 8
score += 15 if user.turnCount < 2
score += 5 if user.has_active_item?(:BIGROOT)
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("StartDamageTargetEachTurnIfTargetAsleep",
proc { |move, user, target, ai, battle|
next !target.battler.asleep? || target.effects[PBEffects::Nightmare]
}
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("StartDamageTargetEachTurnIfTargetAsleep",
proc { |score, move, user, target, ai, battle|
next Battle::AI::MOVE_USELESS_SCORE if target.statusCount <= 1
next score + 8 * target.statusCount
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("StartLeechSeedTarget",
proc { |move, user, target, ai, battle|
next true if target.effects[PBEffects::LeechSeed] >= 0
next true if target.has_type?(:GRASS) || !target.battler.takesIndirectDamage?
next false
}
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("StartLeechSeedTarget",
proc { |score, move, user, target, ai, battle|
score += 15
# Prefer early on
score += 10 if user.turnCount < 2
if ai.trainer.medium_skill?
# Prefer if the user has no damaging moves
score += 10 if !user.check_for_move { |m| m.damagingMove? }
# Prefer if the target can't switch out to remove its seeding
score += 8 if !battle.pbCanChooseNonActive?(target.index)
# Don't prefer if the leeched HP will hurt the user
score -= 20 if target.has_active_ability?([:LIQUIDOOZE])
end
if ai.trainer.high_skill?
# Prefer if user can stall while damage is dealt
if user.check_for_move { |m| m.is_a?(Battle::Move::ProtectMove) }
score += 10
end
# Don't prefer if target can remove the seed
if target.has_move_with_function?("RemoveUserBindingAndEntryHazards")
score -= 15
end
end
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveEffectScore.add("UserLosesHalfOfTotalHP",
proc { |score, move, user, ai, battle|
score -= 15 # User will lose 50% HP, don't prefer this move
if ai.trainer.has_skill_flag?("HPAware")
score += 15 if user.hp >= user.totalhp * 0.75 # User has HP to spare
score += 15 if user.hp <= user.totalhp * 0.25 # User is near fainting anyway; suicide
end
if ai.trainer.high_skill?
reserves = battle.pbAbleNonActiveCount(user.idxOwnSide)
foes = battle.pbAbleNonActiveCount(user.idxOpposingSide)
if reserves == 0 # AI is down to its last Pokémon
score += 30 # => Go out with a bang
elsif foes == 0 # Foe is down to their last Pokémon, AI has reserves
score += 20 # => Go for the kill
end
end
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("UserLosesHalfOfTotalHPExplosive",
proc { |move, user, ai, battle|
next !battle.moldBreaker && battle.pbCheckGlobalAbility(:DAMP)
}
)
Battle::AI::Handlers::MoveEffectScore.copy("UserLosesHalfOfTotalHP",
"UserLosesHalfOfTotalHPExplosive")
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.copy("UserLosesHalfOfTotalHPExplosive",
"UserFaintsExplosive")
Battle::AI::Handlers::MoveEffectScore.add("UserFaintsExplosive",
proc { |score, move, user, ai, battle|
score -= 20 # User will faint, don't prefer this move
if ai.trainer.has_skill_flag?("HPAware")
score -= 10 if user.hp >= user.totalhp * 0.5
score += 20 if user.hp <= user.totalhp * 0.25 # User is near fainting anyway; suicide
end
if ai.trainer.high_skill?
reserves = battle.pbAbleNonActiveCount(user.idxOwnSide)
foes = battle.pbAbleNonActiveCount(user.idxOpposingSide)
if reserves == 0 # AI is down to its last Pokémon
score += 30 # => Go out with a bang
elsif foes == 0 # Foe is down to their last Pokémon, AI has reserves
score += 20 # => Go for the kill
end
end
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.copy("UserFaintsExplosive",
"UserFaintsPowersUpInMistyTerrainExplosive")
Battle::AI::Handlers::MoveBasePower.add("UserFaintsPowersUpInMistyTerrainExplosive",
proc { |power, move, user, target, ai, battle|
power = power * 3 / 2 if battle.field.terrain == :Misty
next power
}
)
Battle::AI::Handlers::MoveEffectScore.copy("UserFaintsExplosive",
"UserFaintsPowersUpInMistyTerrainExplosive")
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveBasePower.add("UserFaintsFixedDamageUserHP",
proc { |power, move, user, target, ai, battle|
next user.hp
}
)
Battle::AI::Handlers::MoveEffectScore.copy("UserFaintsExplosive",
"UserFaintsFixedDamageUserHP")
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("UserFaintsLowerTargetAtkSpAtk2",
proc { |score, move, user, target, ai, battle|
score -= 20 # User will faint, don't prefer this move
# Check the impact of lowering the target's stats
score = ai.get_score_for_target_stat_drop(score, target, move.move.statDown)
next score if score == Battle::AI::MOVE_USELESS_SCORE
# Score for the user fainting
if ai.trainer.has_skill_flag?("HPAware")
score -= 10 if user.hp >= user.totalhp * 0.5
score += 20 if user.hp <= user.totalhp * 0.25 # User is near fainting anyway; suicide
end
if ai.trainer.high_skill?
reserves = battle.pbAbleNonActiveCount(user.idxOwnSide)
foes = battle.pbAbleNonActiveCount(user.idxOpposingSide)
if reserves > 0 && foes == 0 # Foe is down to their last Pokémon, AI has reserves
score += 20 # => Can afford to lose this Pokémon
end
end
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("UserFaintsHealAndCureReplacement",
proc { |move, user, ai, battle|
next !battle.pbCanChooseNonActive?(user.index)
}
)
Battle::AI::Handlers::MoveEffectScore.add("UserFaintsHealAndCureReplacement",
proc { |score, move, user, ai, battle|
score -= 20 # User will faint, don't prefer this move
# Check whether the replacement user needs healing, and don't make the below
# calculations if not
if ai.trainer.medium_skill?
need_healing = false
battle.eachInTeamFromBattlerIndex(user.index) do |pkmn, party_index|
next if pkmn.hp >= pkmn.totalhp * 0.75 && pkmn.status == :NONE
need_healing = true
break
end
next Battle::AI::MOVE_USELESS_SCORE if !need_healing
score += 10
end
if ai.trainer.has_skill_flag?("HPAware")
score -= 10 if user.hp >= user.totalhp * 0.5
score += 20 if user.hp <= user.totalhp * 0.25 # User is near fainting anyway; suicide
end
if ai.trainer.high_skill?
reserves = battle.pbAbleNonActiveCount(user.idxOwnSide)
foes = battle.pbAbleNonActiveCount(user.idxOpposingSide)
if reserves > 0 && foes == 0 # Foe is down to their last Pokémon, AI has reserves
score += 20 # => Can afford to lose this Pokémon
end
end
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.copy("UserFaintsHealAndCureReplacement",
"UserFaintsHealAndCureReplacementRestorePP")
Battle::AI::Handlers::MoveEffectScore.copy("UserFaintsHealAndCureReplacement",
"UserFaintsHealAndCureReplacementRestorePP")
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("StartPerishCountsForAllBattlers",
proc { |move, user, target, ai, battle|
next true if target.effects[PBEffects::PerishSong] > 0
next false if !target.ability_active?
next Battle::AbilityEffects.triggerMoveImmunity(target.ability, user.battler, target.battler,
move.move, move.rough_type, battle, false)
}
)
Battle::AI::Handlers::MoveEffectScore.add("StartPerishCountsForAllBattlers",
proc { |score, move, user, ai, battle|
score -= 15
# Check which battlers will be affected by this move
if ai.trainer.medium_skill?
allies_affected = 0
foes_affected = 0
foes_with_high_hp = 0
ai.each_battler do |b|
next if Battle::AI::Handlers.move_will_fail_against_target?("StartPerishCountsForAllBattlers",
move, user, b, ai, battle)
if b.opposes?(user)
foes_affected += 1
foes_with_high_hp += 1 if b.hp >= b.totalhp * 0.75
else
allies_affected += 1
end
end
next Battle::AI::MOVE_USELESS_SCORE if foes_affected == 0
score += 15 if allies_affected == 0 # No downside for user; cancel out inherent negative score
score -= 15 * allies_affected
score += 20 * foes_affected
score += 10 * foes_with_high_hp if ai.trainer.has_skill_flag?("HPAware")
end
if ai.trainer.high_skill?
reserves = battle.pbAbleNonActiveCount(user.idxOwnSide)
foes = battle.pbAbleNonActiveCount(user.idxOpposingSide)
if foes == 0 # Foe is down to their last Pokémon, can't lose Perish count
score += 25 # => Want to auto-win in 3 turns
elsif reserves == 0 # AI is down to its last Pokémon, can't lose Perish count
score -= 15 # => Don't want to auto-lose in 3 turns
end
end
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("AttackerFaintsIfUserFaints",
proc { |move, user, ai, battle|
next Settings::MECHANICS_GENERATION >= 7 && user.effects[PBEffects::DestinyBondPrevious]
}
)
Battle::AI::Handlers::MoveEffectScore.add("AttackerFaintsIfUserFaints",
proc { |score, move, user, ai, battle|
score -= 25
# Check whether user is faster than its foe(s) and could use this move
user_faster_count = 0
ai.each_foe_battler(user.side) do |b, i|
user_faster_count += 1 if user.faster_than?(b)
end
next score if user_faster_count == 0 # Move will almost certainly have no effect
score += 7 * user_faster_count
# Prefer this move at lower user HP
if ai.trainer.has_skill_flag?("HPAware")
score += 20 if user.hp <= user.totalhp * 0.4
score += 10 if user.hp <= user.totalhp * 0.25
score += 15 if user.hp <= user.totalhp * 0.1
end
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveEffectScore.add("SetAttackerMovePPTo0IfUserFaints",
proc { |score, move, user, ai, battle|
score -= 25
# Check whether user is faster than its foe(s) and could use this move
user_faster_count = 0
ai.each_foe_battler(user.side) do |b, i|
user_faster_count += 1 if user.faster_than?(b)
end
next score if user_faster_count == 0 # Move will almost certainly have no effect
score += 7 * user_faster_count
# Prefer this move at lower user HP (not as preferred as Destiny Bond, though)
if ai.trainer.has_skill_flag?("HPAware")
score += 20 if user.hp <= user.totalhp * 0.4
score += 10 if user.hp <= user.totalhp * 0.25
score += 15 if user.hp <= user.totalhp * 0.1
end
next score
}
)

View File

@@ -0,0 +1,376 @@
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("UserTakesTargetItem",
proc { |score, move, user, target, ai, battle|
next score if user.wild? || user.item
next score if !target.item || target.battler.unlosableItem?(target.item)
next score if user.battler.unlosableItem?(target.item)
next score if target.effects[PBEffects::Substitute] > 0
next score if target.has_active_ability?(:STICKYHOLD) && !battle.moldBreaker
# User can steal the target's item; score it
user_item_preference = user.wants_item?(target.item_id)
user_no_item_preference = user.wants_item?(:NONE)
user_diff = user_item_preference - user_no_item_preference
user_diff = 0 if !user.item_active?
target_item_preference = target.wants_item?(target.item_id)
target_no_item_preference = target.wants_item?(:NONE)
target_diff = target_no_item_preference - target_item_preference
target_diff = 0 if !target.item_active?
score += user_diff * 4
score -= target_diff * 4
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("TargetTakesUserItem",
proc { |move, user, target, ai, battle|
next true if !user.item || user.battler.unlosableItem?(user.item)
next true if target.item || target.battler.unlosableItem?(user.item)
next false
}
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TargetTakesUserItem",
proc { |score, move, user, target, ai, battle|
user_item_preference = user.wants_item?(user.item_id)
user_no_item_preference = user.wants_item?(:NONE)
user_diff = user_no_item_preference - user_item_preference
user_diff = 0 if !user.item_active?
target_item_preference = target.wants_item?(user.item_id)
target_no_item_preference = target.wants_item?(:NONE)
target_diff = target_item_preference - target_no_item_preference
target_diff = 0 if !target.item_active?
score += user_diff * 4
score -= target_diff * 4
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("UserTargetSwapItems",
proc { |move, user, target, ai, battle|
next true if user.wild?
next true if !user.item && !target.item
next true if user.battler.unlosableItem?(user.item) || user.battler.unlosableItem?(target.item)
next true if target.battler.unlosableItem?(target.item) || target.battler.unlosableItem?(user.item)
next true if target.has_active_ability?(:STICKYHOLD) && !battle.moldBreaker
next false
}
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("UserTargetSwapItems",
proc { |score, move, user, target, ai, battle|
user_new_item_preference = user.wants_item?(target.item_id)
user_old_item_preference = user.wants_item?(user.item_id)
user_diff = user_new_item_preference - user_old_item_preference
user_diff = 0 if !user.item_active?
target_new_item_preference = target.wants_item?(user.item_id)
target_old_item_preference = target.wants_item?(target.item_id)
target_diff = target_new_item_preference - target_old_item_preference
target_diff = 0 if !target.item_active?
score += user_diff * 4
score -= target_diff * 4
# Don't prefer if user used this move in the last round
score -= 15 if user.battler.lastMoveUsed &&
GameData::Move.exists?(user.battler.lastMoveUsed) &&
GameData::Move.get(user.battler.lastMoveUsed).function_code == move.function
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("RestoreUserConsumedItem",
proc { |move, user, ai, battle|
next !user.battler.recycleItem || user.item
}
)
Battle::AI::Handlers::MoveEffectScore.add("RestoreUserConsumedItem",
proc { |score, move, user, ai, battle|
next Battle::AI::MOVE_USELESS_SCORE if !user.item_active?
item_preference = user.wants_item?(user.battler.recycleItem)
no_item_preference = user.wants_item?(:NONE)
score += (item_preference - no_item_preference) * 4
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveBasePower.add("RemoveTargetItem",
proc { |power, move, user, target, ai, battle|
next move.move.pbBaseDamage(power, user.battler, target.battler)
}
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("RemoveTargetItem",
proc { |score, move, user, target, ai, battle|
next score if user.wild?
next score if !target.item || target.battler.unlosableItem?(target.item)
next score if target.effects[PBEffects::Substitute] > 0
next score if target.has_active_ability?(:STICKYHOLD) && !battle.moldBreaker
next score if !target.item_active?
# User can knock off the target's item; score it
item_preference = target.wants_item?(target.item_id)
no_item_preference = target.wants_item?(:NONE)
score -= (no_item_preference - item_preference) * 4
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("DestroyTargetBerryOrGem",
proc { |score, move, user, target, ai, battle|
next score if !target.item || (!target.item.is_berry? &&
!(Settings::MECHANICS_GENERATION >= 6 && target.item.is_gem?))
next score if user.battler.unlosableItem?(target.item)
next score if target.effects[PBEffects::Substitute] > 0
next score if target.has_active_ability?(:STICKYHOLD) && !battle.moldBreaker
next score if !target.item_active?
# User can incinerate the target's item; score it
item_preference = target.wants_item?(target.item_id)
no_item_preference = target.wants_item?(:NONE)
score -= (no_item_preference - item_preference) * 4
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("CorrodeTargetItem",
proc { |move, user, target, ai, battle|
next true if !target.item || target.unlosableItem?(target.item) ||
target.effects[PBEffects::Substitute] > 0
next true if target.has_active_ability?(:STICKYHOLD)
next true if battle.corrosiveGas[target.index % 2][target.party_index]
next false
}
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("CorrodeTargetItem",
proc { |score, move, user, target, ai, battle|
item_preference = target.wants_item?(target.item_id)
no_item_preference = target.wants_item?(:NONE)
target_diff = no_item_preference - item_preference
target_diff = 0 if !target.item_active?
score += target_diff * 4
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("StartTargetCannotUseItem",
proc { |move, user, target, ai, battle|
next target.effects[PBEffects::Embargo] > 0
}
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("StartTargetCannotUseItem",
proc { |score, move, user, target, ai, battle|
next Battle::AI::MOVE_USELESS_SCORE if !target.item || !target.item_active?
# NOTE: We won't check if the item has an effect, because if a Pokémon is
# holding an item, it probably does.
item_score = target.wants_item?(target.item_id)
next Battle::AI::MOVE_USELESS_SCORE if item_score <= 0 # Item has no effect or is bad
score += item_score * 2
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveEffectScore.add("StartNegateHeldItems",
proc { |score, move, user, ai, battle|
next if battle.field.effects[PBEffects::MagicRoom] == 1 # About to expire anyway
any_held_items = false
total_want = 0 # Positive means foes want their items more than allies do
ai.each_battler do |b, i|
next if !b.item
# Skip b if its item is disabled
if ai.trainer.medium_skill?
# NOTE: We won't check if the item has an effect, because if a Pokémon
# is holding an item, it probably does.
if battle.field.effects[PBEffects::MagicRoom] > 0
# NOTE: Same as b.item_active? but ignoring the Magic Room part.
next if b.effects[PBEffects::Embargo] > 0
next if battle.corrosiveGas[b.index % 2][b.party_index]
next if b.has_active_ability?(:KLUTZ)
else
next if !b.item_active?
end
end
# Rate b's held item and add it to total_want
any_held_items = true
want = b.wants_item?(b.item_id)
total_want += (b.opposes?(user)) ? want : -want
end
# Alter score
next Battle::AI::MOVE_USELESS_SCORE if !any_held_items
if battle.field.effects[PBEffects::MagicRoom] > 0
next Battle::AI::MOVE_USELESS_SCORE if total_want >= 0
score -= [total_want, -5].max * 4 # Will enable items, prefer if allies affected more
else
next Battle::AI::MOVE_USELESS_SCORE if total_want <= 0
score += [total_want, 5].min * 4 # Will disable items, prefer if foes affected more
end
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("UserConsumeBerryRaiseDefense2",
proc { |move, user, ai, battle|
item = user.item
next !item || !item.is_berry? || !user.item_active?
}
)
Battle::AI::Handlers::MoveEffectScore.add("UserConsumeBerryRaiseDefense2",
proc { |score, move, user, ai, battle|
# Score for raising the user's stat
stat_raise_score = Battle::AI::Handlers.apply_move_effect_score("RaiseUserDefense2",
0, move, user, ai, battle)
score += stat_raise_score if stat_raise_score != Battle::AI::MOVE_USELESS_SCORE
# Score for the consumed berry's effect
score += user.get_score_change_for_consuming_item(user.item_id, true)
# Score for other results of consuming the berry
if ai.trainer.medium_skill?
# Prefer if user will heal itself with Cheek Pouch
score += 8 if user.battler.canHeal? && user.hp < user.totalhp / 2 &&
user.has_active_ability?(:CHEEKPOUCH)
# Prefer if target can recover the consumed berry
score += 8 if user.has_active_ability?(:HARVEST) ||
user.has_move_with_function?("RestoreUserConsumedItem")
# Prefer if user couldn't normally consume the berry
score += 5 if !user.battler.canConsumeBerry?
# Prefer if user will become able to use Belch
score += 5 if !user.battler.belched? && user.has_move_with_function?("FailsIfUserNotConsumedBerry")
# Prefer if user will benefit from not having an item
score += 5 if user.has_active_ability?(:UNBURDEN)
end
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("AllBattlersConsumeBerry",
proc { |move, user, target, ai, battle|
next !target.item || !target.item.is_berry? || target.battler.semiInvulnerable?
}
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("AllBattlersConsumeBerry",
proc { |score, move, user, target, ai, battle|
# Score for the consumed berry's effect
score_change = target.get_score_change_for_consuming_item(target.item_id, !target.opposes?(user))
# Score for other results of consuming the berry
if ai.trainer.medium_skill?
# Prefer if target will heal itself with Cheek Pouch
score_change += 8 if target.battler.canHeal? && target.hp < target.totalhp / 2 &&
target.has_active_ability?(:CHEEKPOUCH)
# Prefer if target can recover the consumed berry
score_change += 8 if target.has_active_ability?(:HARVEST) ||
target.has_move_with_function?("RestoreUserConsumedItem")
# Prefer if target couldn't normally consume the berry
score_change += 5 if !target.battler.canConsumeBerry?
# Prefer if target will become able to use Belch
score_change += 5 if !target.battler.belched? && target.has_move_with_function?("FailsIfUserNotConsumedBerry")
# Prefer if target will benefit from not having an item
score_change += 5 if target.has_active_ability?(:UNBURDEN)
end
score += (target.opposes?(user)) ? -score_change : score_change
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("UserConsumeTargetBerry",
proc { |score, move, user, target, ai, battle|
next score if !target.item || !target.item.is_berry?
next score if user.battler.unlosableItem?(target.item)
next score if target.effects[PBEffects::Substitute] > 0
next score if target.has_active_ability?(:STICKYHOLD) && !battle.moldBreaker
# Score the user gaining the item's effect
score += user.get_score_change_for_consuming_item(target.item_id)
# Score for other results of consuming the berry
if ai.trainer.medium_skill?
# Prefer if user will heal itself with Cheek Pouch
score += 8 if user.battler.canHeal? && user.hp < user.totalhp / 2 &&
user.has_active_ability?(:CHEEKPOUCH)
# Prefer if user will become able to use Belch
score += 5 if !user.battler.belched? && user.has_move_with_function?("FailsIfUserNotConsumedBerry")
# Don't prefer if target will benefit from not having an item
score -= 5 if target.has_active_ability?(:UNBURDEN)
end
# Score the target no longer having the item
item_preference = target.wants_item?(target.item_id)
no_item_preference = target.wants_item?(:NONE)
score -= (no_item_preference - item_preference) * 3
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("ThrowUserItemAtTarget",
proc { |move, user, ai, battle|
item = user.item
next true if !item || !user.item_active? || user.battler.unlosableItem?(item)
next true if item.is_berry? && !user.battler.canConsumeBerry?
next true if item.flags.none? { |f| f[/^Fling_/i] }
next false
}
)
Battle::AI::Handlers::MoveBasePower.add("ThrowUserItemAtTarget",
proc { |power, move, user, target, ai, battle|
next move.move.pbBaseDamage(power, user.battler, target.battler)
}
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("ThrowUserItemAtTarget",
proc { |score, move, user, target, ai, battle|
case user.item_id
when :POISONBARB, :TOXICORB
score = Battle::AI::Handlers.apply_move_effect_against_target_score("PoisonTarget",
score, move, user, target, ai, battle)
when :FLAMEORB
score = Battle::AI::Handlers.apply_move_effect_against_target_score("BurnTarget",
score, move, user, target, ai, battle)
when :LIGHTBALL
score = Battle::AI::Handlers.apply_move_effect_against_target_score("ParalyzeTarget",
score, move, user, target, ai, battle)
when :KINGSROCK, :RAZORFANG
score = Battle::AI::Handlers.apply_move_effect_against_target_score("FlinchTarget",
score, move, user, target, ai, battle)
else
score -= target.get_score_change_for_consuming_item(user.item_id)
end
# Score for other results of consuming the berry
if ai.trainer.medium_skill?
# Don't prefer if target will become able to use Belch
score -= 5 if user.item.is_berry? && !target.battler.belched? &&
target.has_move_with_function?("FailsIfUserNotConsumedBerry")
# Prefer if user will benefit from not having an item
score += 5 if user.has_active_ability?(:UNBURDEN)
end
# Prefer if the user doesn't want its held item/don't prefer if it wants to
# keep its held item
item_preference = user.wants_item?(user.item_id)
no_item_preference = user.wants_item?(:NONE)
score += (no_item_preference - item_preference) * 2
next score
}
)

View File

@@ -0,0 +1,623 @@
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveEffectScore.add("RedirectAllMovesToUser",
proc { |score, move, user, ai, battle|
# Useless if there is no ally to redirect attacks from
next Battle::AI::MOVE_USELESS_SCORE if user.battler.allAllies.length == 0
# Prefer if ally is at low HP and user is at high HP
if ai.trainer.has_skill_flag?("HPAware") && user.hp > user.totalhp * 2 / 3
ai.each_ally(user.index) do |b, i|
score += 10 if b.hp <= b.totalhp / 3
end
end
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("RedirectAllMovesToTarget",
proc { |score, move, user, target, ai, battle|
if target.opposes?(user)
# Useless if target is a foe but there is only one foe
next Battle::AI::MOVE_USELESS_SCORE if target.battler.allAllies.length == 0
# Useless if there is no ally to attack the spotlighted foe
next Battle::AI::MOVE_USELESS_SCORE if user.battler.allAllies.length == 0
end
# Generaly don't prefer this move, as it's a waste of the user's turn
next score - 20
}
)
#===============================================================================
#
#===============================================================================
# CannotBeRedirected
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveBasePower.add("RandomlyDamageOrHealTarget",
proc { |power, move, user, target, ai, battle|
next 50 # Average power, ish
}
)
Battle::AI::Handlers::MoveEffectScore.add("RandomlyDamageOrHealTarget",
proc { |score, move, user, ai, battle|
# Generaly don't prefer this move, as it may heal the target instead
next score - 10
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("HealAllyOrDamageFoe",
proc { |move, user, target, ai, battle|
next !target.opposes?(user) && !target.battler.canHeal?
}
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("HealAllyOrDamageFoe",
proc { |score, move, user, target, ai, battle|
next score if target.opposes?(user)
# Consider how much HP will be restored
if ai.trainer.has_skill_flag?("HPAware")
if target.hp >= target.totalhp * 0.5
score -= 10
else
score += 20 * (target.totalhp - target.hp) / target.totalhp # +10 to +20
end
end
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("CurseTargetOrLowerUserSpd1RaiseUserAtkDef1",
proc { |move, user, ai, battle|
next false if user.has_type?(:GHOST) ||
(move.rough_type == :GHOST && user.has_active_ability?([:LIBERO, :PROTEAN]))
will_fail = true
(move.move.statUp.length / 2).times do |i|
next if !user.battler.pbCanRaiseStatStage?(move.move.statUp[i * 2], user.battler, move.move)
will_fail = false
break
end
(move.move.statDown.length / 2).times do |i|
next if !user.battler.pbCanLowerStatStage?(move.move.statDown[i * 2], user.battler, move.move)
will_fail = false
break
end
next will_fail
}
)
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("CurseTargetOrLowerUserSpd1RaiseUserAtkDef1",
proc { |move, user, target, ai, battle|
next false if !user.has_type?(:GHOST) &&
!(move.rough_type == :GHOST && user.has_active_ability?([:LIBERO, :PROTEAN]))
next true if target.effects[PBEffects::Curse] || !target.battler.takesIndirectDamage?
next false
}
)
Battle::AI::Handlers::MoveEffectScore.add("CurseTargetOrLowerUserSpd1RaiseUserAtkDef1",
proc { |score, move, user, ai, battle|
next score if user.has_type?(:GHOST) ||
(move.rough_type == :GHOST && user.has_active_ability?([:LIBERO, :PROTEAN]))
score = ai.get_score_for_target_stat_raise(score, user, move.move.statUp)
next score if score == Battle::AI::MOVE_USELESS_SCORE
next ai.get_score_for_target_stat_drop(score, user, move.move.statDown, false)
}
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("CurseTargetOrLowerUserSpd1RaiseUserAtkDef1",
proc { |score, move, user, target, ai, battle|
next score if !user.has_type?(:GHOST) &&
!(move.rough_type == :GHOST && user.has_active_ability?([:LIBERO, :PROTEAN]))
# Don't prefer if user will faint because of using this move
if ai.trainer.has_skill_flag?("HPAware")
next Battle::AI::MOVE_USELESS_SCORE if user.hp <= user.totalhp / 2
end
# Prefer early on
score += 10 if user.turnCount < 2
if ai.trainer.medium_skill?
# Prefer if the user has no damaging moves
score += 15 if !user.check_for_move { |m| m.damagingMove? }
# Prefer if the target can't switch out to remove its curse
score += 10 if !battle.pbCanChooseNonActive?(target.index)
end
if ai.trainer.high_skill?
# Prefer if user can stall while damage is dealt
if user.check_for_move { |m| m.is_a?(Battle::Move::ProtectMove) }
score += 5
end
end
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("EffectDependsOnEnvironment",
proc { |score, move, user, target, ai, battle|
# Determine this move's effect
move.move.pbOnStartUse(user.battler, [target.battler])
function_code = nil
case move.move.secretPower
when 2
function_code = "SleepTarget"
when 10
function_code = "BurnTarget"
when 0, 1
function_code = "ParalyzeTarget"
when 9
function_code = "FreezeTarget"
when 5
function_code = "LowerTargetAttack1"
when 14
function_code = "LowerTargetDefense1"
when 3
function_code = "LowerTargetSpAtk1"
when 4, 6, 12
function_code = "LowerTargetSpeed1"
when 8
function_code = "LowerTargetAccuracy1"
when 7, 11, 13
function_code = "FlinchTarget"
end
if function_code
next Battle::AI::Handlers.apply_move_effect_against_target_score(function_code,
score, move, user, target, ai, battle)
end
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveBasePower.add("HitsAllFoesAndPowersUpInPsychicTerrain",
proc { |power, move, user, target, ai, battle|
next move.move.pbBaseDamage(power, user.battler, target.battler)
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("TargetNextFireMoveDamagesTarget",
proc { |move, user, target, ai, battle|
next target.effects[PBEffects::Powder]
}
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TargetNextFireMoveDamagesTarget",
proc { |score, move, user, target, ai, battle|
# Prefer if target knows any Fire moves (moreso if that's the only type they know)
next Battle::AI::MOVE_USELESS_SCORE if !target.check_for_move { |m| m.pbCalcType(target.battler) == :FIRE }
score += 10
score += 10 if !target.check_for_move { |m| m.pbCalcType(target.battler) != :FIRE }
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveEffectScore.add("DoublePowerAfterFusionFlare",
proc { |score, move, user, ai, battle|
# Prefer if an ally knows Fusion Flare
ai.each_ally(user.index) do |b, i|
score += 10 if b.has_move_with_function?("DoublePowerAfterFusionBolt")
end
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveEffectScore.add("DoublePowerAfterFusionBolt",
proc { |score, move, user, ai, battle|
# Prefer if an ally knows Fusion Bolt
ai.each_ally(user.index) do |b, i|
score += 10 if b.has_move_with_function?("DoublePowerAfterFusionFlare")
end
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("PowerUpAllyMove",
proc { |move, user, target, ai, battle|
next target.effects[PBEffects::HelpingHand]
}
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("PowerUpAllyMove",
proc { |score, move, user, target, ai, battle|
next Battle::AI::MOVE_USELESS_SCORE if !target.check_for_move { |m| m.damagingMove? }
next score + 5
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveBasePower.add("CounterPhysicalDamage",
proc { |power, move, user, target, ai, battle|
next 60 # Representative value
}
)
Battle::AI::Handlers::MoveEffectScore.add("CounterPhysicalDamage",
proc { |score, move, user, ai, battle|
has_physical_move = false
ai.each_foe_battler(user.side) do |b, i|
next if !b.can_attack?
next if !b.check_for_move { |m| m.physicalMove?(m.type) &&
(user.effects[PBEffects::Substitute] == 0 ||
m.ignoresSubstitute?(b.battler)) }
has_physical_move = true
# Prefer if foe has a higher Attack than Special Attack
score += 5 if b.rough_stat(:ATTACK) > b.rough_stat(:SPECIAL_ATTACK)
# Prefer if the last move the foe used was physical
if ai.trainer.medium_skill? && b.battler.lastMoveUsed
score += 8 if GameData::Move.try_get(b.battler.lastMoveUsed)&.physical?
end
# Prefer if the foe is taunted into using a damaging move
score += 5 if b.effects[PBEffects::Taunt] > 0
end
# Useless if no foes have a physical move to counter
next Battle::AI::MOVE_USELESS_SCORE if !has_physical_move
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveBasePower.add("CounterSpecialDamage",
proc { |power, move, user, target, ai, battle|
next 60 # Representative value
}
)
Battle::AI::Handlers::MoveEffectScore.add("CounterSpecialDamage",
proc { |score, move, user, ai, battle|
has_special_move = false
ai.each_foe_battler(user.side) do |b, i|
next if !b.can_attack?
next if !b.check_for_move { |m| m.specialMove?(m.type) &&
(user.effects[PBEffects::Substitute] == 0 ||
m.ignoresSubstitute?(b.battler)) }
has_special_move = true
# Prefer if foe has a higher Special Attack than Attack
score += 5 if b.rough_stat(:SPECIAL_ATTACK) > b.rough_stat(:ATTACK)
# Prefer if the last move the foe used was special
if ai.trainer.medium_skill? && b.battler.lastMoveUsed
score += 8 if GameData::Move.try_get(b.battler.lastMoveUsed)&.special?
end
# Prefer if the foe is taunted into using a damaging move
score += 5 if b.effects[PBEffects::Taunt] > 0
end
# Useless if no foes have a special move to counter
next Battle::AI::MOVE_USELESS_SCORE if !has_special_move
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveBasePower.add("CounterDamagePlusHalf",
proc { |power, move, user, target, ai, battle|
next 60 # Representative value
}
)
Battle::AI::Handlers::MoveEffectScore.add("CounterDamagePlusHalf",
proc { |score, move, user, ai, battle|
has_damaging_move = false
ai.each_foe_battler(user.side) do |b, i|
next if !b.can_attack? || user.faster_than?(b)
next if !b.check_for_move { |m| m.damagingMove? &&
(user.effects[PBEffects::Substitute] == 0 ||
m.ignoresSubstitute?(b.battler)) }
has_damaging_move = true
# Prefer if the last move the foe used was damaging
if ai.trainer.medium_skill? && b.battler.lastMoveUsed
score += 8 if GameData::Move.try_get(b.battler.lastMoveUsed)&.damaging?
end
# Prefer if the foe is taunted into using a damaging move
score += 5 if b.effects[PBEffects::Taunt] > 0
end
# Useless if no foes have a damaging move to counter
next Battle::AI::MOVE_USELESS_SCORE if !has_damaging_move
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("UserAddStockpileRaiseDefSpDef1",
proc { |move, user, ai, battle|
next user.effects[PBEffects::Stockpile] >= 3
}
)
Battle::AI::Handlers::MoveEffectScore.add("UserAddStockpileRaiseDefSpDef1",
proc { |score, move, user, ai, battle|
score = ai.get_score_for_target_stat_raise(score, user, [:DEFENSE, 1, :SPECIAL_DEFENSE, 1], false)
# More preferable if user also has Spit Up/Swallow
if user.battler.pbHasMoveFunction?("PowerDependsOnUserStockpile",
"HealUserDependingOnUserStockpile")
score += [10, 10, 8, 5][user.effects[PBEffects::Stockpile]]
end
next score
}
)
#===============================================================================
# NOTE: Don't worry about the stat drops caused by losing the stockpile, because
# if these moves are known, they want to be used.
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("PowerDependsOnUserStockpile",
proc { |move, user, ai, battle|
next user.effects[PBEffects::Stockpile] == 0
}
)
Battle::AI::Handlers::MoveBasePower.add("PowerDependsOnUserStockpile",
proc { |power, move, user, target, ai, battle|
next move.move.pbBaseDamage(power, user.battler, target.battler)
}
)
Battle::AI::Handlers::MoveEffectScore.add("PowerDependsOnUserStockpile",
proc { |score, move, user, ai, battle|
# Slightly prefer to hold out for another Stockpile to make this move stronger
score -= 5 if user.effects[PBEffects::Stockpile] < 2
next score
}
)
#===============================================================================
# NOTE: Don't worry about the stat drops caused by losing the stockpile, because
# if these moves are known, they want to be used.
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("HealUserDependingOnUserStockpile",
proc { |move, user, ai, battle|
next true if user.effects[PBEffects::Stockpile] == 0
next true if !user.battler.canHeal? &&
user.effects[PBEffects::StockpileDef] == 0 &&
user.effects[PBEffects::StockpileSpDef] == 0
next false
}
)
Battle::AI::Handlers::MoveEffectScore.add("HealUserDependingOnUserStockpile",
proc { |score, move, user, ai, battle|
next Battle::AI::MOVE_USELESS_SCORE if !user.battler.canHeal?
# Consider how much HP will be restored
if ai.trainer.has_skill_flag?("HPAware")
next score - 10 if user.hp >= user.totalhp * 0.5
score += 20 * (user.totalhp - user.hp) / user.totalhp # +10 to +20
end
# Slightly prefer to hold out for another Stockpile to make this move stronger
score -= 5 if user.effects[PBEffects::Stockpile] < 2
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveEffectScore.add("GrassPledge",
proc { |score, move, user, ai, battle|
# Prefer if an ally knows a different Pledge move
ai.each_ally(user.index) do |b, i|
score += 10 if b.has_move_with_function?("FirePledge", "WaterPledge")
end
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveEffectScore.add("FirePledge",
proc { |score, move, user, ai, battle|
# Prefer if an ally knows a different Pledge move
ai.each_ally(user.index) do |b, i|
score += 10 if b.has_move_with_function?("GrassPledge", "WaterPledge")
end
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveEffectScore.add("WaterPledge",
proc { |score, move, user, ai, battle|
# Prefer if an ally knows a different Pledge move
ai.each_ally(user.index) do |b, i|
score += 10 if b.has_move_with_function?("GrassPledge", "FirePledge")
end
next score
}
)
#===============================================================================
# NOTE: The move that this move will become is determined in def
# set_up_move_check, and the score for that move is calculated instead. If
# this move cannot become another move and will fail, the score for this
# move is calculated as normal (and the code below says it fails).
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("UseLastMoveUsed",
proc { |move, user, ai, battle|
next true if !battle.lastMoveUsed || !GameData::Move.exists?(battle.lastMoveUsed)
next move.move.moveBlacklist.include?(GameData::Move.get(battle.lastMoveUsed).function_code)
}
)
#===============================================================================
# NOTE: The move that this move will become is determined in def
# set_up_move_check, and the score for that move is calculated instead. If
# this move cannot become another move and will fail, the score for this
# move is calculated as normal (and the code below says it fails).
#===============================================================================
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("UseLastMoveUsedByTarget",
proc { |move, user, target, ai, battle|
next true if !target.battler.lastRegularMoveUsed
next true if !GameData::Move.exists?(target.battler.lastRegularMoveUsed)
next !GameData::Move.get(target.battler.lastRegularMoveUsed).has_flag?("CanMirrorMove")
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("UseMoveTargetIsAboutToUse",
proc { |move, user, target, ai, battle|
next !target.check_for_move { |m| m.damagingMove? && !move.move.moveBlacklist.include?(m.function_code) }
}
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("UseMoveTargetIsAboutToUse",
proc { |score, move, user, target, ai, battle|
next Battle::AI::MOVE_USELESS_SCORE if target.faster_than?(user)
# Don't prefer if target knows any moves that can't be copied
if target.check_for_move { |m| m.statusMove? || move.move.moveBlacklist.include?(m.function_code) }
score -= 8
end
next score
}
)
#===============================================================================
# NOTE: The move that this move will become is determined in def
# set_up_move_check, and the score for that move is calculated instead.
#===============================================================================
# UseMoveDependingOnEnvironment
#===============================================================================
#
#===============================================================================
# UseRandomMove
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("UseRandomMoveFromUserParty",
proc { |move, user, ai, battle|
will_fail = true
battle.pbParty(user.index).each_with_index do |pkmn, i|
next if !pkmn || i == user.party_index
next if Settings::MECHANICS_GENERATION >= 6 && pkmn.egg?
pkmn.moves.each do |pkmn_move|
next if move.move.moveBlacklist.include?(pkmn_move.function_code)
next if pkmn_move.type == :SHADOW
will_fail = false
break
end
break if !will_fail
end
next will_fail
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("UseRandomUserMoveIfAsleep",
proc { |move, user, ai, battle|
will_fail = true
user.battler.eachMoveWithIndex do |m, i|
next if move.move.moveBlacklist.include?(m.function)
next if !battle.pbCanChooseMove?(user.index, i, false, true)
will_fail = false
break
end
next will_fail
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveEffectScore.add("BounceBackProblemCausingStatusMoves",
proc { |score, move, user, ai, battle|
next Battle::AI::MOVE_USELESS_SCORE if user.has_active_ability?(:MAGICBOUNCE)
useless = true
ai.each_foe_battler(user.side) do |b, i|
next if !b.can_attack?
next if !b.check_for_move { |m| m.statusMove? && m.canMagicCoat? }
score += 5
useless = false
end
next Battle::AI::MOVE_USELESS_SCORE if useless
# Don't prefer the lower the user's HP is (better to try something else)
if ai.trainer.has_skill_flag?("HPAware") && user.hp < user.totalhp / 2
score -= (20 * (1.0 - (user.hp.to_f / user.totalhp))).to_i # -10 to -20
end
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveEffectScore.add("StealAndUseBeneficialStatusMove",
proc { |score, move, user, ai, battle|
useless = true
ai.each_foe_battler(user.side) do |b, i|
next if !b.can_attack?
next if !b.check_for_move { |m| m.statusMove? && m.canSnatch? }
score += 5
useless = false
end
next Battle::AI::MOVE_USELESS_SCORE if useless
# Don't prefer the lower the user's HP is (better to try something else)
if ai.trainer.has_skill_flag?("HPAware") && user.hp < user.totalhp / 2
score -= (20 * (1.0 - (user.hp.to_f / user.totalhp))).to_i # -10 to -20
end
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("ReplaceMoveThisBattleWithTargetLastMoveUsed",
proc { |move, user, ai, battle|
next user.effects[PBEffects::Transform] || !user.battler.pbHasMove?(move.id)
}
)
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("ReplaceMoveThisBattleWithTargetLastMoveUsed",
proc { |move, user, target, ai, battle|
next false if !user.faster_than?(target)
last_move_data = GameData::Move.try_get(target.battler.lastRegularMoveUsed)
next true if !last_move_data ||
user.battler.pbHasMove?(target.battler.lastRegularMoveUsed) ||
move.move.moveBlacklist.include?(last_move_data.function_code) ||
last_move_data.type == :SHADOW
next false
}
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("ReplaceMoveThisBattleWithTargetLastMoveUsed",
proc { |score, move, user, target, ai, battle|
# Generally don't prefer, as this wastes the user's turn just to gain a move
# of unknown utility
score -= 10
# Slightly prefer if this move will definitely succeed, just for the sake of
# getting rid of this move
score += 5 if user.faster_than?(target)
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.copy("ReplaceMoveThisBattleWithTargetLastMoveUsed",
"ReplaceMoveWithTargetLastMoveUsed")
Battle::AI::Handlers::MoveEffectScore.copy("ReplaceMoveThisBattleWithTargetLastMoveUsed",
"ReplaceMoveWithTargetLastMoveUsed")

View File

@@ -0,0 +1,877 @@
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("FleeFromBattle",
proc { |move, user, ai, battle|
next !battle.pbCanRun?(user.index) || (user.wild? && user.battler.allAllies.length > 0)
}
)
Battle::AI::Handlers::MoveEffectScore.add("FleeFromBattle",
proc { |score, move, user, ai, battle|
# Generally don't prefer (don't want to end the battle too easily)
next score - 20
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("SwitchOutUserStatusMove",
proc { |move, user, ai, battle|
if user.wild?
next !battle.pbCanRun?(user.index) || user.battler.allAllies.length > 0
end
next !battle.pbCanChooseNonActive?(user.index)
}
)
Battle::AI::Handlers::MoveEffectScore.add("SwitchOutUserStatusMove",
proc { |score, move, user, ai, battle|
# Wild Pokémon run from battle - generally don't prefer (don't want to end the battle too easily)
next score - 20 if user.wild?
# Trainer-owned Pokémon switch out
if ai.trainer.has_skill_flag?("ReserveLastPokemon") && battle.pbTeamAbleNonActiveCount(user.index) == 1
next Battle::AI::MOVE_USELESS_SCORE # Don't switch in ace
end
# Prefer if the user switching out will lose a negative effect
score += 20 if user.effects[PBEffects::PerishSong] > 0
score += 10 if user.effects[PBEffects::Confusion] > 1
score += 10 if user.effects[PBEffects::Attract] >= 0
# Consider the user's stat stages
if user.stages.any? { |key, val| val >= 2 }
score -= 15
elsif user.stages.any? { |key, val| val < 0 }
score += 10
end
# Consider the user's end of round damage/healing
eor_damage = user.rough_end_of_round_damage
score += 15 if eor_damage > 0
score -= 15 if eor_damage < 0
# Prefer if the user doesn't have any damaging moves
score += 10 if !user.check_for_move { |m| m.damagingMove? }
# Don't prefer if the user's side has entry hazards on it
score -= 10 if user.pbOwnSide.effects[PBEffects::Spikes] > 0
score -= 10 if user.pbOwnSide.effects[PBEffects::ToxicSpikes] > 0
score -= 10 if user.pbOwnSide.effects[PBEffects::StealthRock]
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveEffectScore.add("SwitchOutUserDamagingMove",
proc { |score, move, user, ai, battle|
next score if !battle.pbCanChooseNonActive?(user.index)
# Don't want to switch in ace
score -= 20 if ai.trainer.has_skill_flag?("ReserveLastPokemon") &&
battle.pbTeamAbleNonActiveCount(user.index) == 1
# Prefer if the user switching out will lose a negative effect
score += 20 if user.effects[PBEffects::PerishSong] > 0
score += 10 if user.effects[PBEffects::Confusion] > 1
score += 10 if user.effects[PBEffects::Attract] >= 0
# Consider the user's stat stages
if user.stages.any? { |key, val| val >= 2 }
score -= 15
elsif user.stages.any? { |key, val| val < 0 }
score += 10
end
# Consider the user's end of round damage/healing
eor_damage = user.rough_end_of_round_damage
score += 15 if eor_damage > 0
score -= 15 if eor_damage < 0
# Don't prefer if the user's side has entry hazards on it
score -= 10 if user.pbOwnSide.effects[PBEffects::Spikes] > 0
score -= 10 if user.pbOwnSide.effects[PBEffects::ToxicSpikes] > 0
score -= 10 if user.pbOwnSide.effects[PBEffects::StealthRock]
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("LowerTargetAtkSpAtk1SwitchOutUser",
proc { |move, user, target, ai, battle|
will_fail = true
(move.move.statDown.length / 2).times do |i|
next if !target.battler.pbCanLowerStatStage?(move.move.statDown[i * 2], user.battler, move.move)
will_fail = false
break
end
next will_fail
}
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("LowerTargetAtkSpAtk1SwitchOutUser",
proc { |score, move, user, target, ai, battle|
next ai.get_score_for_target_stat_drop(score, target, move.move.statDown, false)
}
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.copy("SwitchOutUserDamagingMove",
"LowerTargetAtkSpAtk1SwitchOutUser")
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("SwitchOutUserPassOnEffects",
proc { |move, user, ai, battle|
next !battle.pbCanChooseNonActive?(user.index)
}
)
Battle::AI::Handlers::MoveEffectScore.add("SwitchOutUserPassOnEffects",
proc { |score, move, user, ai, battle|
# Don't want to switch in ace
score -= 20 if ai.trainer.has_skill_flag?("ReserveLastPokemon") &&
battle.pbTeamAbleNonActiveCount(user.index) == 1
# Don't prefer if the user will pass on a negative effect
score -= 10 if user.effects[PBEffects::Confusion] > 1
score -= 15 if user.effects[PBEffects::Curse]
score -= 10 if user.effects[PBEffects::Embargo] > 1
score -= 15 if user.effects[PBEffects::GastroAcid]
score -= 10 if user.effects[PBEffects::HealBlock] > 1
score -= 10 if user.effects[PBEffects::LeechSeed] >= 0
score -= 20 if user.effects[PBEffects::PerishSong] > 0
# Prefer if the user will pass on a positive effect
score += 10 if user.effects[PBEffects::AquaRing]
score += 10 if user.effects[PBEffects::FocusEnergy] > 0
score += 10 if user.effects[PBEffects::Ingrain]
score += 8 if user.effects[PBEffects::MagnetRise] > 1
score += 10 if user.effects[PBEffects::Substitute] > 0
# Consider the user's stat stages
if user.stages.any? { |key, val| val >= 4 }
score += 25
elsif user.stages.any? { |key, val| val >= 2 }
score += 15
elsif user.stages.any? { |key, val| val < 0 }
score -= 15
end
# Consider the user's end of round damage/healing
eor_damage = user.rough_end_of_round_damage
score += 15 if eor_damage > 0
score -= 15 if eor_damage < 0
# Prefer if the user doesn't have any damaging moves
score += 15 if !user.check_for_move { |m| m.damagingMove? }
# Don't prefer if the user's side has entry hazards on it
score -= 10 if user.pbOwnSide.effects[PBEffects::Spikes] > 0
score -= 10 if user.pbOwnSide.effects[PBEffects::ToxicSpikes] > 0
score -= 10 if user.pbOwnSide.effects[PBEffects::StealthRock]
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("SwitchOutTargetStatusMove",
proc { |move, user, target, ai, battle|
next move.move.pbFailsAgainstTarget?(user.battler, target.battler, false)
}
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("SwitchOutTargetStatusMove",
proc { |score, move, user, target, ai, battle|
# Ends the battle - generally don't prefer (don't want to end the battle too easily)
next score - 10 if target.wild?
# Switches the target out
next Battle::AI::MOVE_USELESS_SCORE if target.effects[PBEffects::PerishSong] > 0
# Don't prefer if target is at low HP and could be knocked out instead
if ai.trainer.has_skill_flag?("HPAware")
score -= 10 if target.hp <= target.totalhp / 3
end
# Consider the target's stat stages
if target.stages.any? { |key, val| val >= 2 }
score += 15
elsif target.stages.any? { |key, val| val < 0 }
score -= 15
end
# Consider the target's end of round damage/healing
eor_damage = target.rough_end_of_round_damage
score -= 15 if eor_damage > 0
score += 15 if eor_damage < 0
# Prefer if the target's side has entry hazards on it
score += 10 if target.pbOwnSide.effects[PBEffects::Spikes] > 0
score += 10 if target.pbOwnSide.effects[PBEffects::ToxicSpikes] > 0
score += 10 if target.pbOwnSide.effects[PBEffects::StealthRock]
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("SwitchOutTargetDamagingMove",
proc { |score, move, user, target, ai, battle|
next score if target.wild?
# No score modification if the target can't be made to switch out
next score if !battle.moldBreaker && target.has_active_ability?(:SUCTIONCUPS)
next score if target.effects[PBEffects::Ingrain]
# No score modification if the target can't be replaced
can_switch = false
battle.eachInTeamFromBattlerIndex(target.index) do |_pkmn, i|
can_switch = battle.pbCanSwitchIn?(target.index, i)
break if can_switch
end
next score if !can_switch
# Not score modification if the target has a Substitute
next score if target.effects[PBEffects::Substitute] > 0
# Don't want to switch out the target if it will faint from Perish Song
score -= 20 if target.effects[PBEffects::PerishSong] > 0
# Consider the target's stat stages
if target.stages.any? { |key, val| val >= 2 }
score += 15
elsif target.stages.any? { |key, val| val < 0 }
score -= 15
end
# Consider the target's end of round damage/healing
eor_damage = target.rough_end_of_round_damage
score -= 15 if eor_damage > 0
score += 15 if eor_damage < 0
# Prefer if the target's side has entry hazards on it
score += 10 if target.pbOwnSide.effects[PBEffects::Spikes] > 0
score += 10 if target.pbOwnSide.effects[PBEffects::ToxicSpikes] > 0
score += 10 if target.pbOwnSide.effects[PBEffects::StealthRock]
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("BindTarget",
proc { |score, move, user, target, ai, battle|
next score if target.effects[PBEffects::Trapping] > 0
next score if target.effects[PBEffects::Substitute] > 0
# Prefer if the user has a Binding Band or Grip Claw (because why have it if
# you don't want to use it?)
score += 5 if user.has_active_item?([:BINDINGBAND, :GRIPCLAW])
# Target will take damage at the end of each round from the binding
score += 10 if target.battler.takesIndirectDamage?
# Check whether the target will be trapped in battle by the binding
if target.can_become_trapped?
score += 8 # Prefer if the target will become trapped by this move
eor_damage = target.rough_end_of_round_damage
if eor_damage > 0
# Prefer if the target will take damage at the end of each round on top
# of binding damage
score += 10
elsif eor_damage < 0
# Don't prefer if the target will heal itself at the end of each round
score -= 10
end
# Prefer if the target has been Perish Songed
score += 15 if target.effects[PBEffects::PerishSong] > 0
end
# Don't prefer if the target can remove the binding (and the binding has an
# effect)
if target.can_become_trapped? || target.battler.takesIndirectDamage?
if ai.trainer.medium_skill? &&
target.has_move_with_function?("RemoveUserBindingAndEntryHazards")
score -= 10
end
end
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveBasePower.add("BindTargetDoublePowerIfTargetUnderwater",
proc { |power, move, user, target, ai, battle|
next move.move.pbModifyDamage(power, user.battler, target.battler)
}
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.copy("BindTarget",
"BindTargetDoublePowerIfTargetUnderwater")
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("TrapTargetInBattle",
proc { |move, user, target, ai, battle|
next false if move.damagingMove?
next true if target.effects[PBEffects::MeanLook] >= 0
next true if Settings::MORE_TYPE_EFFECTS && target.has_type?(:GHOST)
next false
}
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TrapTargetInBattle",
proc { |score, move, user, target, ai, battle|
if !target.can_become_trapped? || !battle.pbCanChooseNonActive?(target.index)
next (move.damagingMove?) ? score : Battle::AI::MOVE_USELESS_SCORE
end
# Not worth trapping if target will faint this round anyway
eor_damage = target.rough_end_of_round_damage
if eor_damage >= target.hp
next (move.damagingMove?) ? score : Battle::AI::MOVE_USELESS_SCORE
end
# Not worth trapping if target can remove the binding
if ai.trainer.medium_skill? &&
target.has_move_with_function?("RemoveUserBindingAndEntryHazards")
next (move.damagingMove?) ? score : Battle::AI::MOVE_USELESS_SCORE
end
# Score for being an additional effect
add_effect = move.get_score_change_for_additional_effect(user, target)
next score if add_effect == -999 # Additional effect will be negated
score += add_effect
# Score for target becoming trapped in battle
if target.effects[PBEffects::PerishSong] > 0 ||
target.effects[PBEffects::Attract] >= 0 ||
target.effects[PBEffects::Confusion] > 0 ||
eor_damage > 0
score += 15
end
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.copy("TrapTargetInBattle",
"TrapTargetInBattleMainEffect")
Battle::AI::Handlers::MoveEffectAgainstTargetScore.copy("TrapTargetInBattle",
"TrapTargetInBattleMainEffect")
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("TrapTargetInBattleLowerTargetDefSpDef1EachTurn",
proc { |move, user, target, ai, battle|
next false if move.damagingMove?
next true if target.effects[PBEffects::Octolock] >= 0
next true if Settings::MORE_TYPE_EFFECTS && target.has_type?(:GHOST)
next false
}
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TrapTargetInBattleLowerTargetDefSpDef1EachTurn",
proc { |score, move, user, target, ai, battle|
# Score for stat drop
score = ai.get_score_for_target_stat_drop(score, target, [:DEFENSE, 1, :SPECIAL_DEFENSE, 1], false)
# Score for target becoming trapped in battle
if target.can_become_trapped? && battle.pbCanChooseNonActive?(target.index)
# Not worth trapping if target will faint this round anyway
eor_damage = target.rough_end_of_round_damage
if eor_damage >= target.hp
next (move.damagingMove?) ? score : Battle::AI::MOVE_USELESS_SCORE
end
# Not worth trapping if target can remove the binding
if target.has_move_with_function?("RemoveUserBindingAndEntryHazards")
next (move.damagingMove?) ? score : Battle::AI::MOVE_USELESS_SCORE
end
# Score for target becoming trapped in battle
if target.effects[PBEffects::PerishSong] > 0 ||
target.effects[PBEffects::Attract] >= 0 ||
target.effects[PBEffects::Confusion] > 0 ||
eor_damage > 0
score += 15
end
end
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TrapUserAndTargetInBattle",
proc { |score, move, user, target, ai, battle|
# NOTE: Don't worry about scoring for the user also becoming trapped in
# battle, because it knows this move and accepts what it's getting
# itself into.
if target.can_become_trapped? && battle.pbCanChooseNonActive?(target.index)
# Not worth trapping if target will faint this round anyway
eor_damage = target.rough_end_of_round_damage
if eor_damage >= target.hp
next (move.damagingMove?) ? score : Battle::AI::MOVE_USELESS_SCORE
end
# Not worth trapping if target can remove the binding
if ai.trainer.medium_skill? &&
target.has_move_with_function?("RemoveUserBindingAndEntryHazards")
next (move.damagingMove?) ? score : Battle::AI::MOVE_USELESS_SCORE
end
# Score for target becoming trapped in battle
if target.effects[PBEffects::PerishSong] > 0 ||
target.effects[PBEffects::Attract] >= 0 ||
target.effects[PBEffects::Confusion] > 0 ||
eor_damage > 0
score += 15
end
end
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("TrapAllBattlersInBattleForOneTurn",
proc { |move, user, ai, battle|
next battle.field.effects[PBEffects::FairyLock] > 0
}
)
Battle::AI::Handlers::MoveEffectScore.add("TrapAllBattlersInBattleForOneTurn",
proc { |score, move, user, ai, battle|
# Trapping for just one turn isn't so significant, so generally don't prefer
next score - 10
}
)
#===============================================================================
#
#===============================================================================
# PursueSwitchingFoe
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("UsedAfterUserTakesPhysicalDamage",
proc { |move, user, ai, battle|
found_physical_move = false
ai.each_foe_battler(user.side) do |b, i|
next if !b.check_for_move { |m| m.physicalMove?(m.type) }
found_physical_move = true
break
end
next !found_physical_move
}
)
Battle::AI::Handlers::MoveEffectScore.add("UsedAfterUserTakesPhysicalDamage",
proc { |score, move, user, ai, battle|
next Battle::AI::MOVE_USELESS_SCORE if user.effects[PBEffects::Substitute] > 0
# Prefer if foes don't know any special moves
found_special_move = false
ai.each_foe_battler(user.side) do |b, i|
next if !b.check_for_move { |m| m.specialMove?(m.type) }
found_special_move = true
break
end
score += 10 if !found_special_move
# Generally not worth using
next score - 10
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveEffectScore.add("UsedAfterAllyRoundWithDoublePower",
proc { |score, move, user, ai, battle|
# No score change if no allies know this move
ally_has_move = false
ai.each_same_side_battler(user.side) do |b, i|
next if !b.has_move_with_function?(move.function)
ally_has_move = true
break
end
next score if !ally_has_move
# Prefer for the sake of doubling in power
score += 10
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TargetActsNext",
proc { |score, move, user, target, ai, battle|
# Useless if the target is a foe
next Battle::AI::MOVE_USELESS_SCORE if target.opposes?(user)
# Compare the speeds of all battlers
speeds = []
ai.each_battler { |b, i| speeds.push([i, b.rough_stat(:SPEED)]) }
if battle.field.effects[PBEffects::TrickRoom] > 0
speeds.sort! { |a, b| a[1] <=> b[1] }
else
speeds.sort! { |a, b| b[1] <=> a[1] }
end
idx_user = speeds.index { |ele| ele[0] == user.index }
idx_target = speeds.index { |ele| ele[0] == target.index }
# Useless if the target is faster than the user
next Battle::AI::MOVE_USELESS_SCORE if idx_target < idx_user
# Useless if the target will move next anyway
next Battle::AI::MOVE_USELESS_SCORE if idx_target - idx_user <= 1
# Generally not worth using
# NOTE: Because this move can be used against a foe but is being used on an
# ally (since we're here in this code), this move's score will be
# inverted later. A higher score here means this move will be less
# preferred, which is the result we want.
next score + 10
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TargetActsLast",
proc { |score, move, user, target, ai, battle|
# Useless if the target is an ally
next Battle::AI::MOVE_USELESS_SCORE if !target.opposes?(user)
# Useless if the user has no ally (the point of this move is to let the ally
# get in a hit before the foe)
has_ally = false
ai.each_ally(user.index) { |b, i| has_ally = true if b.can_attack? }
next Battle::AI::MOVE_USELESS_SCORE if !has_ally
# Compare the speeds of all battlers
speeds = []
ai.each_battler { |b, i| speeds.push([i, b.rough_stat(:SPEED)]) }
if battle.field.effects[PBEffects::TrickRoom] > 0
speeds.sort! { |a, b| a[1] <=> b[1] }
else
speeds.sort! { |a, b| b[1] <=> a[1] }
end
idx_user = speeds.index { |ele| ele[0] == user.index }
idx_target = speeds.index { |ele| ele[0] == target.index }
idx_slowest_ally = -1
speeds.each_with_index { |ele, i| idx_slowest_ally = i if user.index.even? == ele[0].even? }
# Useless if the target is faster than the user
next Battle::AI::MOVE_USELESS_SCORE if idx_target < idx_user
# Useless if the target will move last anyway
next Battle::AI::MOVE_USELESS_SCORE if idx_target == speeds.length - 1
# Useless if the slowest ally is faster than the target
next Battle::AI::MOVE_USELESS_SCORE if idx_slowest_ally < idx_target
# Generally not worth using
next score - 10
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("TargetUsesItsLastUsedMoveAgain",
proc { |move, user, target, ai, battle|
next target.battler.usingMultiTurnAttack?
}
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("TargetUsesItsLastUsedMoveAgain",
proc { |score, move, user, target, ai, battle|
# We don't ever want to make a foe act again
next Battle::AI::MOVE_USELESS_SCORE if target.opposes?(user)
# Useless if target will act before the user, as we don't know what move
# will be instructed
next Battle::AI::MOVE_USELESS_SCORE if target.faster_than?(user)
next Battle::AI::MOVE_USELESS_SCORE if !target.battler.lastRegularMoveUsed
mov = nil
target.battler.eachMove do |m|
mov = m if m.id == target.battler.lastRegularMoveUsed
break if mov
end
next Battle::AI::MOVE_USELESS_SCORE if mov.nil? || (mov.pp == 0 && mov.total_pp > 0)
next Battle::AI::MOVE_USELESS_SCORE if move.move.moveBlacklist.include?(mov.function)
# Without lots of code here to determine good/bad moves, using this move is
# likely to just be a waste of a turn
# NOTE: Because this move can be used against a foe but is being used on an
# ally (since we're here in this code), this move's score will be
# inverted later. A higher score here means this move will be less
# preferred, which is the result we want.
score += 10
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveEffectScore.add("StartSlowerBattlersActFirst",
proc { |score, move, user, ai, battle|
# Get the speeds of all battlers
ally_speeds = []
foe_speeds = []
ai.each_battler do |b, i|
if b.opposes?(user)
foe_speeds.push(b.rough_stat(:SPEED))
foe_speeds.last *= 2 if user.pbOpposingSide.effects[PBEffects::Tailwind] > 1
foe_speeds.last /= 2 if user.pbOpposingSide.effects[PBEffects::Swamp] > 1
else
ally_speeds.push(b.rough_stat(:SPEED))
ally_speeds.last *= 2 if user.pbOwnSide.effects[PBEffects::Tailwind] > 1
ally_speeds.last /= 2 if user.pbOwnSide.effects[PBEffects::Swamp] > 1
end
end
# Just in case a side has no battlers
next Battle::AI::MOVE_USELESS_SCORE if ally_speeds.length == 0 || foe_speeds.length == 0
# Invert the speeds if Trick Room applies (and will last longer than this round)
if battle.field.effects[PBEffects::TrickRoom] > 1
foe_speeds.map! { |val| 100_000 - val } # 100_000 is higher than speed can
ally_speeds.map! { |val| 100_000 - val } # possibly be; only order matters
end
# Score based on the relative speeds
next Battle::AI::MOVE_USELESS_SCORE if ally_speeds.min > foe_speeds.max
if foe_speeds.min > ally_speeds.max
score += 20
elsif ally_speeds.sum / ally_speeds.length < foe_speeds.sum / foe_speeds.length
score += 10
else
score -= 10
end
next score
}
)
#===============================================================================
#
#===============================================================================
# HigherPriorityInGrassyTerrain
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("LowerPPOfTargetLastMoveBy3",
proc { |score, move, user, target, ai, battle|
add_effect = move.get_score_change_for_additional_effect(user, target)
next score if add_effect == -999 # Additional effect will be negated
if user.faster_than?(target)
last_move = target.battler.pbGetMoveWithID(target.battler.lastRegularMoveUsed)
if last_move && last_move.total_pp > 0
score += add_effect
next score + 20 if last_move.pp <= 3 # Will fully deplete the move's PP
next score + 10 if last_move.pp <= 5
next score - 10 if last_move.pp > 9 # Too much PP left to make a difference
end
end
next score # Don't know which move it will affect; treat as just a damaging move
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("LowerPPOfTargetLastMoveBy4",
proc { |move, user, target, ai, battle|
next !target.check_for_move { |m| m.id == target.battler.lastRegularMoveUsed }
}
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("LowerPPOfTargetLastMoveBy4",
proc { |score, move, user, target, ai, battle|
if user.faster_than?(target)
last_move = target.battler.pbGetMoveWithID(target.battler.lastRegularMoveUsed)
next score + 20 if last_move.pp <= 4 # Will fully deplete the move's PP
next score + 10 if last_move.pp <= 6
next score - 10 if last_move.pp > 10 # Too much PP left to make a difference
end
next score - 10 # Don't know which move it will affect; don't prefer
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("DisableTargetLastMoveUsed",
proc { |move, user, target, ai, battle|
next true if target.effects[PBEffects::Disable] > 0 || !target.battler.lastRegularMoveUsed
next true if move.move.pbMoveFailedAromaVeil?(user.battler, target.battler, false)
next !target.check_for_move { |m| m.id == target.battler.lastRegularMoveUsed }
}
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("DisableTargetLastMoveUsed",
proc { |score, move, user, target, ai, battle|
next Battle::AI::MOVE_USELESS_SCORE if target.has_active_item?(:MENTALHERB)
# Inherent preference
score += 5
# Prefer if the target is locked into using a single move, or will be
if target.effects[PBEffects::ChoiceBand] ||
target.has_active_item?([:CHOICEBAND, :CHOICESPECS, :CHOICESCARF]) ||
target.has_active_ability?(:GORILLATACTICS)
score += 10
end
# Prefer disabling a damaging move
score += 8 if GameData::Move.try_get(target.battler.lastRegularMoveUsed)&.damaging?
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("DisableTargetUsingSameMoveConsecutively",
proc { |move, user, target, ai, battle|
next true if target.effects[PBEffects::Torment]
next true if move.move.pbMoveFailedAromaVeil?(user.battler, target.battler, false)
next false
}
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("DisableTargetUsingSameMoveConsecutively",
proc { |score, move, user, target, ai, battle|
next Battle::AI::MOVE_USELESS_SCORE if target.has_active_item?(:MENTALHERB)
# Inherent preference
score += 10
# Prefer if the target is locked into using a single move, or will be
if target.effects[PBEffects::ChoiceBand] ||
target.has_active_item?([:CHOICEBAND, :CHOICESPECS, :CHOICESCARF]) ||
target.has_active_ability?(:GORILLATACTICS)
score += 10
end
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("DisableTargetUsingDifferentMove",
proc { |move, user, target, ai, battle|
next true if target.effects[PBEffects::Encore] > 0
next true if !target.battler.lastRegularMoveUsed ||
!GameData::Move.exists?(target.battler.lastRegularMoveUsed) ||
move.move.moveBlacklist.include?(GameData::Move.get(target.battler.lastRegularMoveUsed).function_code)
next true if target.effects[PBEffects::ShellTrap]
next true if move.move.pbMoveFailedAromaVeil?(user.battler, target.battler, false)
will_fail = true
next !target.check_for_move { |m| m.id == target.battler.lastRegularMoveUsed }
}
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("DisableTargetUsingDifferentMove",
proc { |score, move, user, target, ai, battle|
next Battle::AI::MOVE_USELESS_SCORE if target.has_active_item?(:MENTALHERB)
if user.faster_than?(target)
# We know which move is going to be encored (assuming the target doesn't
# use a high priority move)
move_data = GameData::Move.get(target.battler.lastRegularMoveUsed)
if move_data.status?
# Prefer encoring status moves
if [:User, :BothSides].include?(move_data.target)
score += 10
else
score += 8
end
elsif move_data.damaging? && [:NearOther, :Other].include?(move_data.target)
# Prefer encoring damaging moves depending on their type effectiveness
# against the user
eff = user.effectiveness_of_type_against_battler(move_data.type, target)
if Effectiveness.ineffective?(eff)
score += 20
elsif Effectiveness.not_very_effective?(eff)
score += 15
elsif Effectiveness.super_effective?(eff)
score -= 8
else
score += 8
end
end
else
# We don't know which move is going to be encored; just prefer limiting
# the target's options
score += 10
end
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("DisableTargetStatusMoves",
proc { |move, user, target, ai, battle|
next true if target.effects[PBEffects::Taunt] > 0
next true if move.move.pbMoveFailedAromaVeil?(user.battler, target.battler, false)
next true if Settings::MECHANICS_GENERATION >= 6 &&
!battle.moldBreaker && target.has_active_ability?(:OBLIVIOUS)
next false
}
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("DisableTargetStatusMoves",
proc { |score, move, user, target, ai, battle|
next Battle::AI::MOVE_USELESS_SCORE if !target.check_for_move { |m| m.statusMove? }
# Not worth using on a sleeping target that won't imminently wake up
if target.status == :SLEEP && target.statusCount > ((target.faster_than?(user)) ? 2 : 1)
if !target.check_for_move { |m| m.statusMove? && m.usableWhenAsleep? }
next Battle::AI::MOVE_USELESS_SCORE
end
end
# Move is likely useless if the target will lock themselves into a move,
# because they'll likely lock themselves into a damaging move
if !target.effects[PBEffects::ChoiceBand]
if target.has_active_item?([:CHOICEBAND, :CHOICESPECS, :CHOICESCARF]) ||
target.has_active_ability?(:GORILLATACTICS)
next Battle::AI::MOVE_USELESS_SCORE
end
end
# Prefer based on how many status moves the target knows
target.battler.eachMove do |m|
score += 5 if m.statusMove? && (m.pp > 0 || m.total_pp == 0)
end
# Prefer if the target has a protection move
protection_moves = [
"ProtectUser", # Detect, Protect
"ProtectUserSideFromPriorityMoves", # Quick Guard
"ProtectUserSideFromMultiTargetDamagingMoves", # Wide Guard
"UserEnduresFaintingThisTurn", # Endure
"ProtectUserSideFromDamagingMovesIfUserFirstTurn", # Mat Block
"ProtectUserSideFromStatusMoves", # Crafty Shield
"ProtectUserFromDamagingMovesKingsShield", # King's Shield
"ProtectUserFromDamagingMovesObstruct", # Obstruct
"ProtectUserFromTargetingMovesSpikyShield", # Spiky Shield
"ProtectUserBanefulBunker" # Baneful Bunker
]
if target.check_for_move { |m| m.statusMove? && protection_moves.include?(m.function) }
score += 10
end
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureAgainstTargetCheck.add("DisableTargetHealingMoves",
proc { |move, user, target, ai, battle|
next true if target.effects[PBEffects::HealBlock] > 0
next true if move.move.pbMoveFailedAromaVeil?(user.battler, target.battler, false)
next false
}
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("DisableTargetHealingMoves",
proc { |score, move, user, target, ai, battle|
# Useless if the foe can't heal themselves with a move or some held items
if !target.check_for_move { |m| m.healingMove? }
if !target.has_active_item?(:LEFTOVERS) &&
!(target.has_active_item?(:BLACKSLUDGE) && target.has_type?(:POISON))
next Battle::AI::MOVE_USELESS_SCORE
end
end
# Inherent preference
score += 10
next score
}
)
#===============================================================================
#.
#===============================================================================
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("DisableTargetSoundMoves",
proc { |score, move, user, target, ai, battle|
next score if target.effects[PBEffects::ThroatChop] > 1
next score if !target.check_for_move { |m| m.soundMove? }
# Check additional effect chance
add_effect = move.get_score_change_for_additional_effect(user, target)
next score if add_effect == -999 # Additional effect will be negated
score += add_effect
# Inherent preference
score += 8
next score
}
)
#===============================================================================
#
#===============================================================================
Battle::AI::Handlers::MoveFailureCheck.add("DisableTargetMovesKnownByUser",
proc { |move, user, ai, battle|
next user.effects[PBEffects::Imprison]
}
)
Battle::AI::Handlers::MoveEffectAgainstTargetScore.add("DisableTargetMovesKnownByUser",
proc { |score, move, user, target, ai, battle|
# Useless if the foes have no moves that the user also knows
affected_foe_count = 0
user_moves = user.battler.moves.map { |m| m.id }
ai.each_foe_battler(user.side) do |b, i|
next if !b.check_for_move { |m| user_moves.include?(m.id) }
affected_foe_count += 1
break
end
next Battle::AI::MOVE_USELESS_SCORE if affected_foe_count == 0
# Inherent preference
score += 8 * affected_foe_count
next score
}
)

View File

@@ -1,178 +0,0 @@
module PBEffects
#===========================================================================
# These effects apply to a battler
#===========================================================================
AquaRing = 0
Attract = 1
BanefulBunker = 2
BeakBlast = 3
Bide = 4
BideDamage = 5
BideTarget = 6
BurnUp = 7
Charge = 8
ChoiceBand = 9
Confusion = 10
Counter = 11
CounterTarget = 12
Curse = 13
Dancer = 14
DefenseCurl = 15
DestinyBond = 16
DestinyBondPrevious = 17
DestinyBondTarget = 18
Disable = 19
DisableMove = 20
Electrify = 21
Embargo = 22
Encore = 23
EncoreMove = 24
Endure = 25
ExtraType = 111
FirstPledge = 26
FlashFire = 27
Flinch = 28
FocusEnergy = 29
FocusPunch = 30
FollowMe = 31
Foresight = 32
FuryCutter = 33
GastroAcid = 34
GemConsumed = 35
Grudge = 36
HealBlock = 37
HelpingHand = 38
HyperBeam = 39
Illusion = 40
Imprison = 41
Ingrain = 42
Instruct = 43
Instructed = 44
JawLock = 45
KingsShield = 46
LaserFocus = 47
LeechSeed = 48
LockOn = 49
LockOnPos = 50
MagicBounce = 51
MagicCoat = 52
MagnetRise = 53
MeanLook = 54
MeFirst = 55
Metronome = 56
MicleBerry = 57
Minimize = 58
MiracleEye = 59
MirrorCoat = 60
MirrorCoatTarget = 61
MoveNext = 62
MudSport = 63
Nightmare = 64
NoRetreat = 65
Obstruct = 66
Octolock = 67
Outrage = 68
ParentalBond = 69
PerishSong = 70
PerishSongUser = 71
PickupItem = 72
PickupUse = 73
Pinch = 74 # Battle Palace only
Powder = 75
PowerTrick = 76
Prankster = 77
PriorityAbility = 78
PriorityItem = 79
Protect = 80
ProtectRate = 81
Quash = 82
Rage = 83
RagePowder = 84 # Used along with FollowMe
Rollout = 85
Roost = 86
ShellTrap = 87
SkyDrop = 88
SlowStart = 89
SmackDown = 90
Snatch = 91
SpikyShield = 92
Spotlight = 93
Stockpile = 94
StockpileDef = 95
StockpileSpDef = 96
Substitute = 97
TarShot = 98
Taunt = 99
Telekinesis = 100
ThroatChop = 101
Torment = 102
Toxic = 103
Transform = 104
TransformSpecies = 105
Trapping = 106 # Trapping move
TrappingMove = 107
TrappingUser = 108
Truant = 109
TwoTurnAttack = 110
Unburden = 112
Uproar = 113
WaterSport = 114
WeightChange = 115
Yawn = 116
#=============================================================================
# These effects apply to a battler position
#=============================================================================
FutureSightCounter = 0
FutureSightMove = 1
FutureSightUserIndex = 2
FutureSightUserPartyIndex = 3
HealingWish = 4
LunarDance = 5
Wish = 6
WishAmount = 7
WishMaker = 8
#=============================================================================
# These effects apply to a side
#=============================================================================
AuroraVeil = 0
CraftyShield = 1
EchoedVoiceCounter = 2
EchoedVoiceUsed = 3
LastRoundFainted = 4
LightScreen = 5
LuckyChant = 6
MatBlock = 7
Mist = 8
QuickGuard = 9
Rainbow = 10
Reflect = 11
Round = 12
Safeguard = 13
SeaOfFire = 14
Spikes = 15
StealthRock = 16
StickyWeb = 17
Swamp = 18
Tailwind = 19
ToxicSpikes = 20
WideGuard = 21
#=============================================================================
# These effects apply to the battle (i.e. both sides)
#=============================================================================
AmuletCoin = 0
FairyLock = 1
FusionBolt = 2
FusionFlare = 3
Gravity = 4
HappyHour = 5
IonDeluge = 6
MagicRoom = 7
MudSportField = 8
PayDay = 9
TrickRoom = 10
WaterSportField = 11
WonderRoom = 12
end

View File

@@ -0,0 +1,178 @@
module PBEffects
#===========================================================================
# These effects apply to a battler
#===========================================================================
AquaRing = 0
Attract = 1
BanefulBunker = 2
BeakBlast = 3
Bide = 4
BideDamage = 5
BideTarget = 6
BurnUp = 7
Charge = 8
ChoiceBand = 9
Confusion = 10
Counter = 11
CounterTarget = 12
Curse = 13
Dancer = 14
DefenseCurl = 15
DestinyBond = 16
DestinyBondPrevious = 17
DestinyBondTarget = 18
Disable = 19
DisableMove = 20
Electrify = 21
Embargo = 22
Encore = 23
EncoreMove = 24
Endure = 25
ExtraType = 26
FirstPledge = 27
FlashFire = 28
Flinch = 29
FocusEnergy = 30
FocusPunch = 31
FollowMe = 32
Foresight = 33
FuryCutter = 34
GastroAcid = 35
GemConsumed = 36
Grudge = 37
HealBlock = 38
HelpingHand = 39
HyperBeam = 40
Illusion = 41
Imprison = 42
Ingrain = 43
Instruct = 44
Instructed = 45
JawLock = 46
KingsShield = 47
LaserFocus = 48
LeechSeed = 49
LockOn = 50
LockOnPos = 51
MagicBounce = 52
MagicCoat = 53
MagnetRise = 54
MeanLook = 55
MeFirst = 56
Metronome = 57
MicleBerry = 58
Minimize = 59
MiracleEye = 60
MirrorCoat = 61
MirrorCoatTarget = 62
MoveNext = 63
MudSport = 64
Nightmare = 65
NoRetreat = 66
Obstruct = 67
Octolock = 68
Outrage = 69
ParentalBond = 70
PerishSong = 71
PerishSongUser = 72
PickupItem = 73
PickupUse = 74
Pinch = 75 # Battle Palace only
Powder = 76
PowerTrick = 77
Prankster = 78
PriorityAbility = 79
PriorityItem = 80
Protect = 81
ProtectRate = 82
Quash = 83
Rage = 84
RagePowder = 85 # Used along with FollowMe
Rollout = 86
Roost = 87
ShellTrap = 88
SkyDrop = 89
SlowStart = 90
SmackDown = 91
Snatch = 92
SpikyShield = 93
Spotlight = 94
Stockpile = 95
StockpileDef = 96
StockpileSpDef = 97
Substitute = 98
TarShot = 99
Taunt = 100
Telekinesis = 101
ThroatChop = 102
Torment = 103
Toxic = 104
Transform = 105
TransformSpecies = 106
Trapping = 107 # Trapping move that deals EOR damage
TrappingMove = 108
TrappingUser = 109
Truant = 110
TwoTurnAttack = 111
Unburden = 112
Uproar = 113
WaterSport = 114
WeightChange = 115
Yawn = 116
#=============================================================================
# These effects apply to a battler position
#=============================================================================
FutureSightCounter = 700
FutureSightMove = 701
FutureSightUserIndex = 702
FutureSightUserPartyIndex = 703
HealingWish = 704
LunarDance = 705
Wish = 706
WishAmount = 707
WishMaker = 708
#=============================================================================
# These effects apply to a side
#=============================================================================
AuroraVeil = 800
CraftyShield = 801
EchoedVoiceCounter = 802
EchoedVoiceUsed = 803
LastRoundFainted = 804
LightScreen = 805
LuckyChant = 806
MatBlock = 807
Mist = 808
QuickGuard = 809
Rainbow = 810
Reflect = 811
Round = 812
Safeguard = 813
SeaOfFire = 814
Spikes = 815
StealthRock = 816
StickyWeb = 817
Swamp = 818
Tailwind = 819
ToxicSpikes = 820
WideGuard = 821
#=============================================================================
# These effects apply to the battle (i.e. both sides)
#=============================================================================
AmuletCoin = 900
FairyLock = 901
FusionBolt = 902
FusionFlare = 903
Gravity = 904
HappyHour = 905
IonDeluge = 906
MagicRoom = 907
MudSportField = 908
PayDay = 909
TrickRoom = 910
WaterSportField = 911
WonderRoom = 912
end

View File

@@ -248,7 +248,7 @@ class Battle::Move::UserFaintsExplosive
def pbMoveFailed?(user, targets)
if @battle.rules["selfkoclause"]
# Check whether no unfainted Pokemon remain in either party
count = @battle.pbAbleNonActiveCount(user.idxOwnSide)
count = @battle.pbAbleNonActiveCount(user.idxOwnSide)
count += @battle.pbAbleNonActiveCount(user.idxOpposingSide)
if count == 0
@battle.pbDisplay("But it failed!")
@@ -257,7 +257,7 @@ class Battle::Move::UserFaintsExplosive
end
if @battle.rules["selfdestructclause"]
# Check whether no unfainted Pokemon remain in either party
count = @battle.pbAbleNonActiveCount(user.idxOwnSide)
count = @battle.pbAbleNonActiveCount(user.idxOwnSide)
count += @battle.pbAbleNonActiveCount(user.idxOpposingSide)
if count == 0
@battle.pbDisplay(_INTL("{1}'s team was disqualified!", user.pbThis))

View File

@@ -381,7 +381,7 @@ Battle::AbilityEffects::OnHPDroppedBelowHalf.add(:EMERGENCYEXIT,
end
# In trainer battles
next false if battle.pbAllFainted?(battler.idxOpposingSide)
next false if !battle.pbCanSwitch?(battler.index) # Battler can't switch out
next false if !battle.pbCanSwitchOut?(battler.index) # Battler can't switch out
next false if !battle.pbCanChooseNonActive?(battler.index) # No Pokémon can switch in
battle.pbShowAbilitySplash(battler, true)
battle.pbHideAbilitySplash(battler)
@@ -1200,7 +1200,7 @@ Battle::AbilityEffects::DamageCalcFromUser.add(:AERILATE,
}
)
Battle::AbilityEffects::DamageCalcFromUser.copy(:AERILATE, :PIXILATE, :REFRIGERATE, :GALVANIZE, :NORMALIZE)
Battle::AbilityEffects::DamageCalcFromUser.copy(:AERILATE, :GALVANIZE, :NORMALIZE, :PIXILATE, :REFRIGERATE)
Battle::AbilityEffects::DamageCalcFromUser.add(:ANALYTIC,
proc { |ability, user, target, move, mults, power, type|
@@ -1372,6 +1372,12 @@ Battle::AbilityEffects::DamageCalcFromUser.add(:SLOWSTART,
}
)
Battle::AbilityEffects::DamageCalcFromUser.add(:SNIPER,
proc { |ability, user, target, move, mults, power, type|
mults[:final_damage_multiplier] *= 1.5 if target.damageState.critical
}
)
Battle::AbilityEffects::DamageCalcFromUser.add(:SOLARPOWER,
proc { |ability, user, target, move, mults, power, type|
if move.specialMove? && [:Sun, :HarshSun].include?(user.effectiveWeather)
@@ -1380,12 +1386,6 @@ Battle::AbilityEffects::DamageCalcFromUser.add(:SOLARPOWER,
}
)
Battle::AbilityEffects::DamageCalcFromUser.add(:SNIPER,
proc { |ability, user, target, move, mults, power, type|
mults[:final_damage_multiplier] *= 1.5 if target.damageState.critical
}
)
Battle::AbilityEffects::DamageCalcFromUser.add(:STAKEOUT,
proc { |ability, user, target, move, mults, power, type|
mults[:attack_multiplier] *= 2 if target.battle.choices[target.index][0] == :SwitchOut
@@ -1420,7 +1420,7 @@ Battle::AbilityEffects::DamageCalcFromUser.add(:SWARM,
Battle::AbilityEffects::DamageCalcFromUser.add(:TECHNICIAN,
proc { |ability, user, target, move, mults, power, type|
if user.index != target.index && move && move.id != :STRUGGLE &&
if user.index != target.index && move && move.function != "Struggle" &&
power * mults[:power_multiplier] <= 60
mults[:power_multiplier] *= 1.5
end
@@ -1693,7 +1693,7 @@ Battle::AbilityEffects::OnBeingHit.add(:ANGERPOINT,
next if !target.damageState.critical
next if !target.pbCanRaiseStatStage?(:ATTACK, target)
battle.pbShowAbilitySplash(target)
target.stages[:ATTACK] = 6
target.stages[:ATTACK] = Battle::Battler::STAT_STAGE_MAXIMUM
target.statsRaisedThisRound = true
battle.pbCommonAnimation("StatUp", target)
if Battle::Scene::USE_ABILITY_SPLASH

View File

@@ -224,6 +224,12 @@ Battle::ItemEffects::SpeedCalc.add(:CHOICESCARF,
}
)
Battle::ItemEffects::SpeedCalc.add(:IRONBALL,
proc { |item, battler, mult|
next mult / 2
}
)
Battle::ItemEffects::SpeedCalc.add(:MACHOBRACE,
proc { |item, battler, mult|
next mult / 2
@@ -236,14 +242,7 @@ Battle::ItemEffects::SpeedCalc.copy(:MACHOBRACE, :POWERANKLET, :POWERBAND,
Battle::ItemEffects::SpeedCalc.add(:QUICKPOWDER,
proc { |item, battler, mult|
next mult * 2 if battler.isSpecies?(:DITTO) &&
!battler.effects[PBEffects::Transform]
}
)
Battle::ItemEffects::SpeedCalc.add(:IRONBALL,
proc { |item, battler, mult|
next mult / 2
next mult * 2 if battler.isSpecies?(:DITTO) && !battler.effects[PBEffects::Transform]
}
)
@@ -263,7 +262,7 @@ Battle::ItemEffects::WeightCalc.add(:FLOATSTONE,
Battle::ItemEffects::HPHeal.add(:AGUAVBERRY,
proc { |item, battler, battle, forced|
next battler.pbConfusionBerry(item, forced, 4,
next battler.pbConfusionBerry(item, forced, :SPECIAL_DEFENSE,
_INTL("For {1}, the {2} was too bitter!", battler.pbThis(true), GameData::Item.get(item).name)
)
}
@@ -294,7 +293,7 @@ Battle::ItemEffects::HPHeal.add(:BERRYJUICE,
Battle::ItemEffects::HPHeal.add(:FIGYBERRY,
proc { |item, battler, battle, forced|
next battler.pbConfusionBerry(item, forced, 0,
next battler.pbConfusionBerry(item, forced, :ATTACK,
_INTL("For {1}, the {2} was too spicy!", battler.pbThis(true), GameData::Item.get(item).name)
)
}
@@ -308,7 +307,7 @@ Battle::ItemEffects::HPHeal.add(:GANLONBERRY,
Battle::ItemEffects::HPHeal.add(:IAPAPABERRY,
proc { |item, battler, battle, forced|
next battler.pbConfusionBerry(item, forced, 1,
next battler.pbConfusionBerry(item, forced, :DEFENSE,
_INTL("For {1}, the {2} was too sour!", battler.pbThis(true), GameData::Item.get(item).name)
)
}
@@ -338,7 +337,7 @@ Battle::ItemEffects::HPHeal.add(:LIECHIBERRY,
Battle::ItemEffects::HPHeal.add(:MAGOBERRY,
proc { |item, battler, battle, forced|
next battler.pbConfusionBerry(item, forced, 2,
next battler.pbConfusionBerry(item, forced, :SPEED,
_INTL("For {1}, the {2} was too sweet!", battler.pbThis(true), GameData::Item.get(item).name)
)
}
@@ -436,7 +435,7 @@ Battle::ItemEffects::HPHeal.add(:STARFBERRY,
Battle::ItemEffects::HPHeal.add(:WIKIBERRY,
proc { |item, battler, battle, forced|
next battler.pbConfusionBerry(item, forced, 3,
next battler.pbConfusionBerry(item, forced, :SPECIAL_ATTACK,
_INTL("For {1}, the {2} was too dry!", battler.pbThis(true), GameData::Item.get(item).name)
)
}
@@ -451,7 +450,7 @@ Battle::ItemEffects::OnStatLoss.add(:EJECTPACK,
battler.inTwoTurnAttack?("TwoTurnAttackInvulnerableInSkyTargetCannotAct") # Sky Drop
next false if battle.pbAllFainted?(battler.idxOpposingSide)
next false if battler.wild? # Wild Pokémon can't eject
next false if !battle.pbCanSwitch?(battler.index) # Battler can't switch out
next false if !battle.pbCanSwitchOut?(battler.index) # Battler can't switch out
next false if !battle.pbCanChooseNonActive?(battler.index) # No Pokémon can switch in
battle.pbCommonAnimation("UseItem", battler)
battle.pbDisplay(_INTL("{1} is switched out by the {2}!", battler.pbThis, battler.itemName))

View File

@@ -177,13 +177,14 @@ class Battle::AI
@justswitched = [false, false, false, false]
end
unless method_defined?(:_battlePalace_pbEnemyShouldWithdraw?)
alias _battlePalace_pbEnemyShouldWithdraw? pbEnemyShouldWithdraw?
unless method_defined?(:_battlePalace_pbChooseToSwitchOut)
alias _battlePalace_pbChooseToSwitchOut pbChooseToSwitchOut
end
def pbEnemyShouldWithdraw?(idxBattler)
return _battlePalace_pbEnemyShouldWithdraw?(idxBattler) if !@battlePalace
thispkmn = @battle.battlers[idxBattler]
def pbChooseToSwitchOut(force_switch = false)
return _battlePalace_pbChooseToSwitchOut(force_switch) if !@battlePalace
thispkmn = @user
idxBattler = @user.index
shouldswitch = false
if thispkmn.effects[PBEffects::PerishSong] == 1
shouldswitch = true

View File

@@ -50,7 +50,7 @@ class BattleArenaBattle < Battle
@battleAI.battleArena = true
end
def pbCanSwitchLax?(idxBattler, _idxParty, partyScene = nil)
def pbCanSwitchIn?(idxBattler, _idxParty, partyScene = nil)
partyScene&.pbDisplay(_INTL("{1} can't be switched out!", @battlers[idxBattler].pbThis))
return false
end
@@ -207,12 +207,12 @@ end
class Battle::AI
attr_accessor :battleArena
unless method_defined?(:_battleArena_pbEnemyShouldWithdraw?)
alias _battleArena_pbEnemyShouldWithdraw? pbEnemyShouldWithdraw?
unless method_defined?(:_battleArena_pbChooseToSwitchOut)
alias _battleArena_pbChooseToSwitchOut pbChooseToSwitchOut
end
def pbEnemyShouldWithdraw?(idxBattler)
return _battleArena_pbEnemyShouldWithdraw?(idxBattler) if !@battleArena
def pbChooseToSwitchOut(force_switch = false)
return _battleArena_pbChooseToSwitchOut(force_switch) if !@battleArena
return false
end
end

View File

@@ -143,7 +143,7 @@ ItemHandlers::CanUseInBattle.add(:FULLRESTORE, proc { |item, pokemon, battler, m
})
ItemHandlers::CanUseInBattle.add(:REVIVE, proc { |item, pokemon, battler, move, firstAction, battle, scene, showMessages|
if pokemon.able? || pokemon.egg?
if !pokemon.fainted?
scene.pbDisplay(_INTL("It won't have any effect.")) if showMessages
next false
end

View File

@@ -352,7 +352,7 @@ end
#===============================================================================
class Battle::Move::UserLosesHalfHP < Battle::Move::RecoilMove
def pbRecoilDamage(user, target)
return (target.damageState.totalHPLost / 2.0).round
return (user.hp / 2.0).round
end
def pbEffectAfterAllHits(user, target)
@@ -380,12 +380,27 @@ end
# Ends the effects of Light Screen, Reflect and Safeguard on both sides.
# (Shadow Shed)
#===============================================================================
class Battle::Move::RemoveAllScreens < Battle::Move
class Battle::Move::RemoveAllScreensAndSafeguard < Battle::Move
def pbMoveFailed?(user, targets)
will_fail = true
@battle.sides.each do |side|
will_fail = false if side.effects[PBEffects::AuroraVeil] > 0 ||
side.effects[PBEffects::LightScreen] > 0 ||
side.effects[PBEffects::Reflect] > 0 ||
side.effects[PBEffects::Safeguard] > 0
end
if will_fail
@battle.pbDisplay(_INTL("But it failed!"))
return true
end
return false
end
def pbEffectGeneral(user)
@battle.sides.each do |i|
i.effects[PBEffects::AuroraVeil] = 0
i.effects[PBEffects::Reflect] = 0
i.effects[PBEffects::LightScreen] = 0
i.effects[PBEffects::Reflect] = 0
i.effects[PBEffects::Safeguard] = 0
end
@battle.pbDisplay(_INTL("It broke all barriers!"))

View File

@@ -540,7 +540,7 @@ class Pokemon
# @return [Boolean] whether this Pokémon has a particular nature or a nature
# at all
def hasNature?(check_nature = nil)
return !@nature_id.nil? if check_nature.nil?
return !@nature.nil? if check_nature.nil?
return self.nature == check_nature
end

View File

@@ -47,18 +47,21 @@ class Pokemon
end
alias totalpp total_pp
def function_code; return GameData::Move.get(@id).function_code; end
def power; return GameData::Move.get(@id).power; end
def type; return GameData::Move.get(@id).type; end
def category; return GameData::Move.get(@id).category; end
def accuracy; return GameData::Move.get(@id).accuracy; end
def effect_chance; return GameData::Move.get(@id).effect_chance; end
def target; return GameData::Move.get(@id).target; end
def priority; return GameData::Move.get(@id).priority; end
def flags; return GameData::Move.get(@id).flags; end
def name; return GameData::Move.get(@id).name; end
def description; return GameData::Move.get(@id).description; end
def hidden_move?; return GameData::Move.get(@id).hidden_move?; end
def function_code; return GameData::Move.get(@id).function_code; end
def power; return GameData::Move.get(@id).power; end
def type; return GameData::Move.get(@id).type; end
def category; return GameData::Move.get(@id).category; end
def physical_move?; return GameData::Move.get(@id).physical?; end
def special_move?; return GameData::Move.get(@id).special?; end
def status_move?; return GameData::Move.get(@id).status?; end
def accuracy; return GameData::Move.get(@id).accuracy; end
def effect_chance; return GameData::Move.get(@id).effect_chance; end
def target; return GameData::Move.get(@id).target; end
def priority; return GameData::Move.get(@id).priority; end
def flags; return GameData::Move.get(@id).flags; end
def name; return GameData::Move.get(@id).name; end
def description; return GameData::Move.get(@id).description; end
def hidden_move?; return GameData::Move.get(@id).hidden_move?; end
# @deprecated This method is slated to be removed in v22.
def base_damage

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(self.trainer_type)&.skill_level || 0
end
#=============================================================================
# Portion of the ID which is visible on the Trainer Card

View File

@@ -1,3 +1,9 @@
# NOTE: The following clauses have battle code implementing them, but no class
# below to apply them:
# "drawclause"
# "modifiedselfdestructclause"
# "suddendeath"
#===============================================================================
#
#===============================================================================
@@ -58,7 +64,7 @@ end
#
#===============================================================================
class PerishSongClause < BattleRule
def setRule(battle); battle.rules["perishsong"] = true; end
def setRule(battle); battle.rules["perishsongclause"] = true; end
end
#===============================================================================

View File

@@ -42,7 +42,7 @@ def pbRandomMove
loop do
move_id = keys.sample
move = GameData::Move.get(move_id)
next if move.id == :SKETCH || move.id == :STRUGGLE
next if ["Struggle", "ReplaceMoveWithTargetLastMoveUsed"].include?(move.function_code)
return move.id
end
end

View File

@@ -401,7 +401,7 @@ def pbRuledBattle(team1, team2, rule)
items2[i] = p.item_id
trainer2.party.push(p)
end
scene = Battle::DebugSceneNoLogging.new
scene = Battle::DebugSceneNoVisuals.new
battle = rule.createBattle(scene, trainer1, trainer2)
battle.debug = true
battle.controlPlayer = true

View File

@@ -162,7 +162,7 @@ MenuHandlers.add(:battle_pokemon_debug_menu, :set_stat_stages, {
break if cmd < 0
if cmd < stat_ids.length # Set a stat
params = ChooseNumberParams.new
params.setRange(-6, 6)
params.setRange(-Battle::Battler::STAT_STAGE_MAXIMUM, Battle::Battler::STAT_STAGE_MAXIMUM)
params.setNegativesAllowed(true)
params.setDefaultValue(battler.stages[stat_ids[cmd]])
value = pbMessageChooseNumber(

View File

@@ -0,0 +1,82 @@
#===============================================================================
#
#===============================================================================
def marListMoveTargetFunctionCodes
target_hash = {}
function_hash = {}
GameData::Move.each do |move|
target_hash[move.target] ||= []
target_hash[move.target].push(move.function_code)
function_hash[move.function_code] ||= []
function_hash[move.function_code].push(move.target)
end
# Write results to file
File.open("moves_by_target.txt", "wb") { |f|
f.write(0xEF.chr)
f.write(0xBB.chr)
f.write(0xBF.chr)
f.write("=========================================================\r\n")
f.write("SORTED BY TARGET\r\n")
f.write("=========================================================\r\n\r\n")
target_keys = target_hash.keys.sort { |a, b| a.downcase <=> b.downcase }
target_keys.each do |key|
next if !key || !target_hash[key] || target_hash[key].length == 0
f.write("===== #{key} =====\r\n\r\n")
arr = target_hash[key].uniq.sort
arr.each { |code| f.write("#{code}\r\n")}
f.write("\r\n")
end
}
File.open("moves_by_function_code.txt", "wb") { |f|
f.write(0xEF.chr)
f.write(0xBB.chr)
f.write(0xBF.chr)
f.write("=========================================================\r\n")
f.write("SORTED BY FUNCTION CODE\r\n")
f.write("=========================================================\r\n\r\n")
code_keys = function_hash.keys.sort { |a, b| a.downcase <=> b.downcase }
code_keys.each do |key|
next if !key || !function_hash[key] || function_hash[key].length == 0
f.write("===== #{key} =====\r\n\r\n")
function_hash[key].sort.each { |target| f.write("#{target}\r\n")}
f.write("\r\n")
end
}
File.open("moves_by_function_code_multiple_target_types_only.txt", "wb") { |f|
f.write(0xEF.chr)
f.write(0xBB.chr)
f.write(0xBF.chr)
f.write("=========================================================\r\n")
f.write("SORTED BY FUNCTION CODE\r\n")
f.write("=========================================================\r\n\r\n")
code_keys = function_hash.keys.sort { |a, b| a.downcase <=> b.downcase }
code_keys.each do |key|
next if !key || !function_hash[key] || function_hash[key].length == 0
next if function_hash[key].uniq.length <= 1
f.write("===== #{key} =====\r\n\r\n")
function_hash[key].sort.each { |target| f.write("#{target}\r\n")}
f.write("\r\n")
end
}
end
#===============================================================================
# Add to Debug menu
#===============================================================================
MenuHandlers.add(:debug_menu, :print_move_target_functions, {
"name" => _INTL("Print Out Move Targets"),
"parent" => :main,
"description" => _INTL("Print all blah blah blah."),
"effect" => proc {
marListMoveTargetFunctionCodes
}
})

View File

@@ -0,0 +1,158 @@
# TODO: Better randomisation of moves, including tracking of how many times each
# function code has been tested (note that some Pokémon may not be used in
# battle, so their moves won't be score).
# TODO: Add held items.
#===============================================================================
#
#===============================================================================
def debug_set_up_trainer
# Values to return
trainer_array = []
foe_items = [] # Items can't be used except in internal battles
pokemon_array = []
party_starts = [0]
# Choose random trainer type and trainer name
trainer_type = :CHAMPION # GameData::TrainerType.keys.sample
trainer_name = ["Alpha", "Bravo", "Charlie", "Delta", "Echo",
"Foxtrot", "Golf", "Hotel", "India", "Juliette",
"Kilo", "Lima", "Mike", "November", "Oscar",
"Papa", "Quebec", "Romeo", "Sierra", "Tango",
"Uniform", "Victor", "Whiskey", "X-ray", "Yankee", "Zulu"].sample
# Generate trainer
trainer = NPCTrainer.new(trainer_name, trainer_type)
trainer.id = $player.make_foreign_ID
trainer.lose_text = "I lost."
# [:MAXPOTION, :FULLHEAL, :MAXREVIVE, :REVIVE].each do |item|
# trainer.items.push(item)
# end
# foe_items.push(trainer.items)
trainer_array.push(trainer)
# Generate party
valid_species = []
GameData::Species.each_species { |sp| valid_species.push(sp.species) }
Settings::MAX_PARTY_SIZE.times do |i|
this_species = valid_species.sample
this_level = 100 # rand(1, Settings::MAXIMUM_LEVEL)
pkmn = Pokemon.new(this_species, this_level, trainer, false)
all_moves = pkmn.getMoveList.map { |m| m[1] }
all_moves.uniq!
moves = all_moves.sample(4)
moves.each { |m| pkmn.learn_move(m) }
trainer.party.push(pkmn)
pokemon_array.push(pkmn)
end
# Return values
return trainer_array, foe_items, pokemon_array, party_starts
end
def debug_test_auto_battle(logging = false, console_messages = true)
old_internal = $INTERNAL
$INTERNAL = logging
if console_messages
echoln "Start of testing auto-battle."
echoln "" if !$INTERNAL
end
PBDebug.log("")
PBDebug.log("================================================================")
PBDebug.log("")
# Generate information for the foes
foe_trainers, foe_items, foe_party, foe_party_starts = debug_set_up_trainer
# Generate information for the player and partner trainer(s)
player_trainers, ally_items, player_party, player_party_starts = debug_set_up_trainer
# Log the combatants
echo_participant = lambda do |trainer, party, index|
trainer_txt = "[Trainer #{index}] #{trainer.full_name} [skill: #{trainer.skill_level}]"
($INTERNAL) ? PBDebug.log_header(trainer_txt) : echoln(trainer_txt)
party.each do |pkmn|
pkmn_txt = "#{pkmn.name}, Lv.#{pkmn.level}"
pkmn_txt += " [Ability: #{pkmn.ability&.name || "---"}]"
pkmn_txt += " [Item: #{pkmn.item&.name || "---"}]"
($INTERNAL) ? PBDebug.log(pkmn_txt) : echoln(pkmn_txt)
moves_msg = " Moves: "
pkmn.moves.each_with_index do |move, i|
moves_msg += ", " if i > 0
moves_msg += move.name
end
($INTERNAL) ? PBDebug.log(moves_msg) : echoln(moves_msg)
end
end
echo_participant.call(player_trainers[0], player_party, 1) if console_messages
PBDebug.log("")
if console_messages
echoln "" if !$INTERNAL
echo_participant.call(foe_trainers[0], foe_party, 2)
echoln "" if !$INTERNAL
end
# Create the battle scene (the visual side of it)
scene = Battle::DebugSceneNoVisuals.new(logging)
# Create the battle class (the mechanics side of it)
battle = Battle.new(scene, player_party, foe_party, player_trainers, foe_trainers)
battle.party1starts = player_party_starts
battle.party2starts = foe_party_starts
battle.ally_items = ally_items
battle.items = foe_items
battle.debug = true
battle.internalBattle = false
battle.controlPlayer = true
# Set various other properties in the battle class
BattleCreationHelperMethods.prepare_battle(battle)
# Perform the battle itself
outcome = battle.pbStartBattle
# End
if console_messages
text = ["Undecided",
"Trainer 1 #{player_trainers[0].name} won",
"Trainer 2 #{foe_trainers[0].name} won",
"Ran/forfeited",
"Wild Pokémon caught",
"Draw"][outcome]
echoln sprintf("%s after %d rounds", text, battle.turnCount + 1)
echoln ""
end
$INTERNAL = old_internal
end
#===============================================================================
# Add to Debug menu.
#===============================================================================
MenuHandlers.add(:debug_menu, :test_auto_battle, {
"name" => "Test Auto Battle",
"parent" => :main,
"description" => "Runs an AI-controlled battle with no visuals.",
"always_show" => false,
"effect" => proc {
debug_test_auto_battle
}
})
MenuHandlers.add(:debug_menu, :test_auto_battle_logging, {
"name" => "Test Auto Battle with Logging",
"parent" => :main,
"description" => "Runs an AI-controlled battle with no visuals. Logs messages.",
"always_show" => false,
"effect" => proc {
debug_test_auto_battle(true)
pbMessage("Battle transcript was logged in Data/debuglog.txt.")
}
})
MenuHandlers.add(:debug_menu, :bulk_test_auto_battle, {
"name" => "Bulk Test Auto Battle",
"parent" => :main,
"description" => "Runs 50 AI-controlled battles with no visuals.",
"always_show" => false,
"effect" => proc {
echoln "Running 50 battles.."
50.times do |i|
echoln "#{i + 1}..."
debug_test_auto_battle(false, false)
end
echoln "Done!"
}
})