mirror of
https://github.com/infinitefusion/infinitefusion-e18.git
synced 2025-12-09 06:04:59 +00:00
Merge branch 'ai' into dev
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
675
Data/Scripts/011_Battle/005_AI/002_AI_Switch.rb
Normal file
675
Data/Scripts/011_Battle/005_AI/002_AI_Switch.rb
Normal 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
|
||||
}
|
||||
)
|
||||
@@ -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
|
||||
220
Data/Scripts/011_Battle/005_AI/003_AI_UseItem.rb
Normal file
220
Data/Scripts/011_Battle/005_AI/003_AI_UseItem.rb
Normal 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
|
||||
13
Data/Scripts/011_Battle/005_AI/004_AI_MegaEvolve.rb
Normal file
13
Data/Scripts/011_Battle/005_AI/004_AI_MegaEvolve.rb
Normal 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
|
||||
@@ -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
|
||||
389
Data/Scripts/011_Battle/005_AI/005_AI_ChooseMove.rb
Normal file
389
Data/Scripts/011_Battle/005_AI/005_AI_ChooseMove.rb
Normal 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
@@ -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
|
||||
@@ -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
|
||||
517
Data/Scripts/011_Battle/005_AI/007_AI_ChooseMove_OtherScores.rb
Normal file
517
Data/Scripts/011_Battle/005_AI/007_AI_ChooseMove_OtherScores.rb
Normal 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
@@ -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
|
||||
899
Data/Scripts/011_Battle/005_AI/008_AI_Utilities.rb
Normal file
899
Data/Scripts/011_Battle/005_AI/008_AI_Utilities.rb
Normal 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
|
||||
}
|
||||
)
|
||||
100
Data/Scripts/011_Battle/005_AI/010_AITrainer.rb
Normal file
100
Data/Scripts/011_Battle/005_AI/010_AITrainer.rb
Normal 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
|
||||
595
Data/Scripts/011_Battle/005_AI/011_AIBattler.rb
Normal file
595
Data/Scripts/011_Battle/005_AI/011_AIBattler.rb
Normal 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
|
||||
562
Data/Scripts/011_Battle/005_AI/012_AIMove.rb
Normal file
562
Data/Scripts/011_Battle/005_AI/012_AIMove.rb
Normal 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
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
)
|
||||
@@ -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
|
||||
}
|
||||
)
|
||||
@@ -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
|
||||
}
|
||||
)
|
||||
@@ -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")
|
||||
@@ -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
|
||||
}
|
||||
)
|
||||
@@ -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
|
||||
178
Data/Scripts/011_Battle/007_Other battle code/001_PBEffects.rb
Normal file
178
Data/Scripts/011_Battle/007_Other battle code/001_PBEffects.rb
Normal 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
|
||||
@@ -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))
|
||||
@@ -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
|
||||
@@ -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))
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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!"))
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
#===============================================================================
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
82
Data/Scripts/022_Maruno/001_move target lister.rb
Normal file
82
Data/Scripts/022_Maruno/001_move target lister.rb
Normal 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
|
||||
}
|
||||
})
|
||||
158
Data/Scripts/022_Maruno/debug battle tests.rb
Normal file
158
Data/Scripts/022_Maruno/debug battle tests.rb
Normal 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!"
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user