Files
infinitefusion-e18/Data/Scripts/011_Battle/002_Battler/001_Battle_Battler.rb

772 lines
24 KiB
Ruby

class Battle::Battler
# Fundamental to this object
attr_reader :battle
attr_accessor :index
# The Pokémon and its properties
attr_reader :pokemon
attr_accessor :pokemonIndex
attr_accessor :species
attr_accessor :types
attr_accessor :ability_id
attr_accessor :item_id
attr_accessor :moves
attr_accessor :attack
attr_accessor :spatk
attr_accessor :speed
attr_accessor :stages
attr_reader :totalhp
attr_reader :fainted # Boolean to mark whether self has fainted properly
attr_accessor :captured # Boolean to mark whether self was captured
attr_reader :dummy
attr_accessor :effects
# Things the battler has done in battle
attr_accessor :turnCount
attr_accessor :participants
attr_accessor :lastAttacker
attr_accessor :lastFoeAttacker
attr_accessor :lastHPLost
attr_accessor :lastHPLostFromFoe
attr_accessor :lastMoveUsed
attr_accessor :lastMoveUsedType
attr_accessor :lastRegularMoveUsed
attr_accessor :lastRegularMoveTarget # For Instruct
attr_accessor :lastRoundMoved
attr_accessor :lastMoveFailed # For Stomping Tantrum
attr_accessor :lastRoundMoveFailed # For Stomping Tantrum
attr_accessor :movesUsed
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 :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
attr_accessor :statsLoweredThisRound # Boolean for whether self's stat(s) lowered this round
attr_accessor :canRestoreIceFace # Whether Hail started in the round
attr_accessor :damageState
#=============================================================================
# Complex accessors
#=============================================================================
attr_reader :level
def level=(value)
@level = value
@pokemon.level = value if @pokemon
end
attr_reader :form
def form=(value)
@form = value
@pokemon.form = value if @pokemon
end
def ability
return GameData::Ability.try_get(@ability_id)
end
def ability=(value)
new_ability = GameData::Ability.try_get(value)
@ability_id = (new_ability) ? new_ability.id : nil
end
def item
return GameData::Item.try_get(@item_id)
end
def item=(value)
new_item = GameData::Item.try_get(value)
@item_id = (new_item) ? new_item.id : nil
@pokemon.item = @item_id if @pokemon
end
def defense
return @spdef if @battle.field.effects[PBEffects::WonderRoom] > 0
return @defense
end
attr_writer :defense
def spdef
return @defense if @battle.field.effects[PBEffects::WonderRoom] > 0
return @spdef
end
attr_writer :spdef
attr_reader :hp
def hp=(value)
@hp = value.to_i
@pokemon.hp = value.to_i if @pokemon
end
def fainted?; return @hp <= 0; end
attr_reader :status
def status=(value)
@effects[PBEffects::Truant] = false if @status == :SLEEP && value != :SLEEP
@effects[PBEffects::Toxic] = 0 if value != :POISON || self.statusCount == 0
@status = value
@pokemon.status = value if @pokemon
self.statusCount = 0 if value != :POISON && value != :SLEEP
@battle.scene.pbRefreshOne(@index)
end
attr_reader :statusCount
def statusCount=(value)
@statusCount = value
@pokemon.statusCount = value if @pokemon
@battle.scene.pbRefreshOne(@index)
end
#=============================================================================
# Properties from Pokémon
#=============================================================================
def happiness; return @pokemon ? @pokemon.happiness : 0; end
def affection_level; return @pokemon ? @pokemon.affection_level : 2; end
def gender; return @pokemon ? @pokemon.gender : 0; end
def nature; return @pokemon ? @pokemon.nature : nil; end
def pokerusStage; return @pokemon ? @pokemon.pokerusStage : 0; end
#=============================================================================
# Mega Evolution, Primal Reversion, Shadow Pokémon
#=============================================================================
def hasMega?
return false if @effects[PBEffects::Transform]
return @pokemon&.hasMegaForm?
end
def mega?; return @pokemon&.mega?; end
def hasPrimal?
return false if @effects[PBEffects::Transform]
return @pokemon&.hasPrimalForm?
end
def primal?; return @pokemon&.primal?; end
def shadowPokemon?; return false; end
def inHyperMode?; return false; end
#=============================================================================
# Display-only properties
#=============================================================================
def name
return @effects[PBEffects::Illusion].name if @effects[PBEffects::Illusion]
return @name
end
attr_writer :name
def displayPokemon
return @effects[PBEffects::Illusion] if @effects[PBEffects::Illusion]
return self.pokemon
end
def displaySpecies
return @effects[PBEffects::Illusion].species if @effects[PBEffects::Illusion]
return self.species
end
def displayGender
return @effects[PBEffects::Illusion].gender if @effects[PBEffects::Illusion]
return self.gender
end
def displayForm
return @effects[PBEffects::Illusion].form if @effects[PBEffects::Illusion]
return self.form
end
def shiny?
return @effects[PBEffects::Illusion].shiny? if @effects[PBEffects::Illusion]
return @pokemon&.shiny?
end
def super_shiny?
return @pokemon&.super_shiny?
end
def owned?
return false if !@battle.wildBattle?
return $player.owned?(displaySpecies)
end
alias owned owned?
def abilityName
abil = self.ability
return (abil) ? abil.name : ""
end
def itemName
itm = self.item
return (itm) ? itm.name : ""
end
def pbThis(lowerCase = false)
if opposes?
if @battle.trainerBattle?
return lowerCase ? _INTL("the opposing {1}", name) : _INTL("The opposing {1}", name)
else
return lowerCase ? _INTL("the wild {1}", name) : _INTL("The wild {1}", name)
end
elsif !pbOwnedByPlayer?
return lowerCase ? _INTL("the ally {1}", name) : _INTL("The ally {1}", name)
end
return name
end
def pbTeam(lowerCase = false)
if opposes?
return lowerCase ? _INTL("the opposing team") : _INTL("The opposing team")
end
return lowerCase ? _INTL("your team") : _INTL("Your team")
end
def pbOpposingTeam(lowerCase = false)
if opposes?
return lowerCase ? _INTL("your team") : _INTL("Your team")
end
return lowerCase ? _INTL("the opposing team") : _INTL("The opposing team")
end
#=============================================================================
# Calculated properties
#=============================================================================
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]
speedMult = 1.0
# Ability effects that alter calculated Speed
if abilityActive?
speedMult = Battle::AbilityEffects.triggerSpeedCalc(self.ability, self, speedMult)
end
# Item effects that alter calculated Speed
if itemActive?
speedMult = Battle::ItemEffects.triggerSpeedCalc(self.item, self, speedMult)
end
# Other effects
speedMult *= 2 if pbOwnSide.effects[PBEffects::Tailwind] > 0
speedMult /= 2 if pbOwnSide.effects[PBEffects::Swamp] > 0
# Paralysis
if status == :PARALYSIS && !hasActiveAbility?(:QUICKFEET)
speedMult /= (Settings::MECHANICS_GENERATION >= 7) ? 2 : 4
end
# Badge multiplier
if @battle.internalBattle && pbOwnedByPlayer? &&
@battle.pbPlayer.badge_count >= Settings::NUM_BADGES_BOOST_SPEED
speedMult *= 1.1
end
# Calculation
return [(speed * speedMult).round, 1].max
end
def pbWeight
ret = (@pokemon) ? @pokemon.weight : 500
ret += @effects[PBEffects::WeightChange]
ret = 1 if ret < 1
if abilityActive? && !@battle.moldBreaker
ret = Battle::AbilityEffects.triggerWeightCalc(self.ability, self, ret)
end
if itemActive?
ret = Battle::ItemEffects.triggerWeightCalc(self.item, self, ret)
end
return [ret, 1].max
end
#=============================================================================
# Queries about what the battler has
#=============================================================================
def plainStats
ret = {}
ret[:ATTACK] = self.attack
ret[:DEFENSE] = self.defense
ret[:SPECIAL_ATTACK] = self.spatk
ret[:SPECIAL_DEFENSE] = self.spdef
ret[:SPEED] = self.speed
return ret
end
def isSpecies?(species)
return @pokemon&.isSpecies?(species)
end
# Returns the active types of this Pokémon. The array should not include the
# same type more than once, and should not include any invalid types.
def pbTypes(withType3 = false)
ret = @types.uniq
# Burn Up erases the Fire-type.
ret.delete(:FIRE) if @effects[PBEffects::BurnUp]
# Roost erases the Flying-type. If there are no types left, adds the Normal-
# type.
if @effects[PBEffects::Roost]
ret.delete(:FLYING)
ret.push(:NORMAL) if ret.length == 0
end
# Add the third type specially.
if withType3 && @effects[PBEffects::Type3] && !ret.include?(@effects[PBEffects::Type3])
ret.push(@effects[PBEffects::Type3])
end
return ret
end
def pbHasType?(type)
return false if !type
activeTypes = pbTypes(true)
return activeTypes.include?(GameData::Type.get(type).id)
end
def pbHasOtherType?(type)
return false if !type
activeTypes = pbTypes(true)
activeTypes.delete(GameData::Type.get(type).id)
return activeTypes.length > 0
end
# NOTE: Do not create any held item which affects whether a Pokémon's ability
# is active. The ability Klutz affects whether a Pokémon's item is
# active, and the code for the two combined would cause an infinite loop
# (regardless of whether any Pokémon actually has either the ability or
# the item - the code existing is enough to cause the loop).
def abilityActive?(ignore_fainted = false, check_ability = nil)
return false if fainted? && !ignore_fainted
return false if @effects[PBEffects::GastroAcid]
return false if check_ability != :NEUTRALIZINGGAS && self.ability != :NEUTRALIZINGGAS &&
@battle.pbCheckGlobalAbility(:NEUTRALIZINGGAS)
return true
end
def hasActiveAbility?(check_ability, ignore_fainted = false)
return false if !abilityActive?(ignore_fainted, check_ability)
return check_ability.include?(@ability_id) if check_ability.is_a?(Array)
return self.ability == check_ability
end
alias hasWorkingAbility hasActiveAbility?
# Applies to both losing self's ability (i.e. being replaced by another) and
# having self's ability be negated.
def unstoppableAbility?(abil = nil)
abil = @ability_id if !abil
abil = GameData::Ability.try_get(abil)
return false if !abil
ability_blacklist = [
# Form-changing abilities
:BATTLEBOND,
:DISGUISE,
# :FLOWERGIFT, # This can be stopped
# :FORECAST, # This can be stopped
:GULPMISSILE,
:ICEFACE,
:MULTITYPE,
:POWERCONSTRUCT,
:SCHOOLING,
:SHIELDSDOWN,
:STANCECHANGE,
:ZENMODE,
# Abilities intended to be inherent properties of a certain species
:ASONECHILLINGNEIGH,
:ASONEGRIMNEIGH,
:COMATOSE,
:RKSSYSTEM
]
return ability_blacklist.include?(abil.id)
end
# Applies to gaining the ability.
def ungainableAbility?(abil = nil)
abil = @ability_id if !abil
abil = GameData::Ability.try_get(abil)
return false if !abil
ability_blacklist = [
# Form-changing abilities
:BATTLEBOND,
:DISGUISE,
:FLOWERGIFT,
:FORECAST,
:GULPMISSILE,
:ICEFACE,
:MULTITYPE,
:POWERCONSTRUCT,
:SCHOOLING,
:SHIELDSDOWN,
:STANCECHANGE,
:ZENMODE,
# Appearance-changing abilities
:ILLUSION,
:IMPOSTER,
# Abilities intended to be inherent properties of a certain species
:ASONECHILLINGNEIGH,
:ASONEGRIMNEIGH,
:COMATOSE,
:RKSSYSTEM,
# Abilities that can't be negated
:NEUTRALIZINGGAS
]
return ability_blacklist.include?(abil.id)
end
def itemActive?(ignoreFainted = false)
return false if fainted? && !ignoreFainted
return false if @effects[PBEffects::Embargo] > 0
return false if @battle.field.effects[PBEffects::MagicRoom] > 0
return false if @battle.corrosiveGas[@index % 2][@pokemonIndex]
return false if hasActiveAbility?(:KLUTZ, ignoreFainted)
return true
end
def hasActiveItem?(check_item, ignore_fainted = false)
return false if !itemActive?(ignore_fainted)
return check_item.include?(@item_id) if check_item.is_a?(Array)
return self.item == check_item
end
alias hasWorkingItem hasActiveItem?
# Returns whether the specified item will be unlosable for this Pokémon.
def unlosableItem?(check_item)
return false if !check_item
item_data = GameData::Item.get(check_item)
return true if item_data.is_mail?
return false if @effects[PBEffects::Transform]
# Items that change a Pokémon's form
if mega? # Check if item was needed for this Mega Evolution
return true if @pokemon.species_data.mega_stone == item_data.id
else # Check if item could cause a Mega Evolution
GameData::Species.each do |data|
next if data.species != @species || data.unmega_form != @form
return true if data.mega_stone == item_data.id
end
end
# Other unlosable items
return item_data.unlosable?(@species, self.ability)
end
def eachMove
@moves.each { |m| yield m }
end
def eachMoveWithIndex
@moves.each_with_index { |m, i| yield m, i }
end
def pbHasMove?(move_id)
return false if !move_id
eachMove { |m| return true if m.id == move_id }
return false
end
def pbHasMoveType?(check_type)
return false if !check_type
check_type = GameData::Type.get(check_type).id
eachMove { |m| return true if m.type == check_type }
return false
end
def pbHasMoveFunction?(*arg)
return false if !arg
eachMove do |m|
arg.each { |code| return true if m.function == code }
end
return false
end
def pbGetMoveWithID(move_id)
return nil if !move_id
eachMove { |m| return m if m.id == move_id }
return nil
end
def hasMoldBreaker?
return hasActiveAbility?([:MOLDBREAKER, :TERAVOLT, :TURBOBLAZE])
end
def canChangeType?
return ![:MULTITYPE, :RKSSYSTEM].include?(@ability_id)
end
def airborne?
return false if hasActiveItem?(:IRONBALL)
return false if @effects[PBEffects::Ingrain]
return false if @effects[PBEffects::SmackDown]
return false if @battle.field.effects[PBEffects::Gravity] > 0
return true if pbHasType?(:FLYING)
return true if hasActiveAbility?(:LEVITATE) && !@battle.moldBreaker
return true if hasActiveItem?(:AIRBALLOON)
return true if @effects[PBEffects::MagnetRise] > 0
return true if @effects[PBEffects::Telekinesis] > 0
return false
end
def affectedByTerrain?
return false if airborne?
return false if semiInvulnerable?
return true
end
def takesIndirectDamage?(showMsg = false)
return false if fainted?
if hasActiveAbility?(:MAGICGUARD)
if showMsg
@battle.pbShowAbilitySplash(self)
if Battle::Scene::USE_ABILITY_SPLASH
@battle.pbDisplay(_INTL("{1} is unaffected!", pbThis))
else
@battle.pbDisplay(_INTL("{1} is unaffected because of its {2}!", pbThis, abilityName))
end
@battle.pbHideAbilitySplash(self)
end
return false
end
return true
end
def takesSandstormDamage?
return false if !takesIndirectDamage?
return false if pbHasType?(:GROUND) || pbHasType?(:ROCK) || pbHasType?(:STEEL)
return false if inTwoTurnAttack?("TwoTurnAttackInvulnerableUnderground",
"TwoTurnAttackInvulnerableUnderwater")
return false if hasActiveAbility?([:OVERCOAT, :SANDFORCE, :SANDRUSH, :SANDVEIL])
return false if hasActiveItem?(:SAFETYGOGGLES)
return true
end
def takesHailDamage?
return false if !takesIndirectDamage?
return false if pbHasType?(:ICE)
return false if inTwoTurnAttack?("TwoTurnAttackInvulnerableUnderground",
"TwoTurnAttackInvulnerableUnderwater")
return false if hasActiveAbility?([:OVERCOAT, :ICEBODY, :SNOWCLOAK])
return false if hasActiveItem?(:SAFETYGOGGLES)
return true
end
def takesShadowSkyDamage?
return false if fainted?
return false if shadowPokemon?
return true
end
def effectiveWeather
ret = @battle.pbWeather
ret = :None if [:Sun, :Rain, :HarshSun, :HeavyRain].include?(ret) && hasActiveItem?(:UTILITYUMBRELLA)
return ret
end
def affectedByPowder?(showMsg = false)
return false if fainted?
if pbHasType?(:GRASS) && Settings::MORE_TYPE_EFFECTS
@battle.pbDisplay(_INTL("{1} is unaffected!", pbThis)) if showMsg
return false
end
if Settings::MECHANICS_GENERATION >= 6
if hasActiveAbility?(:OVERCOAT) && !@battle.moldBreaker
if showMsg
@battle.pbShowAbilitySplash(self)
if Battle::Scene::USE_ABILITY_SPLASH
@battle.pbDisplay(_INTL("{1} is unaffected!", pbThis))
else
@battle.pbDisplay(_INTL("{1} is unaffected because of its {2}!", pbThis, abilityName))
end
@battle.pbHideAbilitySplash(self)
end
return false
end
if hasActiveItem?(:SAFETYGOGGLES)
if showMsg
@battle.pbDisplay(_INTL("{1} is unaffected because of its {2}!", pbThis, itemName))
end
return false
end
end
return true
end
def canHeal?
return false if fainted? || @hp >= @totalhp
return false if @effects[PBEffects::HealBlock] > 0
return true
end
def affectedByContactEffect?(showMsg = false)
return false if fainted?
if hasActiveItem?(:PROTECTIVEPADS)
@battle.pbDisplay(_INTL("{1} protected itself with the {2}!", pbThis, itemName)) if showMsg
return false
end
return true
end
def trappedInBattle?
return true if @effects[PBEffects::Trapping] > 0
return true if @effects[PBEffects::MeanLook] >= 0
return true if @effects[PBEffects::JawLock] >= 0
return true if @battle.allBattlers.any? { |b| b.effects[PBEffects::JawLock] == @index }
return true if @effects[PBEffects::Octolock] >= 0
return true if @effects[PBEffects::Ingrain]
return true if @effects[PBEffects::NoRetreat]
return true if @battle.field.effects[PBEffects::FairyLock] > 0
return false
end
def movedThisRound?
return @lastRoundMoved && @lastRoundMoved == @battle.turnCount
end
def usingMultiTurnAttack?
return true if @effects[PBEffects::TwoTurnAttack]
return true if @effects[PBEffects::HyperBeam] > 0
return true if @effects[PBEffects::Rollout] > 0
return true if @effects[PBEffects::Outrage] > 0
return true if @effects[PBEffects::Uproar] > 0
return true if @effects[PBEffects::Bide] > 0
return false
end
def inTwoTurnAttack?(*arg)
return false if !@effects[PBEffects::TwoTurnAttack]
ttaFunction = GameData::Move.get(@effects[PBEffects::TwoTurnAttack]).function_code
arg.each { |a| return true if a == ttaFunction }
return false
end
def semiInvulnerable?
return inTwoTurnAttack?("TwoTurnAttackInvulnerableInSky",
"TwoTurnAttackInvulnerableUnderground",
"TwoTurnAttackInvulnerableUnderwater",
"TwoTurnAttackInvulnerableInSkyParalyzeTarget",
"TwoTurnAttackInvulnerableRemoveProtections",
"TwoTurnAttackInvulnerableInSkyTargetCannotAct")
end
def pbEncoredMoveIndex
return -1 if @effects[PBEffects::Encore] == 0 || !@effects[PBEffects::EncoreMove]
ret = -1
eachMoveWithIndex do |m, i|
next if m.id != @effects[PBEffects::EncoreMove]
ret = i
break
end
return ret
end
def initialItem
return @battle.initialItems[@index & 1][@pokemonIndex]
end
def setInitialItem(value)
item_data = GameData::Item.try_get(value)
new_item = (item_data) ? item_data.id : nil
@battle.initialItems[@index & 1][@pokemonIndex] = new_item
end
def recycleItem
return @battle.recycleItems[@index & 1][@pokemonIndex]
end
def setRecycleItem(value)
item_data = GameData::Item.try_get(value)
new_item = (item_data) ? item_data.id : nil
@battle.recycleItems[@index & 1][@pokemonIndex] = new_item
end
def belched?
return @battle.belch[@index & 1][@pokemonIndex]
end
def setBelched
@battle.belch[@index & 1][@pokemonIndex] = true
end
#=============================================================================
# Methods relating to this battler's position on the battlefield
#=============================================================================
# Returns whether the given position belongs to the opposing Pokémon's side.
def opposes?(i = 0)
i = i.index if i.respond_to?("index")
return (@index & 1) != (i & 1)
end
# Returns whether the given position/battler is near to self.
def near?(i)
i = i.index if i.respond_to?("index")
return @battle.nearBattlers?(@index, i)
end
# Returns whether self is owned by the player.
def pbOwnedByPlayer?
return @battle.pbOwnedByPlayer?(@index)
end
def wild?
return @battle.wildBattle? && opposes?
end
# Returns 0 if self is on the player's side, or 1 if self is on the opposing
# side.
def idxOwnSide
return @index & 1
end
# Returns 1 if self is on the player's side, or 0 if self is on the opposing
# side.
def idxOpposingSide
return (@index & 1) ^ 1
end
# Returns the data structure for this battler's side.
def pbOwnSide
return @battle.sides[idxOwnSide]
end
# Returns the data structure for the opposing Pokémon's side.
def pbOpposingSide
return @battle.sides[idxOpposingSide]
end
# Yields each unfainted ally Pokémon.
# Unused
def eachAlly
@battle.battlers.each do |b|
yield b if b && !b.fainted? && !b.opposes?(@index) && b.index != @index
end
end
# Returns an array containing all unfainted ally Pokémon.
def allAllies
return @battle.allSameSideBattlers(@index).reject { |b| b.index == @index }
end
# Yields each unfainted opposing Pokémon.
# Unused
def eachOpposing
@battle.battlers.each { |b| yield b if b && !b.fainted? && b.opposes?(@index) }
end
# Returns an array containing all unfainted opposing Pokémon.
def allOpposing
return @battle.allOtherSideBattlers(@index)
end
# Returns the battler that is most directly opposite to self. unfaintedOnly is
# whether it should prefer to return a non-fainted battler.
def pbDirectOpposing(unfaintedOnly = false)
@battle.pbGetOpposingIndicesInOrder(@index).each do |i|
next if !@battle.battlers[i]
break if unfaintedOnly && @battle.battlers[i].fainted?
return @battle.battlers[i]
end
# Wanted an unfainted battler but couldn't find one; make do with a fainted
# battler
@battle.pbGetOpposingIndicesInOrder(@index).each do |i|
return @battle.battlers[i] if @battle.battlers[i]
end
return @battle.battlers[(@index ^ 1)]
end
end