Yet more script rearranging

This commit is contained in:
Maruno17
2021-04-17 23:45:42 +01:00
parent 96c68e79a3
commit 2ca8a42949
236 changed files with 923 additions and 928 deletions

View File

@@ -0,0 +1,698 @@
class PokeBattle_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 :type1
attr_accessor :type2
attr_accessor :ability_id
attr_accessor :item_id
attr_accessor :moves
attr_accessor :gender
attr_accessor :iv
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 :tookDamage # Boolean for whether self took damage this round
attr_accessor :tookPhysicalHit
attr_accessor :damageState
attr_accessor :initialHP # Set at the start of each move's usage
#=============================================================================
# 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
alias isFainted? fainted?
attr_reader :status
def status=(value)
@effects[PBEffects::Truant] = false if @status == :SLEEP && value != :SLEEP
@effects[PBEffects::Toxic] = 0 if value != :POISON
@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 nature; return @pokemon ? @pokemon.nature : 0; 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 && @pokemon.hasMegaForm?
end
def mega?; return @pokemon && @pokemon.mega?; end
alias isMega? mega?
def hasPrimal?
return false if @effects[PBEffects::Transform]
return @pokemon && @pokemon.hasPrimalForm?
end
def primal?; return @pokemon && @pokemon.primal?; end
alias isPrimal? primal?
def shadowPokemon?; return false; end
alias isShadow? shadowPokemon?
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 && @pokemon.shiny?
end
alias isShiny? shiny?
def owned?
return false if !@battle.wildBattle?
return $Trainer.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 = BattleHandlers.triggerSpeedCalcAbility(self.ability,self,speedMult)
end
# Item effects that alter calculated Speed
if itemActive?
speedMult = BattleHandlers.triggerSpeedCalcItem(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 = BattleHandlers.triggerWeightCalcAbility(self.ability,self,ret)
end
if itemActive?
ret = BattleHandlers.triggerWeightCalcItem(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 && @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 type numbers
# (e.g. -1).
def pbTypes(withType3=false)
ret = [@type1]
ret.push(@type2) if @type2!=@type1
# 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.push(@effects[PBEffects::Type3]) if !ret.include?(@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 actualy has either the ability or
# the item - the code existing is enough to cause the loop).
def abilityActive?(ignore_fainted = false)
return false if fainted? && !ignore_fainted
return false if @effects[PBEffects::GastroAcid]
return true
end
def hasActiveAbility?(check_ability, ignore_fainted = false)
return false if !abilityActive?(ignore_fainted)
return check_ability.include?(@ability_id) if check_ability.is_a?(Array)
return check_ability == self.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
:MULTITYPE,
:POWERCONSTRUCT,
:SCHOOLING,
:SHIELDSDOWN,
:STANCECHANGE,
:ZENMODE,
# Abilities intended to be inherent properties of a certain species
: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,
:MULTITYPE,
:POWERCONSTRUCT,
:SCHOOLING,
:SHIELDSDOWN,
:STANCECHANGE,
:ZENMODE,
# Appearance-changing abilities
:ILLUSION,
:IMPOSTER,
# Abilities intended to be inherent properties of a certain species
:COMATOSE,
:RKSSYSTEM
]
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 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 check_item == self.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
return true if GameData::Item.get(check_item).is_mail?
return false if @effects[PBEffects::Transform]
# Items that change a Pokémon's form
return true if @pokemon && @pokemon.getMegaForm(true) > 0 # Mega Stone
return GameData::Item.get(check_item).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 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 PokeBattle_SceneConstants::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?("0CA","0CB") # Dig, Dive
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?("0CA","0CB") # Dig, Dive
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 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 PokeBattle_SceneConstants::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 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?("0C9","0CA","0CB","0CC","0CD","0CE","14D")
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(newItem)
@battle.initialItems[@index&1][@pokemonIndex] = newItem
end
def recycleItem
return @battle.recycleItems[@index&1][@pokemonIndex]
end
def setRecycleItem(newItem)
@battle.recycleItems[@index&1][@pokemonIndex] = newItem
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
# 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.
def eachAlly
@battle.battlers.each do |b|
yield b if b && !b.fainted? && !b.opposes?(@index) && b.index!=@index
end
end
# Yields each unfainted opposing Pokémon.
def eachOpposing
@battle.battlers.each { |b| yield b if b && !b.fainted? && b.opposes?(@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

View File

@@ -0,0 +1,328 @@
class PokeBattle_Battler
#=============================================================================
# Creating a battler
#=============================================================================
def initialize(btl,idxBattler)
@battle = btl
@index = idxBattler
@captured = false
@dummy = false
@stages = {}
@effects = []
@damageState = PokeBattle_DamageState.new
pbInitBlank
pbInitEffects(false)
end
def pbInitBlank
@name = ""
@species = 0
@form = 0
@level = 0
@hp = @totalhp = 0
@type1 = @type2 = nil
@ability_id = nil
@item_id = nil
@gender = 0
@attack = @defense = @spatk = @spdef = @speed = 0
@status = :NONE
@statusCount = 0
@pokemon = nil
@pokemonIndex = -1
@participants = []
@moves = []
@iv = {}
GameData::Stat.each_main { |s| @iv[s.id] = 0 }
end
# Used by Future Sight only, when Future Sight's user is no longer in battle.
def pbInitDummyPokemon(pkmn,idxParty)
raise _INTL("An egg can't be an active Pokémon.") if pkmn.egg?
@name = pkmn.name
@species = pkmn.species
@form = pkmn.form
@level = pkmn.level
@hp = pkmn.hp
@totalhp = pkmn.totalhp
@type1 = pkmn.type1
@type2 = pkmn.type2
# ability and item intentionally not copied across here
@gender = pkmn.gender
@attack = pkmn.attack
@defense = pkmn.defense
@spatk = pkmn.spatk
@spdef = pkmn.spdef
@speed = pkmn.speed
@status = pkmn.status
@statusCount = pkmn.statusCount
@pokemon = pkmn
@pokemonIndex = idxParty
@participants = []
# moves intentionally not copied across here
@iv = {}
GameData::Stat.each_main { |s| @iv[s.id] = pkmn.iv[s.id] }
@dummy = true
end
def pbInitialize(pkmn,idxParty,batonPass=false)
pbInitPokemon(pkmn,idxParty)
pbInitEffects(batonPass)
@damageState.reset
end
def pbInitPokemon(pkmn,idxParty)
raise _INTL("An egg can't be an active Pokémon.") if pkmn.egg?
@name = pkmn.name
@species = pkmn.species
@form = pkmn.form
@level = pkmn.level
@hp = pkmn.hp
@totalhp = pkmn.totalhp
@type1 = pkmn.type1
@type2 = pkmn.type2
@ability_id = pkmn.ability_id
@item_id = pkmn.item_id
@gender = pkmn.gender
@attack = pkmn.attack
@defense = pkmn.defense
@spatk = pkmn.spatk
@spdef = pkmn.spdef
@speed = pkmn.speed
@status = pkmn.status
@statusCount = pkmn.statusCount
@pokemon = pkmn
@pokemonIndex = idxParty
@participants = [] # Participants earn Exp. if this battler is defeated
@moves = []
pkmn.moves.each_with_index do |m,i|
@moves[i] = PokeBattle_Move.from_pokemon_move(@battle,m)
end
@iv = {}
GameData::Stat.each_main { |s| @iv[s.id] = pkmn.iv[s.id] }
end
def pbInitEffects(batonPass)
if batonPass
# These effects are passed on if Baton Pass is used, but they need to be
# reapplied
@effects[PBEffects::LaserFocus] = (@effects[PBEffects::LaserFocus]>0) ? 2 : 0
@effects[PBEffects::LockOn] = (@effects[PBEffects::LockOn]>0) ? 2 : 0
if @effects[PBEffects::PowerTrick]
@attack,@defense = @defense,@attack
end
# These effects are passed on if Baton Pass is used, but they need to be
# cancelled in certain circumstances anyway
@effects[PBEffects::Telekinesis] = 0 if isSpecies?(:GENGAR) && mega?
@effects[PBEffects::GastroAcid] = false if unstoppableAbility?
else
# These effects are passed on if Baton Pass is used
@stages[:ATTACK] = 0
@stages[:DEFENSE] = 0
@stages[:SPEED] = 0
@stages[:SPECIAL_ATTACK] = 0
@stages[:SPECIAL_DEFENSE] = 0
@stages[:ACCURACY] = 0
@stages[:EVASION] = 0
@effects[PBEffects::AquaRing] = false
@effects[PBEffects::Confusion] = 0
@effects[PBEffects::Curse] = false
@effects[PBEffects::Embargo] = 0
@effects[PBEffects::FocusEnergy] = 0
@effects[PBEffects::GastroAcid] = false
@effects[PBEffects::HealBlock] = 0
@effects[PBEffects::Ingrain] = false
@effects[PBEffects::LaserFocus] = 0
@effects[PBEffects::LeechSeed] = -1
@effects[PBEffects::LockOn] = 0
@effects[PBEffects::LockOnPos] = -1
@effects[PBEffects::MagnetRise] = 0
@effects[PBEffects::PerishSong] = 0
@effects[PBEffects::PerishSongUser] = -1
@effects[PBEffects::PowerTrick] = false
@effects[PBEffects::Substitute] = 0
@effects[PBEffects::Telekinesis] = 0
end
@fainted = (@hp==0)
@initialHP = 0
@lastAttacker = []
@lastFoeAttacker = []
@lastHPLost = 0
@lastHPLostFromFoe = 0
@tookDamage = false
@tookPhysicalHit = false
@lastMoveUsed = nil
@lastMoveUsedType = nil
@lastRegularMoveUsed = nil
@lastRegularMoveTarget = -1
@lastRoundMoved = -1
@lastMoveFailed = false
@lastRoundMoveFailed = false
@movesUsed = []
@turnCount = 0
@effects[PBEffects::Attract] = -1
@battle.eachBattler do |b| # Other battlers no longer attracted to self
b.effects[PBEffects::Attract] = -1 if b.effects[PBEffects::Attract]==@index
end
@effects[PBEffects::BanefulBunker] = false
@effects[PBEffects::BeakBlast] = false
@effects[PBEffects::Bide] = 0
@effects[PBEffects::BideDamage] = 0
@effects[PBEffects::BideTarget] = -1
@effects[PBEffects::BurnUp] = false
@effects[PBEffects::Charge] = 0
@effects[PBEffects::ChoiceBand] = nil
@effects[PBEffects::Counter] = -1
@effects[PBEffects::CounterTarget] = -1
@effects[PBEffects::Dancer] = false
@effects[PBEffects::DefenseCurl] = false
@effects[PBEffects::DestinyBond] = false
@effects[PBEffects::DestinyBondPrevious] = false
@effects[PBEffects::DestinyBondTarget] = -1
@effects[PBEffects::Disable] = 0
@effects[PBEffects::DisableMove] = nil
@effects[PBEffects::Electrify] = false
@effects[PBEffects::Encore] = 0
@effects[PBEffects::EncoreMove] = nil
@effects[PBEffects::Endure] = false
@effects[PBEffects::FirstPledge] = 0
@effects[PBEffects::FlashFire] = false
@effects[PBEffects::Flinch] = false
@effects[PBEffects::FocusPunch] = false
@effects[PBEffects::FollowMe] = 0
@effects[PBEffects::Foresight] = false
@effects[PBEffects::FuryCutter] = 0
@effects[PBEffects::GemConsumed] = nil
@effects[PBEffects::Grudge] = false
@effects[PBEffects::HelpingHand] = false
@effects[PBEffects::HyperBeam] = 0
@effects[PBEffects::Illusion] = nil
if hasActiveAbility?(:ILLUSION)
idxLastParty = @battle.pbLastInTeam(@index)
if idxLastParty >= 0 && idxLastParty != @pokemonIndex
@effects[PBEffects::Illusion] = @battle.pbParty(@index)[idxLastParty]
end
end
@effects[PBEffects::Imprison] = false
@effects[PBEffects::Instruct] = false
@effects[PBEffects::Instructed] = false
@effects[PBEffects::KingsShield] = false
@battle.eachBattler do |b| # Other battlers lose their lock-on against self
next if b.effects[PBEffects::LockOn]==0
next if b.effects[PBEffects::LockOnPos]!=@index
b.effects[PBEffects::LockOn] = 0
b.effects[PBEffects::LockOnPos] = -1
end
@effects[PBEffects::MagicBounce] = false
@effects[PBEffects::MagicCoat] = false
@effects[PBEffects::MeanLook] = -1
@battle.eachBattler do |b| # Other battlers no longer blocked by self
b.effects[PBEffects::MeanLook] = -1 if b.effects[PBEffects::MeanLook]==@index
end
@effects[PBEffects::MeFirst] = false
@effects[PBEffects::Metronome] = 0
@effects[PBEffects::MicleBerry] = false
@effects[PBEffects::Minimize] = false
@effects[PBEffects::MiracleEye] = false
@effects[PBEffects::MirrorCoat] = -1
@effects[PBEffects::MirrorCoatTarget] = -1
@effects[PBEffects::MoveNext] = false
@effects[PBEffects::MudSport] = false
@effects[PBEffects::Nightmare] = false
@effects[PBEffects::Outrage] = 0
@effects[PBEffects::ParentalBond] = 0
@effects[PBEffects::PickupItem] = nil
@effects[PBEffects::PickupUse] = 0
@effects[PBEffects::Pinch] = false
@effects[PBEffects::Powder] = false
@effects[PBEffects::Prankster] = false
@effects[PBEffects::PriorityAbility] = false
@effects[PBEffects::PriorityItem] = false
@effects[PBEffects::Protect] = false
@effects[PBEffects::ProtectRate] = 1
@effects[PBEffects::Pursuit] = false
@effects[PBEffects::Quash] = 0
@effects[PBEffects::Rage] = false
@effects[PBEffects::RagePowder] = false
@effects[PBEffects::Rollout] = 0
@effects[PBEffects::Roost] = false
@effects[PBEffects::SkyDrop] = -1
@battle.eachBattler do |b| # Other battlers no longer Sky Dropped by self
b.effects[PBEffects::SkyDrop] = -1 if b.effects[PBEffects::SkyDrop]==@index
end
@effects[PBEffects::SlowStart] = 0
@effects[PBEffects::SmackDown] = false
@effects[PBEffects::Snatch] = 0
@effects[PBEffects::SpikyShield] = false
@effects[PBEffects::Spotlight] = 0
@effects[PBEffects::Stockpile] = 0
@effects[PBEffects::StockpileDef] = 0
@effects[PBEffects::StockpileSpDef] = 0
@effects[PBEffects::Taunt] = 0
@effects[PBEffects::ThroatChop] = 0
@effects[PBEffects::Torment] = false
@effects[PBEffects::Toxic] = 0
@effects[PBEffects::Transform] = false
@effects[PBEffects::TransformSpecies] = 0
@effects[PBEffects::Trapping] = 0
@effects[PBEffects::TrappingMove] = nil
@effects[PBEffects::TrappingUser] = -1
@battle.eachBattler do |b| # Other battlers no longer trapped by self
next if b.effects[PBEffects::TrappingUser]!=@index
b.effects[PBEffects::Trapping] = 0
b.effects[PBEffects::TrappingUser] = -1
end
@effects[PBEffects::Truant] = false
@effects[PBEffects::TwoTurnAttack] = nil
@effects[PBEffects::Type3] = nil
@effects[PBEffects::Unburden] = false
@effects[PBEffects::Uproar] = 0
@effects[PBEffects::WaterSport] = false
@effects[PBEffects::WeightChange] = 0
@effects[PBEffects::Yawn] = 0
end
#=============================================================================
# Refreshing a battler's properties
#=============================================================================
def pbUpdate(fullChange=false)
return if !@pokemon
@pokemon.calc_stats
@level = @pokemon.level
@hp = @pokemon.hp
@totalhp = @pokemon.totalhp
if !@effects[PBEffects::Transform]
@attack = @pokemon.attack
@defense = @pokemon.defense
@spatk = @pokemon.spatk
@spdef = @pokemon.spdef
@speed = @pokemon.speed
if fullChange
@type1 = @pokemon.type1
@type2 = @pokemon.type2
@ability_id = @pokemon.ability_id
end
end
end
# Used to erase the battler of a Pokémon that has been caught.
def pbReset
@pokemon = nil
@pokemonIndex = -1
@hp = 0
pbInitEffects(false)
@participants = []
# Reset status
@status = :NONE
@statusCount = 0
# Reset choice
@battle.pbClearChoice(@index)
end
# Update which Pokémon will gain Exp if this battler is defeated.
def pbUpdateParticipants
return if fainted? || !@battle.opposes?(@index)
eachOpposing do |b|
@participants.push(b.pokemonIndex) if !@participants.include?(b.pokemonIndex)
end
end
end

View File

@@ -0,0 +1,297 @@
class PokeBattle_Battler
#=============================================================================
# Change HP
#=============================================================================
def pbReduceHP(amt,anim=true,registerDamage=true,anyAnim=true)
amt = amt.round
amt = @hp if amt>@hp
amt = 1 if amt<1 && !fainted?
oldHP = @hp
self.hp -= amt
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
@tookDamage = true if amt>0 && registerDamage
return amt
end
def pbRecoverHP(amt,anim=true,anyAnim=true)
amt = amt.round
amt = @totalhp-@hp if amt>@totalhp-@hp
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
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
return amt
end
def pbRecoverHPFromDrain(amt,target,msg=nil)
if target.hasActiveAbility?(:LIQUIDOOZE)
@battle.pbShowAbilitySplash(target)
pbReduceHP(amt)
@battle.pbDisplay(_INTL("{1} sucked up the liquid ooze!",pbThis))
@battle.pbHideAbilitySplash(target)
pbItemHPHealCheck
else
msg = _INTL("{1} had its energy drained!",target.pbThis) if !msg || msg==""
@battle.pbDisplay(msg)
if canHeal?
amt = (amt*1.3).floor if hasActiveItem?(:BIGROOT)
pbRecoverHP(amt)
end
end
end
def pbFaint(showMessage=true)
if !fainted?
PBDebug.log("!!!***Can't faint with HP greater than 0")
return
end
return if @fainted # Has already fainted properly
@battle.pbDisplayBrief(_INTL("{1} fainted!",pbThis)) if showMessage
PBDebug.log("[Pokémon fainted] #{pbThis} (#{@index})") if !showMessage
@battle.scene.pbFaintBattler(self)
pbInitEffects(false)
# Reset status
self.status = :NONE
self.statusCount = 0
# Lose happiness
if @pokemon && @battle.internalBattle
badLoss = false
@battle.eachOtherSideBattler(@index) do |b|
badLoss = true if b.level>=self.level+30
end
@pokemon.changeHappiness((badLoss) ? "faintbad" : "faint")
end
# Reset form
@battle.peer.pbOnLeavingBattle(@battle,@pokemon,@battle.usedInBattle[idxOwnSide][@index/2])
@pokemon.makeUnmega if mega?
@pokemon.makeUnprimal if primal?
# Do other things
@battle.pbClearChoice(@index) # Reset choice
pbOwnSide.effects[PBEffects::LastRoundFainted] = @battle.turnCount
# Check other battlers' abilities that trigger upon a battler fainting
pbAbilitiesOnFainting
# Check for end of primordial weather
@battle.pbEndPrimordialWeather
end
#=============================================================================
# Move PP
#=============================================================================
def pbSetPP(move,pp)
move.pp = pp
# No need to care about @effects[PBEffects::Mimic], since Mimic can't copy
# Mimic
if move.realMove && move.id==move.realMove.id && !@effects[PBEffects::Transform]
move.realMove.pp = pp
end
end
def pbReducePP(move)
return true if usingMultiTurnAttack?
return true if move.pp<0 # Don't reduce PP for special calls of moves
return true if move.total_pp<=0 # Infinite PP, can always be used
return false if move.pp==0 # Ran out of PP, couldn't reduce
pbSetPP(move,move.pp-1) if move.pp>0
return true
end
def pbReducePPOther(move)
pbSetPP(move,move.pp-1) if move.pp>0
end
#=============================================================================
# Change type
#=============================================================================
def pbChangeTypes(newType)
if newType.is_a?(PokeBattle_Battler)
newTypes = newType.pbTypes
newTypes.push(:NORMAL) if newTypes.length == 0
newType3 = newType.effects[PBEffects::Type3]
newType3 = nil if newTypes.include?(newType3)
@type1 = newTypes[0]
@type2 = (newTypes.length == 1) ? newTypes[0] : newTypes[1]
@effects[PBEffects::Type3] = newType3
else
newType = GameData::Item.get(newType).id
@type1 = newType
@type2 = newType
@effects[PBEffects::Type3] = nil
end
@effects[PBEffects::BurnUp] = false
@effects[PBEffects::Roost] = false
end
#=============================================================================
# Forms
#=============================================================================
def pbChangeForm(newForm,msg)
return if fainted? || @effects[PBEffects::Transform] || @form==newForm
oldForm = @form
oldDmg = @totalhp-@hp
self.form = newForm
pbUpdate(true)
@hp = @totalhp-oldDmg
@effects[PBEffects::WeightChange] = 0 if Settings::MECHANICS_GENERATION >= 6
@battle.scene.pbChangePokemon(self,@pokemon)
@battle.scene.pbRefreshOne(@index)
@battle.pbDisplay(msg) if msg && msg!=""
PBDebug.log("[Form changed] #{pbThis} changed from form #{oldForm} to form #{newForm}")
@battle.pbSetSeen(self)
end
def pbCheckFormOnStatusChange
return if fainted? || @effects[PBEffects::Transform]
# Shaymin - reverts if frozen
if isSpecies?(:SHAYMIN) && frozen?
pbChangeForm(0,_INTL("{1} transformed!",pbThis))
end
end
def pbCheckFormOnMovesetChange
return if fainted? || @effects[PBEffects::Transform]
# Keldeo - knowing Secret Sword
if isSpecies?(:KELDEO)
newForm = 0
newForm = 1 if pbHasMove?(:SECRETSWORD)
pbChangeForm(newForm,_INTL("{1} transformed!",pbThis))
end
end
def pbCheckFormOnWeatherChange
return if fainted? || @effects[PBEffects::Transform]
# Castform - Forecast
if isSpecies?(:CASTFORM)
if hasActiveAbility?(:FORECAST)
newForm = 0
case @battle.pbWeather
when :Sun, :HarshSun then newForm = 1
when :Rain, :HeavyRain then newForm = 2
when :Hail then newForm = 3
end
if @form!=newForm
@battle.pbShowAbilitySplash(self,true)
@battle.pbHideAbilitySplash(self)
pbChangeForm(newForm,_INTL("{1} transformed!",pbThis))
end
else
pbChangeForm(0,_INTL("{1} transformed!",pbThis))
end
end
# Cherrim - Flower Gift
if isSpecies?(:CHERRIM)
if hasActiveAbility?(:FLOWERGIFT)
newForm = 0
newForm = 1 if [:Sun, :HarshSun].include?(@battle.pbWeather)
if @form!=newForm
@battle.pbShowAbilitySplash(self,true)
@battle.pbHideAbilitySplash(self)
pbChangeForm(newForm,_INTL("{1} transformed!",pbThis))
end
else
pbChangeForm(0,_INTL("{1} transformed!",pbThis))
end
end
end
# Checks the Pokémon's form and updates it if necessary. Used for when a
# Pokémon enters battle (endOfRound=false) and at the end of each round
# (endOfRound=true).
def pbCheckForm(endOfRound=false)
return if fainted? || @effects[PBEffects::Transform]
# Form changes upon entering battle and when the weather changes
pbCheckFormOnWeatherChange if !endOfRound
# Darmanitan - Zen Mode
if isSpecies?(:DARMANITAN) && self.ability == :ZENMODE
if @hp<=@totalhp/2
if @form!=1
@battle.pbShowAbilitySplash(self,true)
@battle.pbHideAbilitySplash(self)
pbChangeForm(1,_INTL("{1} triggered!",abilityName))
end
elsif @form!=0
@battle.pbShowAbilitySplash(self,true)
@battle.pbHideAbilitySplash(self)
pbChangeForm(0,_INTL("{1} triggered!",abilityName))
end
end
# Minior - Shields Down
if isSpecies?(:MINIOR) && self.ability == :SHIELDSDOWN
if @hp>@totalhp/2 # Turn into Meteor form
newForm = (@form>=7) ? @form-7 : @form
if @form!=newForm
@battle.pbShowAbilitySplash(self,true)
@battle.pbHideAbilitySplash(self)
pbChangeForm(newForm,_INTL("{1} deactivated!",abilityName))
elsif !endOfRound
@battle.pbDisplay(_INTL("{1} deactivated!",abilityName))
end
elsif @form<7 # Turn into Core form
@battle.pbShowAbilitySplash(self,true)
@battle.pbHideAbilitySplash(self)
pbChangeForm(@form+7,_INTL("{1} activated!",abilityName))
end
end
# Wishiwashi - Schooling
if isSpecies?(:WISHIWASHI) && self.ability == :SCHOOLING
if @level>=20 && @hp>@totalhp/4
if @form!=1
@battle.pbShowAbilitySplash(self,true)
@battle.pbHideAbilitySplash(self)
pbChangeForm(1,_INTL("{1} formed a school!",pbThis))
end
elsif @form!=0
@battle.pbShowAbilitySplash(self,true)
@battle.pbHideAbilitySplash(self)
pbChangeForm(0,_INTL("{1} stopped schooling!",pbThis))
end
end
# Zygarde - Power Construct
if isSpecies?(:ZYGARDE) && self.ability == :POWERCONSTRUCT && endOfRound
if @hp<=@totalhp/2 && @form<2 # Turn into Complete Forme
newForm = @form+2
@battle.pbDisplay(_INTL("You sense the presence of many!"))
@battle.pbShowAbilitySplash(self,true)
@battle.pbHideAbilitySplash(self)
pbChangeForm(newForm,_INTL("{1} transformed into its Complete Forme!",pbThis))
end
end
end
def pbTransform(target)
oldAbil = @ability_id
@effects[PBEffects::Transform] = true
@effects[PBEffects::TransformSpecies] = target.species
pbChangeTypes(target)
self.ability = target.ability
@attack = target.attack
@defense = target.defense
@spatk = target.spatk
@spdef = target.spdef
@speed = target.speed
GameData::Stat.each_battle { |s| @stages[s.id] = target.stages[s.id] }
if Settings::NEW_CRITICAL_HIT_RATE_MECHANICS
@effects[PBEffects::FocusEnergy] = target.effects[PBEffects::FocusEnergy]
@effects[PBEffects::LaserFocus] = target.effects[PBEffects::LaserFocus]
end
@moves.clear
target.moves.each_with_index do |m,i|
@moves[i] = PokeBattle_Move.from_pokemon_move(@battle, Pokemon::Move.new(m.id))
@moves[i].pp = 5
@moves[i].total_pp = 5
end
@effects[PBEffects::Disable] = 0
@effects[PBEffects::DisableMove] = nil
@effects[PBEffects::WeightChange] = target.effects[PBEffects::WeightChange]
@battle.scene.pbRefreshOne(@index)
@battle.pbDisplay(_INTL("{1} transformed into {2}!",pbThis,target.pbThis(true)))
pbOnAbilityChanged(oldAbil)
end
def pbHyperMode; end
end

View File

@@ -0,0 +1,576 @@
class PokeBattle_Battler
#=============================================================================
# Generalised checks for whether a status problem can be inflicted
#=============================================================================
# NOTE: Not all "does it have this status?" checks use this method. If the
# check is leading up to curing self of that status condition, then it
# will look at the value of @status directly instead - if it is that
# status condition then it is curable. This method only checks for
# "counts as having that status", which includes Comatose which can't be
# cured.
def pbHasStatus?(checkStatus)
if BattleHandlers.triggerStatusCheckAbilityNonIgnorable(self.ability,self,checkStatus)
return true
end
return @status==checkStatus
end
def pbHasAnyStatus?
if BattleHandlers.triggerStatusCheckAbilityNonIgnorable(self.ability,self,nil)
return true
end
return @status != :NONE
end
def pbCanInflictStatus?(newStatus,user,showMessages,move=nil,ignoreStatus=false)
return false if fainted?
selfInflicted = (user && user.index==@index)
# Already have that status problem
if self.status==newStatus && !ignoreStatus
if showMessages
msg = ""
case self.status
when :SLEEP then msg = _INTL("{1} is already asleep!", pbThis)
when :POISON then msg = _INTL("{1} is already poisoned!", pbThis)
when :BURN then msg = _INTL("{1} already has a burn!", pbThis)
when :PARALYSIS then msg = _INTL("{1} is already paralyzed!", pbThis)
when :FROZEN then msg = _INTL("{1} is already frozen solid!", pbThis)
end
@battle.pbDisplay(msg)
end
return false
end
# Trying to replace a status problem with another one
if self.status != :NONE && !ignoreStatus && !selfInflicted
@battle.pbDisplay(_INTL("It doesn't affect {1}...",pbThis(true))) if showMessages
return false
end
# Trying to inflict a status problem on a Pokémon behind a substitute
if @effects[PBEffects::Substitute]>0 && !(move && move.ignoresSubstitute?(user)) &&
!selfInflicted
@battle.pbDisplay(_INTL("It doesn't affect {1}...",pbThis(true))) if showMessages
return false
end
# Weather immunity
if newStatus == :FROZEN && [:Sun, :HarshSun].include?(@battle.pbWeather)
@battle.pbDisplay(_INTL("It doesn't affect {1}...",pbThis(true))) if showMessages
return false
end
# Terrains immunity
if affectedByTerrain?
case @battle.field.terrain
when :Electric
if newStatus == :SLEEP
@battle.pbDisplay(_INTL("{1} surrounds itself with electrified terrain!",
pbThis(true))) if showMessages
return false
end
when :Misty
@battle.pbDisplay(_INTL("{1} surrounds itself with misty terrain!",pbThis(true))) if showMessages
return false
end
end
# Uproar immunity
if newStatus == :SLEEP && !(hasActiveAbility?(:SOUNDPROOF) && !@battle.moldBreaker)
@battle.eachBattler do |b|
next if b.effects[PBEffects::Uproar]==0
@battle.pbDisplay(_INTL("But the uproar kept {1} awake!",pbThis(true))) if showMessages
return false
end
end
# Type immunities
hasImmuneType = false
case newStatus
when :SLEEP
# No type is immune to sleep
when :POISON
if !(user && user.hasActiveAbility?(:CORROSION))
hasImmuneType |= pbHasType?(:POISON)
hasImmuneType |= pbHasType?(:STEEL)
end
when :BURN
hasImmuneType |= pbHasType?(:FIRE)
when :PARALYSIS
hasImmuneType |= pbHasType?(:ELECTRIC) && Settings::MORE_TYPE_EFFECTS
when :FROZEN
hasImmuneType |= pbHasType?(:ICE)
end
if hasImmuneType
@battle.pbDisplay(_INTL("It doesn't affect {1}...",pbThis(true))) if showMessages
return false
end
# Ability immunity
immuneByAbility = false; immAlly = nil
if BattleHandlers.triggerStatusImmunityAbilityNonIgnorable(self.ability,self,newStatus)
immuneByAbility = true
elsif selfInflicted || !@battle.moldBreaker
if abilityActive? && BattleHandlers.triggerStatusImmunityAbility(self.ability,self,newStatus)
immuneByAbility = true
else
eachAlly do |b|
next if !b.abilityActive?
next if !BattleHandlers.triggerStatusImmunityAllyAbility(b.ability,self,newStatus)
immuneByAbility = true
immAlly = b
break
end
end
end
if immuneByAbility
if showMessages
@battle.pbShowAbilitySplash(immAlly || self)
msg = ""
if PokeBattle_SceneConstants::USE_ABILITY_SPLASH
case newStatus
when :SLEEP then msg = _INTL("{1} stays awake!", pbThis)
when :POISON then msg = _INTL("{1} cannot be poisoned!", pbThis)
when :BURN then msg = _INTL("{1} cannot be burned!", pbThis)
when :PARALYSIS then msg = _INTL("{1} cannot be paralyzed!", pbThis)
when :FROZEN then msg = _INTL("{1} cannot be frozen solid!", pbThis)
end
elsif immAlly
case newStatus
when :SLEEP
msg = _INTL("{1} stays awake because of {2}'s {3}!",
pbThis,immAlly.pbThis(true),immAlly.abilityName)
when :POISON
msg = _INTL("{1} cannot be poisoned because of {2}'s {3}!",
pbThis,immAlly.pbThis(true),immAlly.abilityName)
when :BURN
msg = _INTL("{1} cannot be burned because of {2}'s {3}!",
pbThis,immAlly.pbThis(true),immAlly.abilityName)
when :PARALYSIS
msg = _INTL("{1} cannot be paralyzed because of {2}'s {3}!",
pbThis,immAlly.pbThis(true),immAlly.abilityName)
when :FROZEN
msg = _INTL("{1} cannot be frozen solid because of {2}'s {3}!",
pbThis,immAlly.pbThis(true),immAlly.abilityName)
end
else
case newStatus
when :SLEEP then msg = _INTL("{1} stays awake because of its {2}!", pbThis, abilityName)
when :POISON then msg = _INTL("{1}'s {2} prevents poisoning!", pbThis, abilityName)
when :BURN then msg = _INTL("{1}'s {2} prevents burns!", pbThis, abilityName)
when :PARALYSIS then msg = _INTL("{1}'s {2} prevents paralysis!", pbThis, abilityName)
when :FROZEN then msg = _INTL("{1}'s {2} prevents freezing!", pbThis, abilityName)
end
end
@battle.pbDisplay(msg)
@battle.pbHideAbilitySplash(immAlly || self)
end
return false
end
# Safeguard immunity
if pbOwnSide.effects[PBEffects::Safeguard]>0 && !selfInflicted && move &&
!(user && user.hasActiveAbility?(:INFILTRATOR))
@battle.pbDisplay(_INTL("{1}'s team is protected by Safeguard!",pbThis)) if showMessages
return false
end
return true
end
def pbCanSynchronizeStatus?(newStatus,target)
return false if fainted?
# Trying to replace a status problem with another one
return false if self.status != :NONE
# Terrain immunity
return false if @battle.field.terrain == :Misty && affectedByTerrain?
# Type immunities
hasImmuneType = false
case newStatus
when :POISON
# NOTE: target will have Synchronize, so it can't have Corrosion.
if !(target && target.hasActiveAbility?(:CORROSION))
hasImmuneType |= pbHasType?(:POISON)
hasImmuneType |= pbHasType?(:STEEL)
end
when :BURN
hasImmuneType |= pbHasType?(:FIRE)
when :PARALYSIS
hasImmuneType |= pbHasType?(:ELECTRIC) && Settings::MORE_TYPE_EFFECTS
end
return false if hasImmuneType
# Ability immunity
if BattleHandlers.triggerStatusImmunityAbilityNonIgnorable(self.ability,self,newStatus)
return false
end
if abilityActive? && BattleHandlers.triggerStatusImmunityAbility(self.ability,self,newStatus)
return false
end
eachAlly do |b|
next if !b.abilityActive?
next if !BattleHandlers.triggerStatusImmunityAllyAbility(b.ability,self,newStatus)
return false
end
# Safeguard immunity
if pbOwnSide.effects[PBEffects::Safeguard]>0 &&
!(user && user.hasActiveAbility?(:INFILTRATOR))
return false
end
return true
end
#=============================================================================
# Generalised infliction of status problem
#=============================================================================
def pbInflictStatus(newStatus,newStatusCount=0,msg=nil,user=nil)
# Inflict the new status
self.status = newStatus
self.statusCount = newStatusCount
@effects[PBEffects::Toxic] = 0
# Show animation
if newStatus == :POISON && newStatusCount > 0
@battle.pbCommonAnimation("Toxic", self)
else
anim_name = GameData::Status.get(newStatus).animation
@battle.pbCommonAnimation(anim_name, self) if anim_name
end
# Show message
if msg && !msg.empty?
@battle.pbDisplay(msg)
else
case newStatus
when :SLEEP
@battle.pbDisplay(_INTL("{1} fell asleep!", pbThis))
when :POISON
if newStatusCount>0
@battle.pbDisplay(_INTL("{1} was badly poisoned!", pbThis))
else
@battle.pbDisplay(_INTL("{1} was poisoned!", pbThis))
end
when :BURN
@battle.pbDisplay(_INTL("{1} was burned!", pbThis))
when :PARALYSIS
@battle.pbDisplay(_INTL("{1} is paralyzed! It may be unable to move!", pbThis))
when :FROZEN
@battle.pbDisplay(_INTL("{1} was frozen solid!", pbThis))
end
end
PBDebug.log("[Status change] #{pbThis}'s sleep count is #{newStatusCount}") if newStatus == :SLEEP
# Form change check
pbCheckFormOnStatusChange
# Synchronize
if abilityActive?
BattleHandlers.triggerAbilityOnStatusInflicted(self.ability,self,user,newStatus)
end
# Status cures
pbItemStatusCureCheck
pbAbilityStatusCureCheck
# Petal Dance/Outrage/Thrash get cancelled immediately by falling asleep
# NOTE: I don't know why this applies only to Outrage and only to falling
# asleep (i.e. it doesn't cancel Rollout/Uproar/other multi-turn
# moves, and it doesn't cancel any moves if self becomes frozen/
# disabled/anything else). This behaviour was tested in Gen 5.
if @status == :SLEEP && @effects[PBEffects::Outrage] > 0
@effects[PBEffects::Outrage] = 0
@currentMove = nil
end
end
#=============================================================================
# Sleep
#=============================================================================
def asleep?
return pbHasStatus?(:SLEEP)
end
def pbCanSleep?(user, showMessages, move = nil, ignoreStatus = false)
return pbCanInflictStatus?(:SLEEP, user, showMessages, move, ignoreStatus)
end
def pbCanSleepYawn?
return false if self.status != :NONE
if affectedByTerrain?
return false if [:Electric, :Misty].include?(@battle.field.terrain)
end
if !hasActiveAbility?(:SOUNDPROOF)
@battle.eachBattler do |b|
return false if b.effects[PBEffects::Uproar]>0
end
end
if BattleHandlers.triggerStatusImmunityAbilityNonIgnorable(self.ability, self, :SLEEP)
return false
end
# NOTE: Bulbapedia claims that Flower Veil shouldn't prevent sleep due to
# drowsiness, but I disagree because that makes no sense. Also, the
# comparable Sweet Veil does prevent sleep due to drowsiness.
if abilityActive? && BattleHandlers.triggerStatusImmunityAbility(self.ability, self, :SLEEP)
return false
end
eachAlly do |b|
next if !b.abilityActive?
next if !BattleHandlers.triggerStatusImmunityAllyAbility(b.ability, self, :SLEEP)
return false
end
# NOTE: Bulbapedia claims that Safeguard shouldn't prevent sleep due to
# drowsiness. I disagree with this too. Compare with the other sided
# effects Misty/Electric Terrain, which do prevent it.
return false if pbOwnSide.effects[PBEffects::Safeguard]>0
return true
end
def pbSleep(msg = nil)
pbInflictStatus(:SLEEP, pbSleepDuration, msg)
end
def pbSleepSelf(msg = nil, duration = -1)
pbInflictStatus(:SLEEP, pbSleepDuration(duration), msg)
end
def pbSleepDuration(duration = -1)
duration = 2 + @battle.pbRandom(3) if duration <= 0
duration = (duration / 2).floor if hasActiveAbility?(:EARLYBIRD)
return duration
end
#=============================================================================
# Poison
#=============================================================================
def poisoned?
return pbHasStatus?(:POISON)
end
def pbCanPoison?(user, showMessages, move = nil)
return pbCanInflictStatus?(:POISON, user, showMessages, move)
end
def pbCanPoisonSynchronize?(target)
return pbCanSynchronizeStatus?(:POISON, target)
end
def pbPoison(user = nil, msg = nil, toxic = false)
pbInflictStatus(:POISON, (toxic) ? 1 : 0, msg, user)
end
#=============================================================================
# Burn
#=============================================================================
def burned?
return pbHasStatus?(:BURN)
end
def pbCanBurn?(user, showMessages, move = nil)
return pbCanInflictStatus?(:BURN, user, showMessages, move)
end
def pbCanBurnSynchronize?(target)
return pbCanSynchronizeStatus?(:BURN, target)
end
def pbBurn(user = nil, msg = nil)
pbInflictStatus(:BURN, 0, msg, user)
end
#=============================================================================
# Paralyze
#=============================================================================
def paralyzed?
return pbHasStatus?(:PARALYSIS)
end
def pbCanParalyze?(user, showMessages, move = nil)
return pbCanInflictStatus?(:PARALYSIS, user, showMessages, move)
end
def pbCanParalyzeSynchronize?(target)
return pbCanSynchronizeStatus?(:PARALYSIS, target)
end
def pbParalyze(user = nil, msg = nil)
pbInflictStatus(:PARALYSIS, 0, msg, user)
end
#=============================================================================
# Freeze
#=============================================================================
def frozen?
return pbHasStatus?(:FROZEN)
end
def pbCanFreeze?(user, showMessages, move = nil)
return pbCanInflictStatus?(:FROZEN, user, showMessages, move)
end
def pbFreeze(msg = nil)
pbInflictStatus(:FROZEN, 0, msg)
end
#=============================================================================
# Generalised status displays
#=============================================================================
def pbContinueStatus
if self.status == :POISON && @statusCount > 0
@battle.pbCommonAnimation("Toxic", self)
else
anim_name = GameData::Status.get(self.status).animation
@battle.pbCommonAnimation(anim_name, self) if anim_name
end
yield if block_given?
case self.status
when :SLEEP
@battle.pbDisplay(_INTL("{1} is fast asleep.", pbThis))
when :POISON
@battle.pbDisplay(_INTL("{1} was hurt by poison!", pbThis))
when :BURN
@battle.pbDisplay(_INTL("{1} was hurt by its burn!", pbThis))
when :PARALYSIS
@battle.pbDisplay(_INTL("{1} is paralyzed! It can't move!", pbThis))
when :FROZEN
@battle.pbDisplay(_INTL("{1} is frozen solid!", pbThis))
end
PBDebug.log("[Status continues] #{pbThis}'s sleep count is #{@statusCount}") if self.status == :SLEEP
end
def pbCureStatus(showMessages=true)
oldStatus = status
self.status = :NONE
if showMessages
case oldStatus
when :SLEEP then @battle.pbDisplay(_INTL("{1} woke up!", pbThis))
when :POISON then @battle.pbDisplay(_INTL("{1} was cured of its poisoning.", pbThis))
when :BURN then @battle.pbDisplay(_INTL("{1}'s burn was healed.", pbThis))
when :PARALYSIS then @battle.pbDisplay(_INTL("{1} was cured of paralysis.", pbThis))
when :FROZEN then @battle.pbDisplay(_INTL("{1} thawed out!", pbThis))
end
end
PBDebug.log("[Status change] #{pbThis}'s status was cured") if !showMessages
end
#=============================================================================
# Confusion
#=============================================================================
def pbCanConfuse?(user=nil,showMessages=true,move=nil,selfInflicted=false)
return false if fainted?
if @effects[PBEffects::Confusion]>0
@battle.pbDisplay(_INTL("{1} is already confused.",pbThis)) if showMessages
return false
end
if @effects[PBEffects::Substitute]>0 && !(move && move.ignoresSubstitute?(user)) &&
!selfInflicted
@battle.pbDisplay(_INTL("But it failed!")) if showMessages
return false
end
# Terrains immunity
if affectedByTerrain? && @battle.field.terrain == :Misty
@battle.pbDisplay(_INTL("{1} surrounds itself with misty terrain!",pbThis(true))) if showMessages
return false
end
if selfInflicted || !@battle.moldBreaker
if hasActiveAbility?(:OWNTEMPO)
if showMessages
@battle.pbShowAbilitySplash(self)
if PokeBattle_SceneConstants::USE_ABILITY_SPLASH
@battle.pbDisplay(_INTL("{1} doesn't become confused!",pbThis))
else
@battle.pbDisplay(_INTL("{1}'s {2} prevents confusion!",pbThis,abilityName))
end
@battle.pbHideAbilitySplash(self)
end
return false
end
end
if pbOwnSide.effects[PBEffects::Safeguard]>0 && !selfInflicted &&
!(user && user.hasActiveAbility?(:INFILTRATOR))
@battle.pbDisplay(_INTL("{1}'s team is protected by Safeguard!",pbThis)) if showMessages
return false
end
return true
end
def pbCanConfuseSelf?(showMessages)
return pbCanConfuse?(nil,showMessages,nil,true)
end
def pbConfuse(msg=nil)
@effects[PBEffects::Confusion] = pbConfusionDuration
@battle.pbCommonAnimation("Confusion",self)
msg = _INTL("{1} became confused!",pbThis) if !msg || msg==""
@battle.pbDisplay(msg)
PBDebug.log("[Lingering effect] #{pbThis}'s confusion count is #{@effects[PBEffects::Confusion]}")
# Confusion cures
pbItemStatusCureCheck
pbAbilityStatusCureCheck
end
def pbConfusionDuration(duration=-1)
duration = 2+@battle.pbRandom(4) if duration<=0
return duration
end
def pbCureConfusion
@effects[PBEffects::Confusion] = 0
end
#=============================================================================
# Attraction
#=============================================================================
def pbCanAttract?(user,showMessages=true)
return false if fainted?
return false if !user || user.fainted?
if @effects[PBEffects::Attract]>=0
@battle.pbDisplay(_INTL("{1} is unaffected!",pbThis)) if showMessages
return false
end
agender = user.gender
ogender = gender
if agender==2 || ogender==2 || agender==ogender
@battle.pbDisplay(_INTL("{1} is unaffected!",pbThis)) if showMessages
return false
end
if !@battle.moldBreaker
if hasActiveAbility?([:AROMAVEIL,:OBLIVIOUS])
if showMessages
@battle.pbShowAbilitySplash(self)
if PokeBattle_SceneConstants::USE_ABILITY_SPLASH
@battle.pbDisplay(_INTL("{1} is unaffected!",pbThis))
else
@battle.pbDisplay(_INTL("{1}'s {2} prevents romance!",pbThis,abilityName))
end
@battle.pbHideAbilitySplash(self)
end
return false
else
eachAlly do |b|
next if !b.hasActiveAbility?(:AROMAVEIL)
if showMessages
@battle.pbShowAbilitySplash(self)
if PokeBattle_SceneConstants::USE_ABILITY_SPLASH
@battle.pbDisplay(_INTL("{1} is unaffected!",pbThis))
else
@battle.pbDisplay(_INTL("{1}'s {2} prevents romance!",b.pbThis,b.abilityName))
end
@battle.pbHideAbilitySplash(self)
end
return true
end
end
end
return true
end
def pbAttract(user,msg=nil)
@effects[PBEffects::Attract] = user.index
@battle.pbCommonAnimation("Attract",self)
msg = _INTL("{1} fell in love!",pbThis) if !msg || msg==""
@battle.pbDisplay(msg)
# Destiny Knot
if hasActiveItem?(:DESTINYKNOT) && user.pbCanAttract?(self,false)
user.pbAttract(self,_INTL("{1} fell in love from the {2}!",user.pbThis(true),itemName))
end
# Attraction cures
pbItemStatusCureCheck
pbAbilityStatusCureCheck
end
def pbCureAttract
@effects[PBEffects::Attract] = -1
end
#=============================================================================
# Flinching
#=============================================================================
def pbFlinch(_user=nil)
return if hasActiveAbility?(:INNERFOCUS) && !@battle.moldBreaker
@effects[PBEffects::Flinch] = true
end
end

View File

@@ -0,0 +1,308 @@
class PokeBattle_Battler
#=============================================================================
# Increase stat stages
#=============================================================================
def statStageAtMax?(stat)
return @stages[stat]>=6
end
def pbCanRaiseStatStage?(stat,user=nil,move=nil,showFailMsg=false,ignoreContrary=false)
return false if fainted?
# Contrary
if hasActiveAbility?(:CONTRARY) && !ignoreContrary && !@battle.moldBreaker
return pbCanLowerStatStage?(stat,user,move,showFailMsg,true)
end
# Check the stat stage
if statStageAtMax?(stat)
@battle.pbDisplay(_INTL("{1}'s {2} won't go any higher!",
pbThis, GameData::Stat.get(stat).name)) if showFailMsg
return false
end
return true
end
def pbRaiseStatStageBasic(stat,increment,ignoreContrary=false)
if !@battle.moldBreaker
# Contrary
if hasActiveAbility?(:CONTRARY) && !ignoreContrary
return pbLowerStatStageBasic(stat,increment,true)
end
# Simple
increment *= 2 if hasActiveAbility?(:SIMPLE)
end
# Change the stat stage
increment = [increment,6-@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})")
@stages[stat] += increment
end
return increment
end
def pbRaiseStatStage(stat,increment,user,showAnim=true,ignoreContrary=false)
# Contrary
if hasActiveAbility?(:CONTRARY) && !ignoreContrary && !@battle.moldBreaker
return pbLowerStatStage(stat,increment,user,showAnim,true)
end
# Perform the stat stage change
increment = pbRaiseStatStageBasic(stat,increment,ignoreContrary)
return false if increment<=0
# Stat up animation and message
@battle.pbCommonAnimation("StatUp",self) if showAnim
arrStatTexts = [
_INTL("{1}'s {2} rose!",pbThis,GameData::Stat.get(stat).name),
_INTL("{1}'s {2} rose sharply!",pbThis,GameData::Stat.get(stat).name),
_INTL("{1}'s {2} rose drastically!",pbThis,GameData::Stat.get(stat).name)]
@battle.pbDisplay(arrStatTexts[[increment-1,2].min])
# Trigger abilities upon stat gain
if abilityActive?
BattleHandlers.triggerAbilityOnStatGain(self.ability,self,stat,user)
end
return true
end
def pbRaiseStatStageByCause(stat,increment,user,cause,showAnim=true,ignoreContrary=false)
# Contrary
if hasActiveAbility?(:CONTRARY) && !ignoreContrary && !@battle.moldBreaker
return pbLowerStatStageByCause(stat,increment,user,cause,showAnim,true)
end
# Perform the stat stage change
increment = pbRaiseStatStageBasic(stat,increment,ignoreContrary)
return false if increment<=0
# Stat up animation and message
@battle.pbCommonAnimation("StatUp",self) if showAnim
if user.index==@index
arrStatTexts = [
_INTL("{1}'s {2} raised its {3}!",pbThis,cause,GameData::Stat.get(stat).name),
_INTL("{1}'s {2} sharply raised its {3}!",pbThis,cause,GameData::Stat.get(stat).name),
_INTL("{1}'s {2} drastically raised its {3}!",pbThis,cause,GameData::Stat.get(stat).name)]
else
arrStatTexts = [
_INTL("{1}'s {2} raised {3}'s {4}!",user.pbThis,cause,pbThis(true),GameData::Stat.get(stat).name),
_INTL("{1}'s {2} sharply raised {3}'s {4}!",user.pbThis,cause,pbThis(true),GameData::Stat.get(stat).name),
_INTL("{1}'s {2} drastically raised {3}'s {4}!",user.pbThis,cause,pbThis(true),GameData::Stat.get(stat).name)]
end
@battle.pbDisplay(arrStatTexts[[increment-1,2].min])
# Trigger abilities upon stat gain
if abilityActive?
BattleHandlers.triggerAbilityOnStatGain(self.ability,self,stat,user)
end
return true
end
def pbRaiseStatStageByAbility(stat,increment,user,splashAnim=true)
return false if fainted?
ret = false
@battle.pbShowAbilitySplash(user) if splashAnim
if pbCanRaiseStatStage?(stat,user,nil,PokeBattle_SceneConstants::USE_ABILITY_SPLASH)
if PokeBattle_SceneConstants::USE_ABILITY_SPLASH
ret = pbRaiseStatStage(stat,increment,user)
else
ret = pbRaiseStatStageByCause(stat,increment,user,user.abilityName)
end
end
@battle.pbHideAbilitySplash(user) if splashAnim
return ret
end
#=============================================================================
# Decrease stat stages
#=============================================================================
def statStageAtMin?(stat)
return @stages[stat]<=-6
end
def pbCanLowerStatStage?(stat,user=nil,move=nil,showFailMsg=false,ignoreContrary=false)
return false if fainted?
# Contrary
if hasActiveAbility?(:CONTRARY) && !ignoreContrary && !@battle.moldBreaker
return pbCanRaiseStatStage?(stat,user,move,showFailMsg,true)
end
if !user || user.index!=@index # Not self-inflicted
if @effects[PBEffects::Substitute]>0 && !(move && move.ignoresSubstitute?(user))
@battle.pbDisplay(_INTL("{1} is protected by its substitute!",pbThis)) if showFailMsg
return false
end
if pbOwnSide.effects[PBEffects::Mist]>0 &&
!(user && user.hasActiveAbility?(:INFILTRATOR))
@battle.pbDisplay(_INTL("{1} is protected by Mist!",pbThis)) if showFailMsg
return false
end
if abilityActive?
return false if BattleHandlers.triggerStatLossImmunityAbility(
self.ability,self,stat,@battle,showFailMsg) if !@battle.moldBreaker
return false if BattleHandlers.triggerStatLossImmunityAbilityNonIgnorable(
self.ability,self,stat,@battle,showFailMsg)
end
if !@battle.moldBreaker
eachAlly do |b|
next if !b.abilityActive?
return false if BattleHandlers.triggerStatLossImmunityAllyAbility(
b.ability,b,self,stat,@battle,showFailMsg)
end
end
end
# Check the stat stage
if statStageAtMin?(stat)
@battle.pbDisplay(_INTL("{1}'s {2} won't go any lower!",
pbThis, GameData::Stat.get(stat).name)) if showFailMsg
return false
end
return true
end
def pbLowerStatStageBasic(stat,increment,ignoreContrary=false)
if !@battle.moldBreaker
# Contrary
if hasActiveAbility?(:CONTRARY) && !ignoreContrary
return pbRaiseStatStageBasic(stat,increment,true)
end
# Simple
increment *= 2 if hasActiveAbility?(:SIMPLE)
end
# Change the stat stage
increment = [increment,6+@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})")
@stages[stat] -= increment
end
return increment
end
def pbLowerStatStage(stat,increment,user,showAnim=true,ignoreContrary=false)
# Contrary
if hasActiveAbility?(:CONTRARY) && !ignoreContrary && !@battle.moldBreaker
return pbRaiseStatStage(stat,increment,user,showAnim,true)
end
# Perform the stat stage change
increment = pbLowerStatStageBasic(stat,increment,ignoreContrary)
return false if increment<=0
# Stat down animation and message
@battle.pbCommonAnimation("StatDown",self) if showAnim
arrStatTexts = [
_INTL("{1}'s {2} fell!",pbThis,GameData::Stat.get(stat).name),
_INTL("{1}'s {2} harshly fell!",pbThis,GameData::Stat.get(stat).name),
_INTL("{1}'s {2} severely fell!",pbThis,GameData::Stat.get(stat).name)]
@battle.pbDisplay(arrStatTexts[[increment-1,2].min])
# Trigger abilities upon stat loss
if abilityActive?
BattleHandlers.triggerAbilityOnStatLoss(self.ability,self,stat,user)
end
return true
end
def pbLowerStatStageByCause(stat,increment,user,cause,showAnim=true,ignoreContrary=false)
# Contrary
if hasActiveAbility?(:CONTRARY) && !ignoreContrary && !@battle.moldBreaker
return pbRaiseStatStageByCause(stat,increment,user,cause,showAnim,true)
end
# Perform the stat stage change
increment = pbLowerStatStageBasic(stat,increment,ignoreContrary)
return false if increment<=0
# Stat down animation and message
@battle.pbCommonAnimation("StatDown",self) if showAnim
if user.index==@index
arrStatTexts = [
_INTL("{1}'s {2} lowered its {3}!",pbThis,cause,GameData::Stat.get(stat).name),
_INTL("{1}'s {2} harshly lowered its {3}!",pbThis,cause,GameData::Stat.get(stat).name),
_INTL("{1}'s {2} severely lowered its {3}!",pbThis,cause,GameData::Stat.get(stat).name)]
else
arrStatTexts = [
_INTL("{1}'s {2} lowered {3}'s {4}!",user.pbThis,cause,pbThis(true),GameData::Stat.get(stat).name),
_INTL("{1}'s {2} harshly lowered {3}'s {4}!",user.pbThis,cause,pbThis(true),GameData::Stat.get(stat).name),
_INTL("{1}'s {2} severely lowered {3}'s {4}!",user.pbThis,cause,pbThis(true),GameData::Stat.get(stat).name)]
end
@battle.pbDisplay(arrStatTexts[[increment-1,2].min])
# Trigger abilities upon stat loss
if abilityActive?
BattleHandlers.triggerAbilityOnStatLoss(self.ability,self,stat,user)
end
return true
end
def pbLowerStatStageByAbility(stat,increment,user,splashAnim=true,checkContact=false)
ret = false
@battle.pbShowAbilitySplash(user) if splashAnim
if pbCanLowerStatStage?(stat,user,nil,PokeBattle_SceneConstants::USE_ABILITY_SPLASH) &&
(!checkContact || affectedByContactEffect?(PokeBattle_SceneConstants::USE_ABILITY_SPLASH))
if PokeBattle_SceneConstants::USE_ABILITY_SPLASH
ret = pbLowerStatStage(stat,increment,user)
else
ret = pbLowerStatStageByCause(stat,increment,user,user.abilityName)
end
end
@battle.pbHideAbilitySplash(user) if splashAnim
return ret
end
def pbLowerAttackStatStageIntimidate(user)
return false if fainted?
# NOTE: Substitute intentially blocks Intimidate even if self has Contrary.
if @effects[PBEffects::Substitute]>0
if PokeBattle_SceneConstants::USE_ABILITY_SPLASH
@battle.pbDisplay(_INTL("{1} is protected by its substitute!",pbThis))
else
@battle.pbDisplay(_INTL("{1}'s substitute protected it from {2}'s {3}!",
pbThis,user.pbThis(true),user.abilityName))
end
return false
end
if PokeBattle_SceneConstants::USE_ABILITY_SPLASH
return pbLowerStatStageByAbility(:ATTACK,1,user,false)
end
# NOTE: These checks exist to ensure appropriate messages are shown if
# Intimidate is blocked somehow (i.e. the messages should mention the
# Intimidate ability by name).
if !hasActiveAbility?(:CONTRARY)
if pbOwnSide.effects[PBEffects::Mist]>0
@battle.pbDisplay(_INTL("{1} is protected from {2}'s {3} by Mist!",
pbThis,user.pbThis(true),user.abilityName))
return false
end
if abilityActive?
if BattleHandlers.triggerStatLossImmunityAbility(self.ability,self,:ATTACK,@battle,false) ||
BattleHandlers.triggerStatLossImmunityAbilityNonIgnorable(self.ability,self,:ATTACK,@battle,false)
@battle.pbDisplay(_INTL("{1}'s {2} prevented {3}'s {4} from working!",
pbThis,abilityName,user.pbThis(true),user.abilityName))
return false
end
end
eachAlly do |b|
next if !b.abilityActive?
if BattleHandlers.triggerStatLossImmunityAllyAbility(b.ability,b,self,:ATTACK,@battle,false)
@battle.pbDisplay(_INTL("{1} is protected from {2}'s {3} by {4}'s {5}!",
pbThis,user.pbThis(true),user.abilityName,b.pbThis(true),b.abilityName))
return false
end
end
end
return false if !pbCanLowerStatStage?(:ATTACK,user)
return pbLowerStatStageByCause(:ATTACK,1,user,user.abilityName)
end
#=============================================================================
# Reset stat stages
#=============================================================================
def hasAlteredStatStages?
GameData::Stat.each_battle { |s| return true if @stages[s.id] != 0 }
return false
end
def hasRaisedStatStages?
GameData::Stat.each_battle { |s| return true if @stages[s.id] > 0 }
return false
end
def hasLoweredStatStages?
GameData::Stat.each_battle { |s| return true if @stages[s.id] < 0 }
return false
end
def pbResetStatStages
GameData::Stat.each_battle { |s| @stages[s.id] = 0 }
end
end

View File

@@ -0,0 +1,296 @@
class PokeBattle_Battler
#=============================================================================
# Called when a Pokémon (self) is sent into battle or its ability changes.
#=============================================================================
def pbEffectsOnSwitchIn(switchIn=false)
# Healing Wish/Lunar Dance/entry hazards
@battle.pbOnActiveOne(self) if switchIn
# Primal Revert upon entering battle
@battle.pbPrimalReversion(@index) if !fainted?
# Ending primordial weather, checking Trace
pbContinualAbilityChecks(true)
# Abilities that trigger upon switching in
if (!fainted? && unstoppableAbility?) || abilityActive?
BattleHandlers.triggerAbilityOnSwitchIn(self.ability,self,@battle)
end
# Check for end of primordial weather
@battle.pbEndPrimordialWeather
# Items that trigger upon switching in (Air Balloon message)
if switchIn && itemActive?
BattleHandlers.triggerItemOnSwitchIn(self.item,self,@battle)
end
# Berry check, status-curing ability check
pbHeldItemTriggerCheck if switchIn
pbAbilityStatusCureCheck
end
#=============================================================================
# Ability effects
#=============================================================================
def pbAbilitiesOnSwitchOut
if abilityActive?
BattleHandlers.triggerAbilityOnSwitchOut(self.ability,self,false)
end
# Reset form
@battle.peer.pbOnLeavingBattle(@battle,@pokemon,@battle.usedInBattle[idxOwnSide][@index/2])
# Treat self as fainted
@hp = 0
@fainted = true
# Check for end of primordial weather
@battle.pbEndPrimordialWeather
end
def pbAbilitiesOnFainting
# Self fainted; check all other battlers to see if their abilities trigger
@battle.pbPriority(true).each do |b|
next if !b || !b.abilityActive?
BattleHandlers.triggerAbilityChangeOnBattlerFainting(b.ability,b,self,@battle)
end
@battle.pbPriority(true).each do |b|
next if !b || !b.abilityActive?
BattleHandlers.triggerAbilityOnBattlerFainting(b.ability,b,self,@battle)
end
end
# Used for Emergency Exit/Wimp Out.
def pbAbilitiesOnDamageTaken(oldHP,newHP=-1)
return false if !abilityActive?
newHP = @hp if newHP<0
return false if oldHP<@totalhp/2 || newHP>=@totalhp/2 # Didn't drop below half
ret = BattleHandlers.triggerAbilityOnHPDroppedBelowHalf(self.ability,self,@battle)
return ret # Whether self has switched out
end
# Called when a Pokémon (self) enters battle, at the end of each move used,
# and at the end of each round.
def pbContinualAbilityChecks(onSwitchIn=false)
# Check for end of primordial weather
@battle.pbEndPrimordialWeather
# Trace
if hasActiveAbility?(:TRACE)
# NOTE: In Gen 5 only, Trace only triggers upon the Trace bearer switching
# in and not at any later times, even if a traceable ability turns
# up later. Essentials ignores this, and allows Trace to trigger
# whenever it can even in the old battle mechanics.
choices = []
@battle.eachOtherSideBattler(@index) do |b|
next if b.ungainableAbility? ||
[:POWEROFALCHEMY, :RECEIVER, :TRACE].include?(b.ability_id)
choices.push(b)
end
if choices.length>0
choice = choices[@battle.pbRandom(choices.length)]
@battle.pbShowAbilitySplash(self)
self.ability = choice.ability
@battle.pbDisplay(_INTL("{1} traced {2}'s {3}!",pbThis,choice.pbThis(true),choice.abilityName))
@battle.pbHideAbilitySplash(self)
if !onSwitchIn && (unstoppableAbility? || abilityActive?)
BattleHandlers.triggerAbilityOnSwitchIn(self.ability,self,@battle)
end
end
end
end
#=============================================================================
# Ability curing
#=============================================================================
# Cures status conditions, confusion and infatuation.
def pbAbilityStatusCureCheck
if abilityActive?
BattleHandlers.triggerStatusCureAbility(self.ability,self)
end
end
#=============================================================================
# Ability change
#=============================================================================
def pbOnAbilityChanged(oldAbil)
if @effects[PBEffects::Illusion] && oldAbil == :ILLUSION
@effects[PBEffects::Illusion] = nil
if !@effects[PBEffects::Transform]
@battle.scene.pbChangePokemon(self, @pokemon)
@battle.pbDisplay(_INTL("{1}'s {2} wore off!", pbThis, GameData::Ability.get(oldAbil).name))
@battle.pbSetSeen(self)
end
end
@effects[PBEffects::GastroAcid] = false if unstoppableAbility?
@effects[PBEffects::SlowStart] = 0 if self.ability != :SLOWSTART
# Revert form if Flower Gift/Forecast was lost
pbCheckFormOnWeatherChange
# Check for end of primordial weather
@battle.pbEndPrimordialWeather
end
#=============================================================================
# Held item consuming/removing
#=============================================================================
def canConsumeBerry?
return false if @battle.pbCheckOpposingAbility(:UNNERVE, @index)
return true
end
def canConsumePinchBerry?(check_gluttony = true)
return false if !canConsumeBerry?
return true if @hp <= @totalhp / 4
return true if @hp <= @totalhp / 2 && (!check_gluttony || hasActiveAbility?(:GLUTTONY))
return false
end
# permanent is whether the item is lost even after battle. Is false for Knock
# Off.
def pbRemoveItem(permanent = true)
@effects[PBEffects::ChoiceBand] = nil
@effects[PBEffects::Unburden] = true if self.item
setInitialItem(nil) if permanent && self.item == self.initialItem
self.item = nil
end
def pbConsumeItem(recoverable=true,symbiosis=true,belch=true)
PBDebug.log("[Item consumed] #{pbThis} consumed its held #{itemName}")
if recoverable
setRecycleItem(@item_id)
@effects[PBEffects::PickupItem] = @item_id
@effects[PBEffects::PickupUse] = @battle.nextPickupUse
end
setBelched if belch && self.item.is_berry?
pbRemoveItem
pbSymbiosis if symbiosis
end
def pbSymbiosis
return if fainted?
return if !self.item
@battle.pbPriority(true).each do |b|
next if b.opposes?
next if !b.hasActiveAbility?(:SYMBIOSIS)
next if !b.item || b.unlosableItem?(b.item)
next if unlosableItem?(b.item)
@battle.pbShowAbilitySplash(b)
if PokeBattle_SceneConstants::USE_ABILITY_SPLASH
@battle.pbDisplay(_INTL("{1} shared its {2} with {3}!",
b.pbThis,b.itemName,pbThis(true)))
else
@battle.pbDisplay(_INTL("{1}'s {2} let it share its {3} with {4}!",
b.pbThis,b.abilityName,b.itemName,pbThis(true)))
end
self.item = b.item
b.item = nil
b.effects[PBEffects::Unburden] = true
@battle.pbHideAbilitySplash(b)
pbHeldItemTriggerCheck
break
end
end
# item_to_use is an item ID or GameData::Item object. own_item is whether the
# item is held by self. fling is for Fling only.
def pbHeldItemTriggered(item_to_use, own_item = true, fling = false)
# Cheek Pouch
if hasActiveAbility?(:CHEEKPOUCH) && GameData::Item.get(item_to_use).is_berry? && canHeal?
@battle.pbShowAbilitySplash(self)
pbRecoverHP(@totalhp / 3)
if PokeBattle_SceneConstants::USE_ABILITY_SPLASH
@battle.pbDisplay(_INTL("{1}'s HP was restored.", pbThis))
else
@battle.pbDisplay(_INTL("{1}'s {2} restored its HP.", pbThis, abilityName))
end
@battle.pbHideAbilitySplash(self)
end
pbConsumeItem if own_item
pbSymbiosis if !own_item && !fling # Bug Bite/Pluck users trigger Symbiosis
end
#=============================================================================
# Held item trigger checks
#=============================================================================
# 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.
# fling is for Fling only.
def pbHeldItemTriggerCheck(item_to_use = nil, fling = false)
return if fainted?
return if !item_to_use && !itemActive?
pbItemHPHealCheck(item_to_use, fling)
pbItemStatusCureCheck(item_to_use, fling)
pbItemEndOfMoveCheck(item_to_use, fling)
# For Enigma Berry, Kee Berry and Maranga Berry, which have their effects
# when forcibly consumed by Pluck/Fling.
if item_to_use
itm = item_to_use || self.item
if BattleHandlers.triggerTargetItemOnHitPositiveBerry(itm, self, @battle, true)
pbHeldItemTriggered(itm, false, fling)
end
end
end
# item_to_use is an item ID for Bug Bite/Pluck and Fling, and nil otherwise.
# fling is for Fling only.
def pbItemHPHealCheck(item_to_use = nil, fling = false)
return if !item_to_use && !itemActive?
itm = item_to_use || self.item
if BattleHandlers.triggerHPHealItem(itm, self, @battle, !item_to_use.nil?)
pbHeldItemTriggered(itm, item_to_use.nil?, fling)
elsif !item_to_use
pbItemTerrainStatBoostCheck
end
end
# Cures status conditions, confusion, infatuation and the other effects cured
# by Mental Herb.
# item_to_use is an item ID for Bug Bite/Pluck and Fling, and nil otherwise.
# fling is for Fling only.
def pbItemStatusCureCheck(item_to_use = nil, fling = false)
return if fainted?
return if !item_to_use && !itemActive?
itm = item_to_use || self.item
if BattleHandlers.triggerStatusCureItem(itm, self, @battle, !item_to_use.nil?)
pbHeldItemTriggered(itm, item_to_use.nil?, fling)
end
end
# Called at the end of using a move.
# item_to_use is an item ID for Bug Bite/Pluck and Fling, and nil otherwise.
# fling is for Fling only.
def pbItemEndOfMoveCheck(item_to_use = nil, fling = false)
return if fainted?
return if !item_to_use && !itemActive?
itm = item_to_use || self.item
if BattleHandlers.triggerEndOfMoveItem(itm, self, @battle, !item_to_use.nil?)
pbHeldItemTriggered(itm, item_to_use.nil?, fling)
elsif BattleHandlers.triggerEndOfMoveStatRestoreItem(itm, self, @battle, !item_to_use.nil?)
pbHeldItemTriggered(itm, item_to_use.nil?, fling)
end
end
# Used for White Herb (restore lowered stats). Only called by Moody and Sticky
# Web, as all other stat reduction happens because of/during move usage and
# this handler is also called at the end of each move's usage.
# item_to_use is an item ID for Bug Bite/Pluck and Fling, and nil otherwise.
# fling is for Fling only.
def pbItemStatRestoreCheck(item_to_use = nil, fling = false)
return if fainted?
return if !item_to_use && !itemActive?
itm = item_to_use || self.item
if BattleHandlers.triggerEndOfMoveStatRestoreItem(itm, self, @battle, !item_to_use.nil?)
pbHeldItemTriggered(itm, item_to_use.nil?, fling)
end
end
# Called when the battle terrain changes and when a Pokémon loses HP.
def pbItemTerrainStatBoostCheck
return if !itemActive?
if BattleHandlers.triggerTerrainStatBoostItem(self.item, self, @battle)
pbHeldItemTriggered(self.item)
end
end
# Used for Adrenaline Orb. Called when Intimidate is triggered (even if
# Intimidate has no effect on the Pokémon).
def pbItemOnIntimidatedCheck
return if !itemActive?
if BattleHandlers.triggerItemOnIntimidated(self.item, self, @battle)
pbHeldItemTriggered(self.item)
end
end
end

View File

@@ -0,0 +1,728 @@
class PokeBattle_Battler
#=============================================================================
# Turn processing
#=============================================================================
def pbProcessTurn(choice,tryFlee=true)
return false if fainted?
# Wild roaming Pokémon always flee if possible
if tryFlee && @battle.wildBattle? && opposes? &&
@battle.rules["alwaysflee"] && @battle.pbCanRun?(@index)
pbBeginTurn(choice)
pbSEPlay("Battle flee")
@battle.pbDisplay(_INTL("{1} fled from battle!",pbThis))
@battle.decision = 3
pbEndTurn(choice)
return true
end
# Shift with the battler next to this one
if choice[0]==:Shift
idxOther = -1
case @battle.pbSideSize(@index)
when 2
idxOther = (@index+2)%4
when 3
if @index!=2 && @index!=3 # If not in middle spot already
idxOther = ((@index%2)==0) ? 2 : 3
end
end
if idxOther>=0
@battle.pbSwapBattlers(@index,idxOther)
case @battle.pbSideSize(@index)
when 2
@battle.pbDisplay(_INTL("{1} moved across!",pbThis))
when 3
@battle.pbDisplay(_INTL("{1} moved to the center!",pbThis))
end
end
pbBeginTurn(choice)
pbCancelMoves
@lastRoundMoved = @battle.turnCount # Done something this round
return true
end
# If this battler's action for this round wasn't "use a move"
if choice[0]!=:UseMove
# Clean up effects that end at battler's turn
pbBeginTurn(choice)
pbEndTurn(choice)
return false
end
# Turn is skipped if Pursuit was used during switch
if @effects[PBEffects::Pursuit]
@effects[PBEffects::Pursuit] = false
pbCancelMoves
pbEndTurn(choice)
@battle.pbJudge
return false
end
# Use the move
PBDebug.log("[Move usage] #{pbThis} started using #{choice[2].name}")
PBDebug.logonerr{
pbUseMove(choice,choice[2]==@battle.struggle)
}
@battle.pbJudge
# Update priority order
@battle.pbCalculatePriority if Settings::RECALCULATE_TURN_ORDER_AFTER_SPEED_CHANGES
return true
end
#=============================================================================
#
#=============================================================================
def pbBeginTurn(_choice)
# Cancel some lingering effects which only apply until the user next moves
@effects[PBEffects::BeakBlast] = false
@effects[PBEffects::DestinyBondPrevious] = @effects[PBEffects::DestinyBond]
@effects[PBEffects::DestinyBond] = false
@effects[PBEffects::Grudge] = false
@effects[PBEffects::MoveNext] = false
@effects[PBEffects::Quash] = 0
@effects[PBEffects::ShellTrap] = false
# Encore's effect ends if the encored move is no longer available
if @effects[PBEffects::Encore]>0 && pbEncoredMoveIndex<0
@effects[PBEffects::Encore] = 0
@effects[PBEffects::EncoreMove] = nil
end
end
# Called when the usage of various multi-turn moves is disrupted due to
# failing pbTryUseMove, being ineffective against all targets, or because
# Pursuit was used specially to intercept a switching foe.
# Cancels the use of multi-turn moves and counters thereof. Note that Hyper
# Beam's effect is NOT cancelled.
def pbCancelMoves
# Outragers get confused anyway if they are disrupted during their final
# turn of using the move
if @effects[PBEffects::Outrage]==1 && pbCanConfuseSelf?(false)
pbConfuse(_INTL("{1} became confused due to fatigue!",pbThis))
end
# Cancel usage of most multi-turn moves
@effects[PBEffects::TwoTurnAttack] = nil
@effects[PBEffects::Rollout] = 0
@effects[PBEffects::Outrage] = 0
@effects[PBEffects::Uproar] = 0
@effects[PBEffects::Bide] = 0
@currentMove = nil
# Reset counters for moves which increase them when used in succession
@effects[PBEffects::FuryCutter] = 0
end
def pbEndTurn(_choice)
@lastRoundMoved = @battle.turnCount # Done something this round
if !@effects[PBEffects::ChoiceBand] &&
hasActiveItem?([:CHOICEBAND,:CHOICESPECS,:CHOICESCARF])
if @lastMoveUsed && pbHasMove?(@lastMoveUsed)
@effects[PBEffects::ChoiceBand] = @lastMoveUsed
elsif @lastRegularMoveUsed && pbHasMove?(@lastRegularMoveUsed)
@effects[PBEffects::ChoiceBand] = @lastRegularMoveUsed
end
end
@effects[PBEffects::Charge] = 0 if @effects[PBEffects::Charge]==1
@effects[PBEffects::GemConsumed] = nil
@battle.eachBattler { |b| b.pbContinualAbilityChecks } # Trace, end primordial weathers
end
def pbConfusionDamage(msg)
@damageState.reset
@damageState.initialHP = @hp
confusionMove = PokeBattle_Confusion.new(@battle,nil)
confusionMove.calcType = confusionMove.pbCalcType(self) # nil
@damageState.typeMod = confusionMove.pbCalcTypeMod(confusionMove.calcType,self,self) # 8
confusionMove.pbCheckDamageAbsorption(self,self)
confusionMove.pbCalcDamage(self,self)
confusionMove.pbReduceDamage(self,self)
self.hp -= @damageState.hpLost
confusionMove.pbAnimateHitAndHPLost(self,[self])
@battle.pbDisplay(msg) # "It hurt itself in its confusion!"
confusionMove.pbRecordDamageLost(self,self)
confusionMove.pbEndureKOMessage(self)
pbFaint if fainted?
pbItemHPHealCheck
end
#=============================================================================
# Simple "use move" method, used when a move calls another move and for Future
# Sight's attack
#=============================================================================
def pbUseMoveSimple(moveID,target=-1,idxMove=-1,specialUsage=true)
choice = []
choice[0] = :UseMove # "Use move"
choice[1] = idxMove # Index of move to be used in user's moveset
if idxMove>=0
choice[2] = @moves[idxMove]
else
choice[2] = PokeBattle_Move.from_pokemon_move(@battle, Pokemon::Move.new(moveID))
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}")
pbUseMove(choice,specialUsage)
end
#=============================================================================
# Master "use move" method
#=============================================================================
def pbUseMove(choice,specialUsage=false)
# NOTE: This is intentionally determined before a multi-turn attack can
# set specialUsage to true.
skipAccuracyCheck = (specialUsage && choice[2]!=@battle.struggle)
# Start using the move
pbBeginTurn(choice)
# Force the use of certain moves if they're already being used
if usingMultiTurnAttack?
choice[2] = PokeBattle_Move.from_pokemon_move(@battle, Pokemon::Move.new(@currentMove))
specialUsage = true
elsif @effects[PBEffects::Encore]>0 && choice[1]>=0 &&
@battle.pbCanShowCommands?(@index)
idxEncoredMove = pbEncoredMoveIndex
if idxEncoredMove>=0 && @battle.pbCanChooseMove?(@index,idxEncoredMove,false)
if choice[1]!=idxEncoredMove # Change move if battler was Encored mid-round
choice[1] = idxEncoredMove
choice[2] = @moves[idxEncoredMove]
choice[3] = -1 # No target chosen
end
end
end
# Labels the move being used as "move"
move = choice[2]
return if !move # if move was not chosen somehow
# Try to use the move (inc. disobedience)
@lastMoveFailed = false
if !pbTryUseMove(choice,move,specialUsage,skipAccuracyCheck)
@lastMoveUsed = nil
@lastMoveUsedType = nil
if !specialUsage
@lastRegularMoveUsed = nil
@lastRegularMoveTarget = -1
end
@battle.pbGainExp # In case self is KO'd due to confusion
pbCancelMoves
pbEndTurn(choice)
return
end
move = choice[2] # In case disobedience changed the move to be used
return if !move # if move was not chosen somehow
# Subtract PP
if !specialUsage
if !pbReducePP(move)
@battle.pbDisplay(_INTL("{1} used {2}!",pbThis,move.name))
@battle.pbDisplay(_INTL("But there was no PP left for the move!"))
@lastMoveUsed = nil
@lastMoveUsedType = nil
@lastRegularMoveUsed = nil
@lastRegularMoveTarget = -1
@lastMoveFailed = true
pbCancelMoves
pbEndTurn(choice)
return
end
end
# Stance Change
if isSpecies?(:AEGISLASH) && self.ability == :STANCECHANGE
if move.damagingMove?
pbChangeForm(1,_INTL("{1} changed to Blade Forme!",pbThis))
elsif move.id == :KINGSSHIELD
pbChangeForm(0,_INTL("{1} changed to Shield Forme!",pbThis))
end
end
# Calculate the move's type during this usage
move.calcType = move.pbCalcType(self)
# Start effect of Mold Breaker
@battle.moldBreaker = hasMoldBreaker?
# Remember that user chose a two-turn move
if move.pbIsChargingTurn?(self)
# Beginning the use of a two-turn attack
@effects[PBEffects::TwoTurnAttack] = move.id
@currentMove = move.id
else
@effects[PBEffects::TwoTurnAttack] = nil # Cancel use of two-turn attack
end
# Add to counters for moves which increase them when used in succession
move.pbChangeUsageCounters(self,specialUsage)
# Charge up Metronome item
if hasActiveItem?(:METRONOME) && !move.callsAnotherMove?
if @lastMoveUsed && @lastMoveUsed==move.id && !@lastMoveFailed
@effects[PBEffects::Metronome] += 1
else
@effects[PBEffects::Metronome] = 0
end
end
# Record move as having been used
@lastMoveUsed = move.id
@lastMoveUsedType = move.calcType # For Conversion 2
if !specialUsage
@lastRegularMoveUsed = move.id # For Disable, Encore, Instruct, Mimic, Mirror Move, Sketch, Spite
@lastRegularMoveTarget = choice[3] # For Instruct (remembering original target is fine)
@movesUsed.push(move.id) if !@movesUsed.include?(move.id) # For Last Resort
end
@battle.lastMoveUsed = move.id # For Copycat
@battle.lastMoveUser = @index # For "self KO" battle clause to avoid draws
@battle.successStates[@index].useState = 1 # Battle Arena - assume failure
# Find the default user (self or Snatcher) and target(s)
user = pbFindUser(choice,move)
user = pbChangeUser(choice,move,user)
targets = pbFindTargets(choice,move,user)
targets = pbChangeTargets(move,user,targets)
# Pressure
if !specialUsage
targets.each do |b|
next unless b.opposes?(user) && b.hasActiveAbility?(:PRESSURE)
PBDebug.log("[Ability triggered] #{b.pbThis}'s #{b.abilityName}")
user.pbReducePP(move)
end
if move.pbTarget(user).affects_foe_side
@battle.eachOtherSideBattler(user) do |b|
next unless b.hasActiveAbility?(:PRESSURE)
PBDebug.log("[Ability triggered] #{b.pbThis}'s #{b.abilityName}")
user.pbReducePP(move)
end
end
end
# Dazzling/Queenly Majesty make the move fail here
@battle.pbPriority(true).each do |b|
next if !b || !b.abilityActive?
if BattleHandlers.triggerMoveBlockingAbility(b.ability,b,user,targets,move,@battle)
@battle.pbDisplayBrief(_INTL("{1} used {2}!",user.pbThis,move.name))
@battle.pbShowAbilitySplash(b)
@battle.pbDisplay(_INTL("{1} cannot use {2}!",user.pbThis,move.name))
@battle.pbHideAbilitySplash(b)
user.lastMoveFailed = true
pbCancelMoves
pbEndTurn(choice)
return
end
end
# "X used Y!" message
# Can be different for Bide, Fling, Focus Punch and Future Sight
# NOTE: This intentionally passes self rather than user. The user is always
# self except if Snatched, but this message should state the original
# user (self) even if the move is Snatched.
move.pbDisplayUseMessage(self)
# Snatch's message (user is the new user, self is the original user)
if move.snatched
@lastMoveFailed = true # Intentionally applies to self, not user
@battle.pbDisplay(_INTL("{1} snatched {2}'s move!",user.pbThis,pbThis(true)))
end
# "But it failed!" checks
if move.pbMoveFailed?(user,targets)
PBDebug.log(sprintf("[Move failed] In function code %s's def pbMoveFailed?",move.function))
user.lastMoveFailed = true
pbCancelMoves
pbEndTurn(choice)
return
end
# Perform set-up actions and display messages
# Messages include Magnitude's number and Pledge moves' "it's a combo!"
move.pbOnStartUse(user,targets)
# Self-thawing due to the move
if user.status == :FROZEN && move.thawsUser?
user.pbCureStatus(false)
@battle.pbDisplay(_INTL("{1} melted the ice!",user.pbThis))
end
# Powder
if user.effects[PBEffects::Powder] && move.calcType == :FIRE
@battle.pbCommonAnimation("Powder",user)
@battle.pbDisplay(_INTL("When the flame touched the powder on the Pokémon, it exploded!"))
user.lastMoveFailed = true
if ![:Rain, :HeavyRain].include?(@battle.pbWeather) && user.takesIndirectDamage?
oldHP = user.hp
user.pbReduceHP((user.totalhp/4.0).round,false)
user.pbFaint if user.fainted?
@battle.pbGainExp # In case user is KO'd by this
user.pbItemHPHealCheck
if user.pbAbilitiesOnDamageTaken(oldHP)
user.pbEffectsOnSwitchIn(true)
end
end
pbCancelMoves
pbEndTurn(choice)
return
end
# Primordial Sea, Desolate Land
if move.damagingMove?
case @battle.pbWeather
when :HeavyRain
if move.calcType == :FIRE
@battle.pbDisplay(_INTL("The Fire-type attack fizzled out in the heavy rain!"))
user.lastMoveFailed = true
pbCancelMoves
pbEndTurn(choice)
return
end
when :HarshSun
if move.calcType == :WATER
@battle.pbDisplay(_INTL("The Water-type attack evaporated in the harsh sunlight!"))
user.lastMoveFailed = true
pbCancelMoves
pbEndTurn(choice)
return
end
end
end
# Protean
if user.hasActiveAbility?(:PROTEAN) && !move.callsAnotherMove? && !move.snatched
if user.pbHasOtherType?(move.calcType) && !GameData::Type.get(move.calcType).pseudo_type
@battle.pbShowAbilitySplash(user)
user.pbChangeTypes(move.calcType)
typeName = GameData::Type.get(move.calcType).name
@battle.pbDisplay(_INTL("{1} transformed into the {2} type!",user.pbThis,typeName))
@battle.pbHideAbilitySplash(user)
# NOTE: The GF games say that if Curse is used by a non-Ghost-type
# Pokémon which becomes Ghost-type because of Protean, it should
# target and curse itself. I think this is silly, so I'm making it
# choose a random opponent to curse instead.
if move.function=="10D" && targets.length==0 # Curse
choice[3] = -1
targets = pbFindTargets(choice,move,user)
end
end
end
#---------------------------------------------------------------------------
magicCoater = -1
magicBouncer = -1
if targets.length == 0 && move.pbTarget(user).num_targets > 0 && !move.worksWithNoTargets?
# def pbFindTargets should have found a target(s), but it didn't because
# they were all fainted
# All target types except: None, User, UserSide, FoeSide, BothSides
@battle.pbDisplay(_INTL("But there was no target..."))
user.lastMoveFailed = true
else # We have targets, or move doesn't use targets
# Reset whole damage state, perform various success checks (not accuracy)
user.initialHP = user.hp
targets.each do |b|
b.damageState.reset
b.damageState.initialHP = b.hp
if !pbSuccessCheckAgainstTarget(move,user,b)
b.damageState.unaffected = true
end
end
# Magic Coat/Magic Bounce checks (for moves which don't target Pokémon)
if targets.length==0 && move.canMagicCoat?
@battle.pbPriority(true).each do |b|
next if b.fainted? || !b.opposes?(user)
next if b.semiInvulnerable?
if b.effects[PBEffects::MagicCoat]
magicCoater = b.index
b.effects[PBEffects::MagicCoat] = false
break
elsif b.hasActiveAbility?(:MAGICBOUNCE) && !@battle.moldBreaker &&
!b.effects[PBEffects::MagicBounce]
magicBouncer = b.index
b.effects[PBEffects::MagicBounce] = true
break
end
end
end
# Get the number of hits
numHits = move.pbNumHits(user,targets)
# Process each hit in turn
realNumHits = 0
for i in 0...numHits
break if magicCoater>=0 || magicBouncer>=0
success = pbProcessMoveHit(move,user,targets,i,skipAccuracyCheck)
if !success
if i==0 && targets.length>0
hasFailed = false
targets.each do |t|
next if t.damageState.protected
hasFailed = t.damageState.unaffected
break if !t.damageState.unaffected
end
user.lastMoveFailed = hasFailed
end
break
end
realNumHits += 1
break if user.fainted?
break if [:SLEEP, :FROZEN].include?(user.status)
# NOTE: If a multi-hit move becomes disabled partway through doing those
# hits (e.g. by Cursed Body), the rest of the hits continue as
# normal.
break if !targets.any? { |t| !t.fainted? } # All targets are fainted
end
# Battle Arena only - attack is successful
@battle.successStates[user.index].useState = 2
if targets.length>0
@battle.successStates[user.index].typeMod = 0
targets.each do |b|
next if b.damageState.unaffected
@battle.successStates[user.index].typeMod += b.damageState.typeMod
end
end
# Effectiveness message for multi-hit moves
# NOTE: No move is both multi-hit and multi-target, and the messages below
# aren't quite right for such a hypothetical move.
if numHits>1
if move.damagingMove?
targets.each do |b|
next if b.damageState.unaffected || b.damageState.substitute
move.pbEffectivenessMessage(user,b,targets.length)
end
end
if realNumHits==1
@battle.pbDisplay(_INTL("Hit 1 time!"))
elsif realNumHits>1
@battle.pbDisplay(_INTL("Hit {1} times!",realNumHits))
end
end
# Magic Coat's bouncing back (move has targets)
targets.each do |b|
next if b.fainted?
next if !b.damageState.magicCoat && !b.damageState.magicBounce
@battle.pbShowAbilitySplash(b) if b.damageState.magicBounce
@battle.pbDisplay(_INTL("{1} bounced the {2} back!",b.pbThis,move.name))
@battle.pbHideAbilitySplash(b) if b.damageState.magicBounce
newChoice = choice.clone
newChoice[3] = user.index
newTargets = pbFindTargets(newChoice,move,b)
newTargets = pbChangeTargets(move,b,newTargets)
success = pbProcessMoveHit(move,b,newTargets,0,false)
b.lastMoveFailed = true if !success
targets.each { |otherB| otherB.pbFaint if otherB && otherB.fainted? }
user.pbFaint if user.fainted?
end
# Magic Coat's bouncing back (move has no targets)
if magicCoater>=0 || magicBouncer>=0
mc = @battle.battlers[(magicCoater>=0) ? magicCoater : magicBouncer]
if !mc.fainted?
user.lastMoveFailed = true
@battle.pbShowAbilitySplash(mc) if magicBouncer>=0
@battle.pbDisplay(_INTL("{1} bounced the {2} back!",mc.pbThis,move.name))
@battle.pbHideAbilitySplash(mc) if magicBouncer>=0
success = pbProcessMoveHit(move,mc,[],0,false)
mc.lastMoveFailed = true if !success
targets.each { |b| b.pbFaint if b && b.fainted? }
user.pbFaint if user.fainted?
end
end
# Move-specific effects after all hits
targets.each { |b| move.pbEffectAfterAllHits(user,b) }
# Faint if 0 HP
targets.each { |b| b.pbFaint if b && b.fainted? }
user.pbFaint if user.fainted?
# External/general effects after all hits. Eject Button, Shell Bell, etc.
pbEffectsAfterMove(user,targets,move,realNumHits)
end
# End effect of Mold Breaker
@battle.moldBreaker = false
# Gain Exp
@battle.pbGainExp
# Battle Arena only - update skills
@battle.eachBattler { |b| @battle.successStates[b.index].updateSkill }
# Shadow Pokémon triggering Hyper Mode
pbHyperMode if @battle.choices[@index][0]!=:None # Not if self is replaced
# End of move usage
pbEndTurn(choice)
# Instruct
@battle.eachBattler do |b|
next if !b.effects[PBEffects::Instruct] || !b.lastMoveUsed
b.effects[PBEffects::Instruct] = false
idxMove = -1
b.eachMoveWithIndex { |m,i| idxMove = i if m.id==b.lastMoveUsed }
next if idxMove<0
oldLastRoundMoved = b.lastRoundMoved
@battle.pbDisplay(_INTL("{1} used the move instructed by {2}!",b.pbThis,user.pbThis(true)))
PBDebug.logonerr{
b.effects[PBEffects::Instructed] = true
b.pbUseMoveSimple(b.lastMoveUsed,b.lastRegularMoveTarget,idxMove,false)
b.effects[PBEffects::Instructed] = false
}
b.lastRoundMoved = oldLastRoundMoved
@battle.pbJudge
return if @battle.decision>0
end
# Dancer
if !@effects[PBEffects::Dancer] && !user.lastMoveFailed && realNumHits>0 &&
!move.snatched && magicCoater<0 && @battle.pbCheckGlobalAbility(:DANCER) &&
move.danceMove?
dancers = []
@battle.pbPriority(true).each do |b|
dancers.push(b) if b.index!=user.index && b.hasActiveAbility?(:DANCER)
end
while dancers.length>0
nextUser = dancers.pop
oldLastRoundMoved = nextUser.lastRoundMoved
# NOTE: Petal Dance being used because of Dancer shouldn't lock the
# Dancer into using that move, and shouldn't contribute to its
# turn counter if it's already locked into Petal Dance.
oldOutrage = nextUser.effects[PBEffects::Outrage]
nextUser.effects[PBEffects::Outrage] += 1 if nextUser.effects[PBEffects::Outrage]>0
oldCurrentMove = nextUser.currentMove
preTarget = choice[3]
preTarget = user.index if nextUser.opposes?(user) || !nextUser.opposes?(preTarget)
@battle.pbShowAbilitySplash(nextUser,true)
@battle.pbHideAbilitySplash(nextUser)
if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH
@battle.pbDisplay(_INTL("{1} kept the dance going with {2}!",
nextUser.pbThis,nextUser.abilityName))
end
PBDebug.logonerr{
nextUser.effects[PBEffects::Dancer] = true
nextUser.pbUseMoveSimple(move.id,preTarget)
nextUser.effects[PBEffects::Dancer] = false
}
nextUser.lastRoundMoved = oldLastRoundMoved
nextUser.effects[PBEffects::Outrage] = oldOutrage
nextUser.currentMove = oldCurrentMove
@battle.pbJudge
return if @battle.decision>0
end
end
end
#=============================================================================
# Attack a single target
#=============================================================================
def pbProcessMoveHit(move,user,targets,hitNum,skipAccuracyCheck)
return false if user.fainted?
# For two-turn attacks being used in a single turn
move.pbInitialEffect(user,targets,hitNum)
numTargets = 0 # Number of targets that are affected by this hit
targets.each { |b| b.damageState.resetPerHit }
# Count a hit for Parental Bond (if it applies)
user.effects[PBEffects::ParentalBond] -= 1 if user.effects[PBEffects::ParentalBond]>0
# Accuracy check (accuracy/evasion calc)
if hitNum==0 || move.successCheckPerHit?
targets.each do |b|
next if b.damageState.unaffected
if pbSuccessCheckPerHit(move,user,b,skipAccuracyCheck)
numTargets += 1
else
b.damageState.missed = true
b.damageState.unaffected = true
end
end
# If failed against all targets
if targets.length>0 && numTargets==0 && !move.worksWithNoTargets?
targets.each do |b|
next if !b.damageState.missed || b.damageState.magicCoat
pbMissMessage(move,user,b)
end
move.pbCrashDamage(user)
user.pbItemHPHealCheck
pbCancelMoves
return false
end
end
# If we get here, this hit will happen and do something
#---------------------------------------------------------------------------
# Calculate damage to deal
if move.pbDamagingMove?
targets.each do |b|
next if b.damageState.unaffected
# Check whether Substitute/Disguise will absorb the damage
move.pbCheckDamageAbsorption(user,b)
# Calculate the damage against b
# pbCalcDamage shows the "eat berry" animation for SE-weakening
# berries, although the message about it comes after the additional
# effect below
move.pbCalcDamage(user,b,targets.length) # Stored in damageState.calcDamage
# Lessen damage dealt because of False Swipe/Endure/etc.
move.pbReduceDamage(user,b) # Stored in damageState.hpLost
end
end
# Show move animation (for this hit)
move.pbShowAnimation(move.id,user,targets,hitNum)
# Type-boosting Gem consume animation/message
if user.effects[PBEffects::GemConsumed] && hitNum==0
# NOTE: The consume animation and message for Gems are shown now, but the
# actual removal of the item happens in def pbEffectsAfterMove.
@battle.pbCommonAnimation("UseItem",user)
@battle.pbDisplay(_INTL("The {1} strengthened {2}'s power!",
GameData::Item.get(user.effects[PBEffects::GemConsumed]).name,move.name))
end
# Messages about missed target(s) (relevant for multi-target moves only)
targets.each do |b|
next if !b.damageState.missed
pbMissMessage(move,user,b)
end
# Deal the damage (to all allies first simultaneously, then all foes
# simultaneously)
if move.pbDamagingMove?
# This just changes the HP amounts and does nothing else
targets.each do |b|
next if b.damageState.unaffected
move.pbInflictHPDamage(b)
end
# Animate the hit flashing and HP bar changes
move.pbAnimateHitAndHPLost(user,targets)
end
# Self-Destruct/Explosion's damaging and fainting of user
move.pbSelfKO(user) if hitNum==0
user.pbFaint if user.fainted?
if move.pbDamagingMove?
targets.each do |b|
next if b.damageState.unaffected
# NOTE: This method is also used for the OKHO special message.
move.pbHitEffectivenessMessages(user,b,targets.length)
# Record data about the hit for various effects' purposes
move.pbRecordDamageLost(user,b)
end
# Close Combat/Superpower's stat-lowering, Flame Burst's splash damage,
# and Incinerate's berry destruction
targets.each do |b|
next if b.damageState.unaffected
move.pbEffectWhenDealingDamage(user,b)
end
# Ability/item effects such as Static/Rocky Helmet, and Grudge, etc.
targets.each do |b|
next if b.damageState.unaffected
pbEffectsOnMakingHit(move,user,b)
end
# Disguise/Endure/Sturdy/Focus Sash/Focus Band messages
targets.each do |b|
next if b.damageState.unaffected
move.pbEndureKOMessage(b)
end
# HP-healing held items (checks all battlers rather than just targets
# because Flame Burst's splash damage affects non-targets)
@battle.pbPriority(true).each { |b| b.pbItemHPHealCheck }
# Animate battlers fainting (checks all battlers rather than just targets
# because Flame Burst's splash damage affects non-targets)
@battle.pbPriority(true).each { |b| b.pbFaint if b && b.fainted? }
end
@battle.pbJudgeCheckpoint(user,move)
# Main effect (recoil/drain, etc.)
targets.each do |b|
next if b.damageState.unaffected
move.pbEffectAgainstTarget(user,b)
end
move.pbEffectGeneral(user)
targets.each { |b| b.pbFaint if b && b.fainted? }
user.pbFaint if user.fainted?
# Additional effect
if !user.hasActiveAbility?(:SHEERFORCE)
targets.each do |b|
next if b.damageState.calcDamage==0
chance = move.pbAdditionalEffectChance(user,b)
next if chance<=0
if @battle.pbRandom(100)<chance
move.pbAdditionalEffect(user,b)
end
end
end
# Make the target flinch (because of an item/ability)
targets.each do |b|
next if b.fainted?
next if b.damageState.calcDamage==0 || b.damageState.substitute
chance = move.pbFlinchChance(user,b)
next if chance<=0
if @battle.pbRandom(100)<chance
PBDebug.log("[Item/ability triggered] #{user.pbThis}'s King's Rock/Razor Fang or Stench")
b.pbFlinch(user)
end
end
# Message for and consuming of type-weakening berries
# NOTE: The "consume held item" animation for type-weakening berries occurs
# during pbCalcDamage above (before the move's animation), but the
# message about it only shows here.
targets.each do |b|
next if b.damageState.unaffected
next if !b.damageState.berryWeakened
@battle.pbDisplay(_INTL("The {1} weakened the damage to {2}!",b.itemName,b.pbThis(true)))
b.pbConsumeItem
end
targets.each { |b| b.pbFaint if b && b.fainted? }
user.pbFaint if user.fainted?
return true
end
end

View File

@@ -0,0 +1,193 @@
class PokeBattle_Battler
#=============================================================================
# Get move's user
#=============================================================================
def pbFindUser(_choice,_move)
return self
end
def pbChangeUser(choice,move,user)
# Snatch
move.snatched = false
if move.canSnatch?
newUser = nil; strength = 100
@battle.eachBattler do |b|
next if b.effects[PBEffects::Snatch]==0 ||
b.effects[PBEffects::Snatch]>=strength
next if b.effects[PBEffects::SkyDrop]>=0
newUser = b
strength = b.effects[PBEffects::Snatch]
end
if newUser
user = newUser
user.effects[PBEffects::Snatch] = 0
move.snatched = true
@battle.moldBreaker = user.hasMoldBreaker?
choice[3] = -1 # Clear pre-chosen target
end
end
return user
end
#=============================================================================
# Get move's default target(s)
#=============================================================================
def pbFindTargets(choice,move,user)
preTarget = choice[3] # A target that was already chosen
targets = []
# Get list of targets
case move.pbTarget(user).id # Curse can change its target type
when :NearAlly
targetBattler = (preTarget>=0) ? @battle.battlers[preTarget] : nil
if !pbAddTarget(targets,user,targetBattler,move)
pbAddTargetRandomAlly(targets,user,move)
end
when :UserOrNearAlly
targetBattler = (preTarget>=0) ? @battle.battlers[preTarget] : nil
if !pbAddTarget(targets,user,targetBattler,move,true,true)
pbAddTarget(targets,user,user,move,true,true)
end
when :UserAndAllies
pbAddTarget(targets,user,user,move,true,true)
@battle.eachSameSideBattler(user.index) { |b| pbAddTarget(targets,user,b,move,false,true) }
when :NearFoe, :NearOther
targetBattler = (preTarget>=0) ? @battle.battlers[preTarget] : nil
if !pbAddTarget(targets,user,targetBattler,move)
if preTarget>=0 && !user.opposes?(preTarget)
pbAddTargetRandomAlly(targets,user,move)
else
pbAddTargetRandomFoe(targets,user,move)
end
end
when :RandomNearFoe
pbAddTargetRandomFoe(targets,user,move)
when :AllNearFoes
@battle.eachOtherSideBattler(user.index) { |b| pbAddTarget(targets,user,b,move) }
when :Foe, :Other
targetBattler = (preTarget>=0) ? @battle.battlers[preTarget] : nil
if !pbAddTarget(targets,user,targetBattler,move,false)
if preTarget>=0 && !user.opposes?(preTarget)
pbAddTargetRandomAlly(targets,user,move,false)
else
pbAddTargetRandomFoe(targets,user,move,false)
end
end
when :AllFoes
@battle.eachOtherSideBattler(user.index) { |b| pbAddTarget(targets,user,b,move,false) }
when :AllNearOthers
@battle.eachBattler { |b| pbAddTarget(targets,user,b,move) }
when :AllBattlers
@battle.eachBattler { |b| pbAddTarget(targets,user,b,move,false,true) }
else
# Used by Counter/Mirror Coat/Metal Burst/Bide
move.pbAddTarget(targets,user) # Move-specific pbAddTarget, not the def below
end
return targets
end
#=============================================================================
# Redirect attack to another target
#=============================================================================
def pbChangeTargets(move,user,targets)
target_data = move.pbTarget(user)
return targets if @battle.switching # For Pursuit interrupting a switch
return targets if move.cannotRedirect?
return targets if !target_data.can_target_one_foe? || targets.length != 1
priority = @battle.pbPriority(true)
nearOnly = !target_data.can_choose_distant_target?
# Spotlight (takes priority over Follow Me/Rage Powder/Lightning Rod/Storm Drain)
newTarget = nil; strength = 100 # Lower strength takes priority
priority.each do |b|
next if b.fainted? || b.effects[PBEffects::SkyDrop]>=0
next if b.effects[PBEffects::Spotlight]==0 ||
b.effects[PBEffects::Spotlight]>=strength
next if !b.opposes?(user)
next if nearOnly && !b.near?(user)
newTarget = b
strength = b.effects[PBEffects::Spotlight]
end
if newTarget
PBDebug.log("[Move target changed] #{newTarget.pbThis}'s Spotlight made it the target")
targets = []
pbAddTarget(targets,user,newTarget,move,nearOnly)
return targets
end
# Follow Me/Rage Powder (takes priority over Lightning Rod/Storm Drain)
newTarget = nil; strength = 100 # Lower strength takes priority
priority.each do |b|
next if b.fainted? || b.effects[PBEffects::SkyDrop]>=0
next if b.effects[PBEffects::RagePowder] && !user.affectedByPowder?
next if b.effects[PBEffects::FollowMe]==0 ||
b.effects[PBEffects::FollowMe]>=strength
next if !b.opposes?(user)
next if nearOnly && !b.near?(user)
newTarget = b
strength = b.effects[PBEffects::FollowMe]
end
if newTarget
PBDebug.log("[Move target changed] #{newTarget.pbThis}'s Follow Me/Rage Powder made it the target")
targets = []
pbAddTarget(targets,user,newTarget,move,nearOnly)
return targets
end
# Lightning Rod
targets = pbChangeTargetByAbility(:LIGHTNINGROD,:ELECTRIC,move,user,targets,priority,nearOnly)
# Storm Drain
targets = pbChangeTargetByAbility(:STORMDRAIN,:WATER,move,user,targets,priority,nearOnly)
return targets
end
def pbChangeTargetByAbility(drawingAbility,drawnType,move,user,targets,priority,nearOnly)
return targets if move.calcType != drawnType
return targets if targets[0].hasActiveAbility?(drawingAbility)
priority.each do |b|
next if b.index==user.index || b.index==targets[0].index
next if !b.hasActiveAbility?(drawingAbility)
next if nearOnly && !b.near?(user)
@battle.pbShowAbilitySplash(b)
targets.clear
pbAddTarget(targets,user,b,move,nearOnly)
if PokeBattle_SceneConstants::USE_ABILITY_SPLASH
@battle.pbDisplay(_INTL("{1} took the attack!",b.pbThis))
else
@battle.pbDisplay(_INTL("{1} took the attack with its {2}!",b.pbThis,b.abilityName))
end
@battle.pbHideAbilitySplash(b)
break
end
return targets
end
#=============================================================================
# Register target
#=============================================================================
def pbAddTarget(targets,user,target,move,nearOnly=true,allowUser=false)
return false if !target || (target.fainted? && !move.cannotRedirect?)
return false if !(allowUser && user==target) && nearOnly && !user.near?(target)
targets.each { |b| return true if b.index==target.index } # Already added
targets.push(target)
return true
end
def pbAddTargetRandomAlly(targets,user,_move,nearOnly=true)
choices = []
user.eachAlly do |b|
next if nearOnly && !user.near?(b)
pbAddTarget(choices,user,b,nearOnly)
end
if choices.length>0
pbAddTarget(targets,user,choices[@battle.pbRandom(choices.length)],nearOnly)
end
end
def pbAddTargetRandomFoe(targets,user,_move,nearOnly=true)
choices = []
user.eachOpposing do |b|
next if nearOnly && !user.near?(b)
pbAddTarget(choices,user,b,nearOnly)
end
if choices.length>0
pbAddTarget(targets,user,choices[@battle.pbRandom(choices.length)],nearOnly)
end
end
end

View File

@@ -0,0 +1,538 @@
class PokeBattle_Battler
#=============================================================================
# Decide whether the trainer is allowed to tell the Pokémon to use the given
# move. Called when choosing a command for the round.
# Also called when processing the Pokémon's action, because these effects also
# prevent Pokémon action. Relevant because these effects can become active
# earlier in the same round (after choosing the command but before using the
# move) or an unusable move may be called by another move such as Metronome.
#=============================================================================
def pbCanChooseMove?(move,commandPhase,showMessages=true,specialUsage=false)
# Disable
if @effects[PBEffects::DisableMove]==move.id && !specialUsage
if showMessages
msg = _INTL("{1}'s {2} is disabled!",pbThis,move.name)
(commandPhase) ? @battle.pbDisplayPaused(msg) : @battle.pbDisplay(msg)
end
return false
end
# Heal Block
if @effects[PBEffects::HealBlock]>0 && move.healingMove?
if showMessages
msg = _INTL("{1} can't use {2} because of Heal Block!",pbThis,move.name)
(commandPhase) ? @battle.pbDisplayPaused(msg) : @battle.pbDisplay(msg)
end
return false
end
# Gravity
if @battle.field.effects[PBEffects::Gravity]>0 && move.unusableInGravity?
if showMessages
msg = _INTL("{1} can't use {2} because of gravity!",pbThis,move.name)
(commandPhase) ? @battle.pbDisplayPaused(msg) : @battle.pbDisplay(msg)
end
return false
end
# Throat Chop
if @effects[PBEffects::ThroatChop]>0 && move.soundMove?
if showMessages
msg = _INTL("{1} can't use {2} because of Throat Chop!",pbThis,move.name)
(commandPhase) ? @battle.pbDisplayPaused(msg) : @battle.pbDisplay(msg)
end
return false
end
# Choice Band
if @effects[PBEffects::ChoiceBand]
if hasActiveItem?([:CHOICEBAND,:CHOICESPECS,:CHOICESCARF]) &&
pbHasMove?(@effects[PBEffects::ChoiceBand])
if move.id!=@effects[PBEffects::ChoiceBand]
if showMessages
msg = _INTL("{1} allows the use of only {2}!",itemName,
GameData::Move.get(@effects[PBEffects::ChoiceBand]).name)
(commandPhase) ? @battle.pbDisplayPaused(msg) : @battle.pbDisplay(msg)
end
return false
end
else
@effects[PBEffects::ChoiceBand] = nil
end
end
# Taunt
if @effects[PBEffects::Taunt]>0 && move.statusMove?
if showMessages
msg = _INTL("{1} can't use {2} after the taunt!",pbThis,move.name)
(commandPhase) ? @battle.pbDisplayPaused(msg) : @battle.pbDisplay(msg)
end
return false
end
# Torment
if @effects[PBEffects::Torment] && !@effects[PBEffects::Instructed] &&
@lastMoveUsed && move.id==@lastMoveUsed && move.id!=@battle.struggle.id
if showMessages
msg = _INTL("{1} can't use the same move twice in a row due to the torment!",pbThis)
(commandPhase) ? @battle.pbDisplayPaused(msg) : @battle.pbDisplay(msg)
end
return false
end
# Imprison
@battle.eachOtherSideBattler(@index) do |b|
next if !b.effects[PBEffects::Imprison] || !b.pbHasMove?(move.id)
if showMessages
msg = _INTL("{1} can't use its sealed {2}!",pbThis,move.name)
(commandPhase) ? @battle.pbDisplayPaused(msg) : @battle.pbDisplay(msg)
end
return false
end
# Assault Vest (prevents choosing status moves but doesn't prevent
# executing them)
if hasActiveItem?(:ASSAULTVEST) && move.statusMove? && commandPhase
if showMessages
msg = _INTL("The effects of the {1} prevent status moves from being used!",
itemName)
(commandPhase) ? @battle.pbDisplayPaused(msg) : @battle.pbDisplay(msg)
end
return false
end
# Belch
return false if !move.pbCanChooseMove?(self,commandPhase,showMessages)
return true
end
#=============================================================================
# Obedience check
#=============================================================================
# Return true if Pokémon continues attacking (although it may have chosen to
# use a different move in disobedience), or false if attack stops.
def pbObedienceCheck?(choice)
return true if usingMultiTurnAttack?
return true if choice[0]!=:UseMove
return true if !@battle.internalBattle
return true if !@battle.pbOwnedByPlayer?(@index)
disobedient = false
# Pokémon may be disobedient; calculate if it is
badgeLevel = 10 * (@battle.pbPlayer.badge_count + 1)
badgeLevel = GameData::GrowthRate.max_level if @battle.pbPlayer.badge_count >= 8
if @pokemon.foreign?(@battle.pbPlayer) && @level>badgeLevel
a = ((@level+badgeLevel)*@battle.pbRandom(256)/256).floor
disobedient |= (a>=badgeLevel)
end
disobedient |= !pbHyperModeObedience(choice[2])
return true if !disobedient
# Pokémon is disobedient; make it do something else
return pbDisobey(choice,badgeLevel)
end
def pbDisobey(choice,badgeLevel)
move = choice[2]
PBDebug.log("[Disobedience] #{pbThis} disobeyed")
@effects[PBEffects::Rage] = false
# Do nothing if using Snore/Sleep Talk
if @status == :SLEEP && move.usableWhenAsleep?
@battle.pbDisplay(_INTL("{1} ignored orders and kept sleeping!",pbThis))
return false
end
b = ((@level+badgeLevel)*@battle.pbRandom(256)/256).floor
# Use another move
if b<badgeLevel
@battle.pbDisplay(_INTL("{1} ignored orders!",pbThis))
return false if !@battle.pbCanShowFightMenu?(@index)
otherMoves = []
eachMoveWithIndex do |_m,i|
next if i==choice[1]
otherMoves.push(i) if @battle.pbCanChooseMove?(@index,i,false)
end
return false if otherMoves.length==0 # No other move to use; do nothing
newChoice = otherMoves[@battle.pbRandom(otherMoves.length)]
choice[1] = newChoice
choice[2] = @moves[newChoice]
choice[3] = -1
return true
end
c = @level-badgeLevel
r = @battle.pbRandom(256)
# Fall asleep
if r<c && pbCanSleep?(self,false)
pbSleepSelf(_INTL("{1} began to nap!",pbThis))
return false
end
# Hurt self in confusion
r -= c
if r < c && @status != :SLEEP
pbConfusionDamage(_INTL("{1} won't obey! It hurt itself in its confusion!",pbThis))
return false
end
# Show refusal message and do nothing
case @battle.pbRandom(4)
when 0 then @battle.pbDisplay(_INTL("{1} won't obey!",pbThis))
when 1 then @battle.pbDisplay(_INTL("{1} turned away!",pbThis))
when 2 then @battle.pbDisplay(_INTL("{1} is loafing around!",pbThis))
when 3 then @battle.pbDisplay(_INTL("{1} pretended not to notice!",pbThis))
end
return false
end
#=============================================================================
# Check whether the user (self) is able to take action at all.
# If this returns true, and if PP isn't a problem, the move will be considered
# to have been used (even if it then fails for whatever reason).
#=============================================================================
def pbTryUseMove(choice,move,specialUsage,skipAccuracyCheck)
# Check whether it's possible for self to use the given move
# NOTE: Encore has already changed the move being used, no need to have a
# check for it here.
if !pbCanChooseMove?(move,false,true,specialUsage)
@lastMoveFailed = true
return false
end
# Check whether it's possible for self to do anything at all
if @effects[PBEffects::SkyDrop]>=0 # Intentionally no message here
PBDebug.log("[Move failed] #{pbThis} can't use #{move.name} because of being Sky Dropped")
return false
end
if @effects[PBEffects::HyperBeam]>0 # Intentionally before Truant
@battle.pbDisplay(_INTL("{1} must recharge!",pbThis))
return false
end
if choice[1]==-2 # Battle Palace
@battle.pbDisplay(_INTL("{1} appears incapable of using its power!",pbThis))
return false
end
# Skip checking all applied effects that could make self fail doing something
return true if skipAccuracyCheck
# Check status problems and continue their effects/cure them
case @status
when :SLEEP
self.statusCount -= 1
if @statusCount<=0
pbCureStatus
else
pbContinueStatus
if !move.usableWhenAsleep? # Snore/Sleep Talk
@lastMoveFailed = true
return false
end
end
when :FROZEN
if !move.thawsUser?
if @battle.pbRandom(100)<20
pbCureStatus
else
pbContinueStatus
@lastMoveFailed = true
return false
end
end
end
# Obedience check
return false if !pbObedienceCheck?(choice)
# Truant
if hasActiveAbility?(:TRUANT)
@effects[PBEffects::Truant] = !@effects[PBEffects::Truant]
if !@effects[PBEffects::Truant] # True means loafing, but was just inverted
@battle.pbShowAbilitySplash(self)
@battle.pbDisplay(_INTL("{1} is loafing around!",pbThis))
@lastMoveFailed = true
@battle.pbHideAbilitySplash(self)
return false
end
end
# Flinching
if @effects[PBEffects::Flinch]
@battle.pbDisplay(_INTL("{1} flinched and couldn't move!",pbThis))
if abilityActive?
BattleHandlers.triggerAbilityOnFlinch(self.ability,self,@battle)
end
@lastMoveFailed = true
return false
end
# Confusion
if @effects[PBEffects::Confusion]>0
@effects[PBEffects::Confusion] -= 1
if @effects[PBEffects::Confusion]<=0
pbCureConfusion
@battle.pbDisplay(_INTL("{1} snapped out of its confusion.",pbThis))
else
@battle.pbCommonAnimation("Confusion",self)
@battle.pbDisplay(_INTL("{1} is confused!",pbThis))
threshold = (Settings::MECHANICS_GENERATION >= 7) ? 33 : 50 # % chance
if @battle.pbRandom(100)<threshold
pbConfusionDamage(_INTL("It hurt itself in its confusion!"))
@lastMoveFailed = true
return false
end
end
end
# Paralysis
if @status == :PARALYSIS
if @battle.pbRandom(100)<25
pbContinueStatus
@lastMoveFailed = true
return false
end
end
# Infatuation
if @effects[PBEffects::Attract]>=0
@battle.pbCommonAnimation("Attract",self)
@battle.pbDisplay(_INTL("{1} is in love with {2}!",pbThis,
@battle.battlers[@effects[PBEffects::Attract]].pbThis(true)))
if @battle.pbRandom(100)<50
@battle.pbDisplay(_INTL("{1} is immobilized by love!",pbThis))
@lastMoveFailed = true
return false
end
end
return true
end
#=============================================================================
# Initial success check against the target. Done once before the first hit.
# Includes move-specific failure conditions, protections and type immunities.
#=============================================================================
def pbSuccessCheckAgainstTarget(move,user,target)
typeMod = move.pbCalcTypeMod(move.calcType,user,target)
target.damageState.typeMod = typeMod
# 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)
# 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
@battle.pbDisplay(_INTL("{1} surrounds itself with psychic terrain!",target.pbThis))
return false
end
# Crafty Shield
if target.pbOwnSide.effects[PBEffects::CraftyShield] && user.index!=target.index &&
move.statusMove? && !move.pbTarget(user).targets_all
@battle.pbCommonAnimation("CraftyShield",target)
@battle.pbDisplay(_INTL("Crafty Shield protected {1}!",target.pbThis(true)))
target.damageState.protected = true
@battle.successStates[user.index].protected = true
return false
end
# Wide Guard
if target.pbOwnSide.effects[PBEffects::WideGuard] && user.index!=target.index &&
move.pbTarget(user).num_targets > 1 &&
(Settings::MECHANICS_GENERATION >= 7 || move.damagingMove?)
@battle.pbCommonAnimation("WideGuard",target)
@battle.pbDisplay(_INTL("Wide Guard protected {1}!",target.pbThis(true)))
target.damageState.protected = true
@battle.successStates[user.index].protected = true
return false
end
if move.canProtectAgainst?
# Quick Guard
if target.pbOwnSide.effects[PBEffects::QuickGuard] &&
@battle.choices[user.index][4]>0 # Move priority saved from pbCalculatePriority
@battle.pbCommonAnimation("QuickGuard",target)
@battle.pbDisplay(_INTL("Quick Guard protected {1}!",target.pbThis(true)))
target.damageState.protected = true
@battle.successStates[user.index].protected = true
return false
end
# Protect
if target.effects[PBEffects::Protect]
@battle.pbCommonAnimation("Protect",target)
@battle.pbDisplay(_INTL("{1} protected itself!",target.pbThis))
target.damageState.protected = true
@battle.successStates[user.index].protected = true
return false
end
# King's Shield
if target.effects[PBEffects::KingsShield] && move.damagingMove?
@battle.pbCommonAnimation("KingsShield",target)
@battle.pbDisplay(_INTL("{1} protected itself!",target.pbThis))
target.damageState.protected = true
@battle.successStates[user.index].protected = true
if move.pbContactMove?(user) && user.affectedByContactEffect?
if user.pbCanLowerStatStage?(:ATTACK)
user.pbLowerStatStage(:ATTACK,2,nil)
end
end
return false
end
# Spiky Shield
if target.effects[PBEffects::SpikyShield]
@battle.pbCommonAnimation("SpikyShield",target)
@battle.pbDisplay(_INTL("{1} protected itself!",target.pbThis))
target.damageState.protected = true
@battle.successStates[user.index].protected = true
if move.pbContactMove?(user) && user.affectedByContactEffect?
@battle.scene.pbDamageAnimation(user)
user.pbReduceHP(user.totalhp/8,false)
@battle.pbDisplay(_INTL("{1} was hurt!",user.pbThis))
user.pbItemHPHealCheck
end
return false
end
# Baneful Bunker
if target.effects[PBEffects::BanefulBunker]
@battle.pbCommonAnimation("BanefulBunker",target)
@battle.pbDisplay(_INTL("{1} protected itself!",target.pbThis))
target.damageState.protected = true
@battle.successStates[user.index].protected = true
if move.pbContactMove?(user) && user.affectedByContactEffect?
user.pbPoison(target) if user.pbCanPoison?(target,false)
end
return false
end
# Mat Block
if target.pbOwnSide.effects[PBEffects::MatBlock] && move.damagingMove?
# NOTE: Confirmed no common animation for this effect.
@battle.pbDisplay(_INTL("{1} was blocked by the kicked-up mat!",move.name))
target.damageState.protected = true
@battle.successStates[user.index].protected = true
return false
end
end
# Magic Coat/Magic Bounce
if move.canMagicCoat? && !target.semiInvulnerable? && target.opposes?(user)
if target.effects[PBEffects::MagicCoat]
target.damageState.magicCoat = true
target.effects[PBEffects::MagicCoat] = false
return false
end
if target.hasActiveAbility?(:MAGICBOUNCE) && !@battle.moldBreaker &&
!target.effects[PBEffects::MagicBounce]
target.damageState.magicBounce = true
target.effects[PBEffects::MagicBounce] = true
return false
end
end
# Immunity because of ability (intentionally before type immunity check)
return false if move.pbImmunityByAbility(user,target)
# Type immunity
if move.pbDamagingMove? && Effectiveness.ineffective?(typeMod)
PBDebug.log("[Target immune] #{target.pbThis}'s type immunity")
@battle.pbDisplay(_INTL("It doesn't affect {1}...",target.pbThis(true)))
return false
end
# Dark-type immunity to moves made faster by Prankster
if Settings::MECHANICS_GENERATION >= 7 && user.effects[PBEffects::Prankster] &&
target.pbHasType?(:DARK) && target.opposes?(user)
PBDebug.log("[Target immune] #{target.pbThis} is Dark-type and immune to Prankster-boosted moves")
@battle.pbDisplay(_INTL("It doesn't affect {1}...",target.pbThis(true)))
return false
end
# Airborne-based immunity to Ground moves
if move.damagingMove? && move.calcType == :GROUND &&
target.airborne? && !move.hitsFlyingTargets?
if target.hasActiveAbility?(:LEVITATE) && !@battle.moldBreaker
@battle.pbShowAbilitySplash(target)
if PokeBattle_SceneConstants::USE_ABILITY_SPLASH
@battle.pbDisplay(_INTL("{1} avoided the attack!",target.pbThis))
else
@battle.pbDisplay(_INTL("{1} avoided the attack with {2}!",target.pbThis,target.abilityName))
end
@battle.pbHideAbilitySplash(target)
return false
end
if target.hasActiveItem?(:AIRBALLOON)
@battle.pbDisplay(_INTL("{1}'s {2} makes Ground moves miss!",target.pbThis,target.itemName))
return false
end
if target.effects[PBEffects::MagnetRise]>0
@battle.pbDisplay(_INTL("{1} makes Ground moves miss with Magnet Rise!",target.pbThis))
return false
end
if target.effects[PBEffects::Telekinesis]>0
@battle.pbDisplay(_INTL("{1} makes Ground moves miss with Telekinesis!",target.pbThis))
return false
end
end
# Immunity to powder-based moves
if move.powderMove?
if target.pbHasType?(:GRASS) && Settings::MORE_TYPE_EFFECTS
PBDebug.log("[Target immune] #{target.pbThis} is Grass-type and immune to powder-based moves")
@battle.pbDisplay(_INTL("It doesn't affect {1}...",target.pbThis(true)))
return false
end
if Settings::MECHANICS_GENERATION >= 6
if target.hasActiveAbility?(:OVERCOAT) && !@battle.moldBreaker
@battle.pbShowAbilitySplash(target)
if PokeBattle_SceneConstants::USE_ABILITY_SPLASH
@battle.pbDisplay(_INTL("It doesn't affect {1}...",target.pbThis(true)))
else
@battle.pbDisplay(_INTL("It doesn't affect {1} because of its {2}.",target.pbThis(true),target.abilityName))
end
@battle.pbHideAbilitySplash(target)
return false
end
if target.hasActiveItem?(:SAFETYGOGGLES)
PBDebug.log("[Item triggered] #{target.pbThis} has Safety Goggles and is immune to powder-based moves")
@battle.pbDisplay(_INTL("It doesn't affect {1}...",target.pbThis(true)))
return false
end
end
end
# Substitute
if target.effects[PBEffects::Substitute]>0 && move.statusMove? &&
!move.ignoresSubstitute?(user) && user.index!=target.index
PBDebug.log("[Target immune] #{target.pbThis} is protected by its Substitute")
@battle.pbDisplay(_INTL("{1} avoided the attack!",target.pbThis(true)))
return false
end
return true
end
#=============================================================================
# Per-hit success check against the target.
# Includes semi-invulnerable move use and accuracy calculation.
#=============================================================================
def pbSuccessCheckPerHit(move,user,target,skipAccuracyCheck)
# Two-turn attacks can't fail here in the charging turn
return true if user.effects[PBEffects::TwoTurnAttack]
# Lock-On
return true if user.effects[PBEffects::LockOn]>0 &&
user.effects[PBEffects::LockOnPos]==target.index
# Toxic
return true if move.pbOverrideSuccessCheckPerHit(user,target)
miss = false; hitsInvul = false
# No Guard
hitsInvul = true if user.hasActiveAbility?(:NOGUARD) ||
target.hasActiveAbility?(:NOGUARD)
# Future Sight
hitsInvul = true if @battle.futureSight
# Helping Hand
hitsInvul = true if move.function=="09C"
if !hitsInvul
# Semi-invulnerable moves
if target.effects[PBEffects::TwoTurnAttack]
if target.inTwoTurnAttack?("0C9","0CC","0CE") # Fly, Bounce, Sky Drop
miss = true if !move.hitsFlyingTargets?
elsif target.inTwoTurnAttack?("0CA") # Dig
miss = true if !move.hitsDiggingTargets?
elsif target.inTwoTurnAttack?("0CB") # Dive
miss = true if !move.hitsDivingTargets?
elsif target.inTwoTurnAttack?("0CD","14D") # Shadow Force, Phantom Force
miss = true
end
end
if target.effects[PBEffects::SkyDrop]>=0 &&
target.effects[PBEffects::SkyDrop]!=user.index
miss = true if !move.hitsFlyingTargets?
end
end
if !miss
# Called by another move
return true if skipAccuracyCheck
# Accuracy check
return true if move.pbAccuracyCheck(user,target) # Includes Counter/Mirror Coat
end
# Missed
PBDebug.log("[Move failed] Failed pbAccuracyCheck or target is semi-invulnerable")
return false
end
#=============================================================================
# Message shown when a move fails the per-hit success check above.
#=============================================================================
def pbMissMessage(move,user,target)
if move.pbTarget(user).num_targets > 1
@battle.pbDisplay(_INTL("{1} avoided the attack!",target.pbThis))
elsif target.effects[PBEffects::TwoTurnAttack]
@battle.pbDisplay(_INTL("{1} avoided the attack!",target.pbThis))
elsif !move.pbMissMessage(user,target)
@battle.pbDisplay(_INTL("{1}'s attack missed!",user.pbThis))
end
end
end

View File

@@ -0,0 +1,186 @@
class PokeBattle_Battler
#=============================================================================
# Effect per hit
#=============================================================================
def pbEffectsOnMakingHit(move,user,target)
if target.damageState.calcDamage>0 && !target.damageState.substitute
# Target's ability
if target.abilityActive?(true)
oldHP = user.hp
BattleHandlers.triggerTargetAbilityOnHit(target.ability,user,target,move,@battle)
user.pbItemHPHealCheck if user.hp<oldHP
end
# User's ability
if user.abilityActive?(true)
BattleHandlers.triggerUserAbilityOnHit(user.ability,user,target,move,@battle)
user.pbItemHPHealCheck
end
# Target's item
if target.itemActive?(true)
oldHP = user.hp
BattleHandlers.triggerTargetItemOnHit(target.item,user,target,move,@battle)
user.pbItemHPHealCheck if user.hp<oldHP
end
end
if target.opposes?(user)
# Rage
if target.effects[PBEffects::Rage] && !target.fainted?
if target.pbCanRaiseStatStage?(:ATTACK,target)
@battle.pbDisplay(_INTL("{1}'s rage is building!",target.pbThis))
target.pbRaiseStatStage(:ATTACK,1,target)
end
end
# Beak Blast
if target.effects[PBEffects::BeakBlast]
PBDebug.log("[Lingering effect] #{target.pbThis}'s Beak Blast")
if move.pbContactMove?(user) && user.affectedByContactEffect?
target.pbBurn(user) if target.pbCanBurn?(user,false,self)
end
end
# Shell Trap (make the trapper move next if the trap was triggered)
if target.effects[PBEffects::ShellTrap] &&
@battle.choices[target.index][0]==:UseMove && !target.movedThisRound?
if target.damageState.hpLost>0 && !target.damageState.substitute && move.physicalMove?
target.tookPhysicalHit = true
target.effects[PBEffects::MoveNext] = true
target.effects[PBEffects::Quash] = 0
end
end
# Grudge
if target.effects[PBEffects::Grudge] && target.fainted?
move.pp = 0
@battle.pbDisplay(_INTL("{1}'s {2} lost all of its PP due to the grudge!",
user.pbThis,move.name))
end
# Destiny Bond (recording that it should apply)
if target.effects[PBEffects::DestinyBond] && target.fainted?
if user.effects[PBEffects::DestinyBondTarget]<0
user.effects[PBEffects::DestinyBondTarget] = target.index
end
end
end
end
#=============================================================================
# Effects after all hits (i.e. at end of move usage)
#=============================================================================
def pbEffectsAfterMove(user,targets,move,numHits)
# Defrost
if move.damagingMove?
targets.each do |b|
next if b.damageState.unaffected || b.damageState.substitute
next if b.status != :FROZEN
# NOTE: Non-Fire-type moves that thaw the user will also thaw the
# target (in Gen 6+).
if move.calcType == :FIRE || (Settings::MECHANICS_GENERATION >= 6 && move.thawsUser?)
b.pbCureStatus
end
end
end
# Destiny Bond
# NOTE: Although Destiny Bond is similar to Grudge, they don't apply at
# the same time (although Destiny Bond does check whether it's going
# to trigger at the same time as Grudge).
if user.effects[PBEffects::DestinyBondTarget]>=0 && !user.fainted?
dbName = @battle.battlers[user.effects[PBEffects::DestinyBondTarget]].pbThis
@battle.pbDisplay(_INTL("{1} took its attacker down with it!",dbName))
user.pbReduceHP(user.hp,false)
user.pbItemHPHealCheck
user.pbFaint
@battle.pbJudgeCheckpoint(user)
end
# User's ability
if user.abilityActive?
BattleHandlers.triggerUserAbilityEndOfMove(user.ability,user,targets,move,@battle)
end
# Greninja - Battle Bond
if !user.fainted? && !user.effects[PBEffects::Transform] &&
user.isSpecies?(:GRENINJA) && user.ability == :BATTLEBOND
if !@battle.pbAllFainted?(user.idxOpposingSide) &&
!@battle.battleBond[user.index&1][user.pokemonIndex]
numFainted = 0
targets.each { |b| numFainted += 1 if b.damageState.fainted }
if numFainted>0 && user.form==1
@battle.battleBond[user.index&1][user.pokemonIndex] = true
@battle.pbDisplay(_INTL("{1} became fully charged due to its bond with its Trainer!",user.pbThis))
@battle.pbShowAbilitySplash(user,true)
@battle.pbHideAbilitySplash(user)
user.pbChangeForm(2,_INTL("{1} became Ash-Greninja!",user.pbThis))
end
end
end
# Consume user's Gem
if user.effects[PBEffects::GemConsumed]
# NOTE: The consume animation and message for Gems are shown immediately
# after the move's animation, but the item is only consumed now.
user.pbConsumeItem
end
# Pokémon switching caused by Roar, Whirlwind, Circle Throw, Dragon Tail
switchedBattlers = []
move.pbSwitchOutTargetsEffect(user,targets,numHits,switchedBattlers)
# Target's item, user's item, target's ability (all negated by Sheer Force)
if move.addlEffect==0 || !user.hasActiveAbility?(:SHEERFORCE)
pbEffectsAfterMove2(user,targets,move,numHits,switchedBattlers)
end
# Some move effects that need to happen here, i.e. U-turn/Volt Switch
# switching, Baton Pass switching, Parting Shot switching, Relic Song's form
# changing, Fling/Natural Gift consuming item.
if !switchedBattlers.include?(user.index)
move.pbEndOfMoveUsageEffect(user,targets,numHits,switchedBattlers)
end
if numHits>0
@battle.eachBattler { |b| b.pbItemEndOfMoveCheck }
end
end
# Everything in this method is negated by Sheer Force.
def pbEffectsAfterMove2(user,targets,move,numHits,switchedBattlers)
hpNow = user.hp # Intentionally determined now, before Shell Bell
# Target's held item (Eject Button, Red Card)
switchByItem = []
@battle.pbPriority(true).each do |b|
next if !targets.any? { |targetB| targetB.index==b.index }
next if b.damageState.unaffected || b.damageState.calcDamage==0 ||
switchedBattlers.include?(b.index)
next if !b.itemActive?
BattleHandlers.triggerTargetItemAfterMoveUse(b.item,b,user,move,switchByItem,@battle)
end
@battle.moldBreaker = false if switchByItem.include?(user.index)
@battle.pbPriority(true).each do |b|
b.pbEffectsOnSwitchIn(true) if switchByItem.include?(b.index)
end
switchByItem.each { |idxB| switchedBattlers.push(idxB) }
# User's held item (Life Orb, Shell Bell)
if !switchedBattlers.include?(user.index) && user.itemActive?
BattleHandlers.triggerUserItemAfterMoveUse(user.item,user,targets,move,numHits,@battle)
end
# Target's ability (Berserk, Color Change, Emergency Exit, Pickpocket, Wimp Out)
switchWimpOut = []
@battle.pbPriority(true).each do |b|
next if !targets.any? { |targetB| targetB.index==b.index }
next if b.damageState.unaffected || switchedBattlers.include?(b.index)
next if !b.abilityActive?
BattleHandlers.triggerTargetAbilityAfterMoveUse(b.ability,b,user,move,switchedBattlers,@battle)
if !switchedBattlers.include?(b.index) && move.damagingMove?
if b.pbAbilitiesOnDamageTaken(b.damageState.initialHP) # Emergency Exit, Wimp Out
switchWimpOut.push(b.index)
end
end
end
@battle.moldBreaker = false if switchWimpOut.include?(user.index)
@battle.pbPriority(true).each do |b|
next if b.index==user.index
b.pbEffectsOnSwitchIn(true) if switchWimpOut.include?(b.index)
end
switchWimpOut.each { |idxB| switchedBattlers.push(idxB) }
# User's ability (Emergency Exit, Wimp Out)
if !switchedBattlers.include?(user.index) && move.damagingMove?
hpNow = user.hp if user.hp<hpNow # In case HP was lost because of Life Orb
if user.pbAbilitiesOnDamageTaken(user.initialHP,hpNow)
@battle.moldBreaker = false
user.pbEffectsOnSwitchIn(true)
switchedBattlers.push(user.index)
end
end
end
end

View File

@@ -0,0 +1,181 @@
begin
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
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
KingsShield = 45
LaserFocus = 46
LeechSeed = 47
LockOn = 48
LockOnPos = 49
MagicBounce = 50
MagicCoat = 51
MagnetRise = 52
MeanLook = 53
MeFirst = 54
Metronome = 55
MicleBerry = 56
Minimize = 57
MiracleEye = 58
MirrorCoat = 59
MirrorCoatTarget = 60
MoveNext = 61
MudSport = 62
Nightmare = 63
Outrage = 64
ParentalBond = 65
PerishSong = 66
PerishSongUser = 67
PickupItem = 68
PickupUse = 69
Pinch = 70 # Battle Palace only
Powder = 71
PowerTrick = 72
Prankster = 73
PriorityAbility = 74
PriorityItem = 75
Protect = 76
ProtectRate = 77
Pursuit = 78
Quash = 79
Rage = 80
RagePowder = 81 # Used along with FollowMe
Rollout = 82
Roost = 83
ShellTrap = 84
SkyDrop = 85
SlowStart = 86
SmackDown = 87
Snatch = 88
SpikyShield = 89
Spotlight = 90
Stockpile = 91
StockpileDef = 92
StockpileSpDef = 93
Substitute = 94
Taunt = 95
Telekinesis = 96
ThroatChop = 97
Torment = 98
Toxic = 99
Transform = 100
TransformSpecies = 101
Trapping = 102 # Trapping move
TrappingMove = 103
TrappingUser = 104
Truant = 105
TwoTurnAttack = 106
Type3 = 107
Unburden = 108
Uproar = 109
WaterSport = 110
WeightChange = 111
Yawn = 112
#===========================================================================
# 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
rescue Exception
if $!.is_a?(SystemExit) || "#{$!.class}"=="Reset"
raise $!
end
end

View File

@@ -0,0 +1,601 @@
module BattleHandlers
# Battler's speed calculation
SpeedCalcAbility = AbilityHandlerHash.new
SpeedCalcItem = ItemHandlerHash.new
# Battler's weight calculation
WeightCalcAbility = AbilityHandlerHash.new
WeightCalcItem = ItemHandlerHash.new # Float Stone
# Battler's HP changed
HPHealItem = ItemHandlerHash.new
AbilityOnHPDroppedBelowHalf = AbilityHandlerHash.new
# Battler's status problem
StatusCheckAbilityNonIgnorable = AbilityHandlerHash.new # Comatose
StatusImmunityAbility = AbilityHandlerHash.new
StatusImmunityAbilityNonIgnorable = AbilityHandlerHash.new
StatusImmunityAllyAbility = AbilityHandlerHash.new
AbilityOnStatusInflicted = AbilityHandlerHash.new # Synchronize
StatusCureItem = ItemHandlerHash.new
StatusCureAbility = AbilityHandlerHash.new
# Battler's stat stages
StatLossImmunityAbility = AbilityHandlerHash.new
StatLossImmunityAbilityNonIgnorable = AbilityHandlerHash.new # Full Metal Body
StatLossImmunityAllyAbility = AbilityHandlerHash.new # Flower Veil
AbilityOnStatGain = AbilityHandlerHash.new # None!
AbilityOnStatLoss = AbilityHandlerHash.new
# Priority and turn order
PriorityChangeAbility = AbilityHandlerHash.new
PriorityBracketChangeAbility = AbilityHandlerHash.new # Stall
PriorityBracketChangeItem = ItemHandlerHash.new
PriorityBracketUseAbility = AbilityHandlerHash.new # None!
PriorityBracketUseItem = ItemHandlerHash.new
# Move usage failures
AbilityOnFlinch = AbilityHandlerHash.new # Steadfast
MoveBlockingAbility = AbilityHandlerHash.new
MoveImmunityTargetAbility = AbilityHandlerHash.new
# Move usage
MoveBaseTypeModifierAbility = AbilityHandlerHash.new
# Accuracy calculation
AccuracyCalcUserAbility = AbilityHandlerHash.new
AccuracyCalcUserAllyAbility = AbilityHandlerHash.new # Victory Star
AccuracyCalcTargetAbility = AbilityHandlerHash.new
AccuracyCalcUserItem = ItemHandlerHash.new
AccuracyCalcTargetItem = ItemHandlerHash.new
# Damage calculation
DamageCalcUserAbility = AbilityHandlerHash.new
DamageCalcUserAllyAbility = AbilityHandlerHash.new
DamageCalcTargetAbility = AbilityHandlerHash.new
DamageCalcTargetAbilityNonIgnorable = AbilityHandlerHash.new
DamageCalcTargetAllyAbility = AbilityHandlerHash.new
DamageCalcUserItem = ItemHandlerHash.new
DamageCalcTargetItem = ItemHandlerHash.new
# Critical hit calculation
CriticalCalcUserAbility = AbilityHandlerHash.new
CriticalCalcTargetAbility = AbilityHandlerHash.new
CriticalCalcUserItem = ItemHandlerHash.new
CriticalCalcTargetItem = ItemHandlerHash.new # None!
# Upon a move hitting a target
TargetAbilityOnHit = AbilityHandlerHash.new
UserAbilityOnHit = AbilityHandlerHash.new # Poison Touch
TargetItemOnHit = ItemHandlerHash.new
TargetItemOnHitPositiveBerry = ItemHandlerHash.new
# Abilities/items that trigger at the end of using a move
UserAbilityEndOfMove = AbilityHandlerHash.new
TargetItemAfterMoveUse = ItemHandlerHash.new
UserItemAfterMoveUse = ItemHandlerHash.new
TargetAbilityAfterMoveUse = AbilityHandlerHash.new
EndOfMoveItem = ItemHandlerHash.new # Leppa Berry
EndOfMoveStatRestoreItem = ItemHandlerHash.new # White Herb
# Experience and EV gain
ExpGainModifierItem = ItemHandlerHash.new # Lucky Egg
EVGainModifierItem = ItemHandlerHash.new
# Weather and terrin
WeatherExtenderItem = ItemHandlerHash.new
TerrainExtenderItem = ItemHandlerHash.new # Terrain Extender
TerrainStatBoostItem = ItemHandlerHash.new
# End Of Round
EORWeatherAbility = AbilityHandlerHash.new
EORHealingAbility = AbilityHandlerHash.new
EORHealingItem = ItemHandlerHash.new
EOREffectAbility = AbilityHandlerHash.new
EOREffectItem = ItemHandlerHash.new
EORGainItemAbility = AbilityHandlerHash.new
# Switching and fainting
CertainSwitchingUserAbility = AbilityHandlerHash.new # None!
CertainSwitchingUserItem = ItemHandlerHash.new # Shed Shell
TrappingTargetAbility = AbilityHandlerHash.new
TrappingTargetItem = ItemHandlerHash.new # None!
AbilityOnSwitchIn = AbilityHandlerHash.new
ItemOnSwitchIn = ItemHandlerHash.new # Air Balloon
ItemOnIntimidated = ItemHandlerHash.new # Adrenaline Orb
AbilityOnSwitchOut = AbilityHandlerHash.new
AbilityChangeOnBattlerFainting = AbilityHandlerHash.new
AbilityOnBattlerFainting = AbilityHandlerHash.new # Soul-Heart
# Running from battle
RunFromBattleAbility = AbilityHandlerHash.new # Run Away
RunFromBattleItem = ItemHandlerHash.new # Smoke Ball
#=============================================================================
def self.triggerSpeedCalcAbility(ability,battler,mult)
ret = SpeedCalcAbility.trigger(ability,battler,mult)
return (ret!=nil) ? ret : mult
end
def self.triggerSpeedCalcItem(item,battler,mult)
ret = SpeedCalcItem.trigger(item,battler,mult)
return (ret!=nil) ? ret : mult
end
#=============================================================================
def self.triggerWeightCalcAbility(ability,battler,w)
ret = WeightCalcAbility.trigger(ability,battler,w)
return (ret!=nil) ? ret : w
end
def self.triggerWeightCalcItem(item,battler,w)
ret = WeightCalcItem.trigger(item,battler,w)
return (ret!=nil) ? ret : w
end
#=============================================================================
def self.triggerHPHealItem(item,battler,battle,forced)
ret = HPHealItem.trigger(item,battler,battle,forced)
return (ret!=nil) ? ret : false
end
def self.triggerAbilityOnHPDroppedBelowHalf(ability,user,battle)
ret = AbilityOnHPDroppedBelowHalf.trigger(ability,user,battle)
return (ret!=nil) ? ret : false
end
#=============================================================================
def self.triggerStatusCheckAbilityNonIgnorable(ability,battler,status)
ret = StatusCheckAbilityNonIgnorable.trigger(ability,battler,status)
return (ret!=nil) ? ret : false
end
def self.triggerStatusImmunityAbility(ability,battler,status)
ret = StatusImmunityAbility.trigger(ability,battler,status)
return (ret!=nil) ? ret : false
end
def self.triggerStatusImmunityAbilityNonIgnorable(ability,battler,status)
ret = StatusImmunityAbilityNonIgnorable.trigger(ability,battler,status)
return (ret!=nil) ? ret : false
end
def self.triggerStatusImmunityAllyAbility(ability,battler,status)
ret = StatusImmunityAllyAbility.trigger(ability,battler,status)
return (ret!=nil) ? ret : false
end
def self.triggerAbilityOnStatusInflicted(ability,battler,user,status)
AbilityOnStatusInflicted.trigger(ability,battler,user,status)
end
def self.triggerStatusCureItem(item,battler,battle,forced)
ret = StatusCureItem.trigger(item,battler,battle,forced)
return (ret!=nil) ? ret : false
end
def self.triggerStatusCureAbility(ability,battler)
ret = StatusCureAbility.trigger(ability,battler)
return (ret!=nil) ? ret : false
end
#=============================================================================
def self.triggerStatLossImmunityAbility(ability,battler,stat,battle,showMessages)
ret = StatLossImmunityAbility.trigger(ability,battler,stat,battle,showMessages)
return (ret!=nil) ? ret : false
end
def self.triggerStatLossImmunityAbilityNonIgnorable(ability,battler,stat,battle,showMessages)
ret = StatLossImmunityAbilityNonIgnorable.trigger(ability,battler,stat,battle,showMessages)
return (ret!=nil) ? ret : false
end
def self.triggerStatLossImmunityAllyAbility(ability,bearer,battler,stat,battle,showMessages)
ret = StatLossImmunityAllyAbility.trigger(ability,bearer,battler,stat,battle,showMessages)
return (ret!=nil) ? ret : false
end
def self.triggerAbilityOnStatGain(ability,battler,stat,user)
AbilityOnStatGain.trigger(ability,battler,stat,user)
end
def self.triggerAbilityOnStatLoss(ability,battler,stat,user)
AbilityOnStatLoss.trigger(ability,battler,stat,user)
end
#=============================================================================
def self.triggerPriorityChangeAbility(ability,battler,move,pri)
ret = PriorityChangeAbility.trigger(ability,battler,move,pri)
return (ret!=nil) ? ret : pri
end
def self.triggerPriorityBracketChangeAbility(ability,battler,subPri,battle)
ret = PriorityBracketChangeAbility.trigger(ability,battler,subPri,battle)
return (ret!=nil) ? ret : subPri
end
def self.triggerPriorityBracketChangeItem(item,battler,subPri,battle)
ret = PriorityBracketChangeItem.trigger(item,battler,subPri,battle)
return (ret!=nil) ? ret : subPri
end
def self.triggerPriorityBracketUseAbility(ability,battler,battle)
PriorityBracketUseAbility.trigger(ability,battler,battle)
end
def self.triggerPriorityBracketUseItem(item,battler,battle)
PriorityBracketUseItem.trigger(item,battler,battle)
end
#=============================================================================
def self.triggerAbilityOnFlinch(ability,battler,battle)
AbilityOnFlinch.trigger(ability,battler,battle)
end
def self.triggerMoveBlockingAbility(ability,bearer,user,targets,move,battle)
ret = MoveBlockingAbility.trigger(ability,bearer,user,targets,move,battle)
return (ret!=nil) ? ret : false
end
def self.triggerMoveImmunityTargetAbility(ability,user,target,move,type,battle)
ret = MoveImmunityTargetAbility.trigger(ability,user,target,move,type,battle)
return (ret!=nil) ? ret : false
end
#=============================================================================
def self.triggerMoveBaseTypeModifierAbility(ability,user,move,type)
ret = MoveBaseTypeModifierAbility.trigger(ability,user,move,type)
return (ret!=nil) ? ret : type
end
#=============================================================================
def self.triggerAccuracyCalcUserAbility(ability,mods,user,target,move,type)
AccuracyCalcUserAbility.trigger(ability,mods,user,target,move,type)
end
def self.triggerAccuracyCalcUserAllyAbility(ability,mods,user,target,move,type)
AccuracyCalcUserAllyAbility.trigger(ability,mods,user,target,move,type)
end
def self.triggerAccuracyCalcTargetAbility(ability,mods,user,target,move,type)
AccuracyCalcTargetAbility.trigger(ability,mods,user,target,move,type)
end
def self.triggerAccuracyCalcUserItem(item,mods,user,target,move,type)
AccuracyCalcUserItem.trigger(item,mods,user,target,move,type)
end
def self.triggerAccuracyCalcTargetItem(item,mods,user,target,move,type)
AccuracyCalcTargetItem.trigger(item,mods,user,target,move,type)
end
#=============================================================================
def self.triggerDamageCalcUserAbility(ability,user,target,move,mults,baseDmg,type)
DamageCalcUserAbility.trigger(ability,user,target,move,mults,baseDmg,type)
end
def self.triggerDamageCalcUserAllyAbility(ability,user,target,move,mults,baseDmg,type)
DamageCalcUserAllyAbility.trigger(ability,user,target,move,mults,baseDmg,type)
end
def self.triggerDamageCalcTargetAbility(ability,user,target,move,mults,baseDmg,type)
DamageCalcTargetAbility.trigger(ability,user,target,move,mults,baseDmg,type)
end
def self.triggerDamageCalcTargetAbilityNonIgnorable(ability,user,target,move,mults,baseDmg,type)
DamageCalcTargetAbilityNonIgnorable.trigger(ability,user,target,move,mults,baseDmg,type)
end
def self.triggerDamageCalcTargetAllyAbility(ability,user,target,move,mults,baseDmg,type)
DamageCalcTargetAllyAbility.trigger(ability,user,target,move,mults,baseDmg,type)
end
def self.triggerDamageCalcUserItem(item,user,target,move,mults,baseDmg,type)
DamageCalcUserItem.trigger(item,user,target,move,mults,baseDmg,type)
end
def self.triggerDamageCalcTargetItem(item,user,target,move,mults,baseDmg,type)
DamageCalcTargetItem.trigger(item,user,target,move,mults,baseDmg,type)
end
#=============================================================================
def self.triggerCriticalCalcUserAbility(ability,user,target,c)
ret = CriticalCalcUserAbility.trigger(ability,user,target,c)
return (ret!=nil) ? ret : c
end
def self.triggerCriticalCalcTargetAbility(ability,user,target,c)
ret = CriticalCalcTargetAbility.trigger(ability,user,target,c)
return (ret!=nil) ? ret : c
end
def self.triggerCriticalCalcUserItem(item,user,target,c)
ret = CriticalCalcUserItem.trigger(item,user,target,c)
return (ret!=nil) ? ret : c
end
def self.triggerCriticalCalcTargetItem(item,user,target,c)
ret = CriticalCalcTargetItem.trigger(item,user,target,c)
return (ret!=nil) ? ret : c
end
#=============================================================================
def self.triggerTargetAbilityOnHit(ability,user,target,move,battle)
TargetAbilityOnHit.trigger(ability,user,target,move,battle)
end
def self.triggerUserAbilityOnHit(ability,user,target,move,battle)
UserAbilityOnHit.trigger(ability,user,target,move,battle)
end
def self.triggerTargetItemOnHit(item,user,target,move,battle)
TargetItemOnHit.trigger(item,user,target,move,battle)
end
def self.triggerTargetItemOnHitPositiveBerry(item,battler,battle,forced)
ret = TargetItemOnHitPositiveBerry.trigger(item,battler,battle,forced)
return (ret!=nil) ? ret : false
end
#=============================================================================
def self.triggerUserAbilityEndOfMove(ability,user,targets,move,battle)
UserAbilityEndOfMove.trigger(ability,user,targets,move,battle)
end
def self.triggerTargetItemAfterMoveUse(item,battler,user,move,switched,battle)
TargetItemAfterMoveUse.trigger(item,battler,user,move,switched,battle)
end
def self.triggerUserItemAfterMoveUse(item,user,targets,move,numHits,battle)
UserItemAfterMoveUse.trigger(item,user,targets,move,numHits,battle)
end
def self.triggerTargetAbilityAfterMoveUse(ability,target,user,move,switched,battle)
TargetAbilityAfterMoveUse.trigger(ability,target,user,move,switched,battle)
end
def self.triggerEndOfMoveItem(item,battler,battle,forced)
ret = EndOfMoveItem.trigger(item,battler,battle,forced)
return (ret!=nil) ? ret : false
end
def self.triggerEndOfMoveStatRestoreItem(item,battler,battle,forced)
ret = EndOfMoveStatRestoreItem.trigger(item,battler,battle,forced)
return (ret!=nil) ? ret : false
end
#=============================================================================
def self.triggerExpGainModifierItem(item,battler,exp)
ret = ExpGainModifierItem.trigger(item,battler,exp)
return (ret!=nil) ? ret : -1
end
def self.triggerEVGainModifierItem(item,battler,evarray)
return false if !EVGainModifierItem[item]
EVGainModifierItem.trigger(item,battler,evarray)
return true
end
#=============================================================================
def self.triggerWeatherExtenderItem(item,weather,duration,battler,battle)
ret = WeatherExtenderItem.trigger(item,weather,duration,battler,battle)
return (ret!=nil) ? ret : duration
end
def self.triggerTerrainExtenderItem(item,terrain,duration,battler,battle)
ret = TerrainExtenderItem.trigger(item,terrain,duration,battler,battle)
return (ret!=nil) ? ret : duration
end
def self.triggerTerrainStatBoostItem(item,battler,battle)
ret = TerrainStatBoostItem.trigger(item,battler,battle)
return (ret!=nil) ? ret : false
end
#=============================================================================
def self.triggerEORWeatherAbility(ability,weather,battler,battle)
EORWeatherAbility.trigger(ability,weather,battler,battle)
end
def self.triggerEORHealingAbility(ability,battler,battle)
EORHealingAbility.trigger(ability,battler,battle)
end
def self.triggerEORHealingItem(item,battler,battle)
EORHealingItem.trigger(item,battler,battle)
end
def self.triggerEOREffectAbility(ability,battler,battle)
EOREffectAbility.trigger(ability,battler,battle)
end
def self.triggerEOREffectItem(item,battler,battle)
EOREffectItem.trigger(item,battler,battle)
end
def self.triggerEORGainItemAbility(ability,battler,battle)
EORGainItemAbility.trigger(ability,battler,battle)
end
#=============================================================================
def self.triggerCertainSwitchingUserAbility(ability,switcher,battle)
ret = CertainSwitchingUserAbility.trigger(ability,switcher,battle)
return (ret!=nil) ? ret : false
end
def self.triggerCertainSwitchingUserItem(item,switcher,battle)
ret = CertainSwitchingUserItem.trigger(item,switcher,battle)
return (ret!=nil) ? ret : false
end
def self.triggerTrappingTargetAbility(ability,switcher,bearer,battle)
ret = TrappingTargetAbility.trigger(ability,switcher,bearer,battle)
return (ret!=nil) ? ret : false
end
def self.triggerTrappingTargetItem(item,switcher,bearer,battle)
ret = TrappingTargetItem.trigger(item,switcher,bearer,battle)
return (ret!=nil) ? ret : false
end
def self.triggerAbilityOnSwitchIn(ability,battler,battle)
AbilityOnSwitchIn.trigger(ability,battler,battle)
end
def self.triggerItemOnSwitchIn(item,battler,battle)
ItemOnSwitchIn.trigger(item,battler,battle)
end
def self.triggerItemOnIntimidated(item,battler,battle)
ret = ItemOnIntimidated.trigger(item,battler,battle)
return (ret!=nil) ? ret : false
end
def self.triggerAbilityOnSwitchOut(ability,battler,endOfBattle)
AbilityOnSwitchOut.trigger(ability,battler,endOfBattle)
end
def self.triggerAbilityChangeOnBattlerFainting(ability,battler,fainted,battle)
AbilityChangeOnBattlerFainting.trigger(ability,battler,fainted,battle)
end
def self.triggerAbilityOnBattlerFainting(ability,battler,fainted,battle)
AbilityOnBattlerFainting.trigger(ability,battler,fainted,battle)
end
#=============================================================================
def self.triggerRunFromBattleAbility(ability,battler)
ret = RunFromBattleAbility.trigger(ability,battler)
return (ret!=nil) ? ret : false
end
def self.triggerRunFromBattleItem(item,battler)
ret = RunFromBattleItem.trigger(item,battler)
return (ret!=nil) ? ret : false
end
end
def pbBattleConfusionBerry(battler,battle,item,forced,flavor,confuseMsg)
return false if !forced && !battler.canHeal?
return false if !forced && !battler.canConsumePinchBerry?(Settings::MECHANICS_GENERATION >= 7)
itemName = GameData::Item.get(item).name
battle.pbCommonAnimation("EatBerry",battler) if !forced
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
amt = battler.pbRecoverHP(battler.totalhp / fraction_to_heal)
if amt>0
if forced
PBDebug.log("[Item triggered] Forced consuming of #{itemName}")
battle.pbDisplay(_INTL("{1}'s HP was restored.",battler.pbThis))
else
battle.pbDisplay(_INTL("{1} restored its health using its {2}!",battler.pbThis,itemName))
end
end
flavor_stat = [:ATTACK, :DEFENSE, :SPEED, :SPECIAL_ATTACK, :SPECIAL_DEFENSE][flavor]
battler.nature.stat_changes.each do |change|
next if change[1] > 0 || change[0] != flavor_stat
battle.pbDisplay(confuseMsg)
battler.pbConfuse if battler.pbCanConfuseSelf?(false)
break
end
return true
end
def pbBattleStatIncreasingBerry(battler,battle,item,forced,stat,increment=1)
return false if !forced && !battler.canConsumePinchBerry?
return false if !battler.pbCanRaiseStatStage?(stat,battler)
itemName = GameData::Item.get(item).name
if forced
PBDebug.log("[Item triggered] Forced consuming of #{itemName}")
return battler.pbRaiseStatStage(stat,increment,battler)
end
battle.pbCommonAnimation("EatBerry",battler)
return battler.pbRaiseStatStageByCause(stat,increment,battler,itemName)
end
# For abilities that grant immunity to moves of a particular type, and raises
# one of the ability's bearer's stats instead.
def pbBattleMoveImmunityStatAbility(user,target,move,moveType,immuneType,stat,increment,battle)
return false if user.index==target.index
return false if moveType != immuneType
battle.pbShowAbilitySplash(target)
if target.pbCanRaiseStatStage?(stat,target)
if PokeBattle_SceneConstants::USE_ABILITY_SPLASH
target.pbRaiseStatStage(stat,increment,target)
else
target.pbRaiseStatStageByCause(stat,increment,target,target.abilityName)
end
else
if PokeBattle_SceneConstants::USE_ABILITY_SPLASH
battle.pbDisplay(_INTL("It doesn't affect {1}...",target.pbThis(true)))
else
battle.pbDisplay(_INTL("{1}'s {2} made {3} ineffective!",
target.pbThis,target.abilityName,move.name))
end
end
battle.pbHideAbilitySplash(target)
return true
end
# For abilities that grant immunity to moves of a particular type, and heals the
# ability's bearer by 1/4 of its total HP instead.
def pbBattleMoveImmunityHealAbility(user,target,move,moveType,immuneType,battle)
return false if user.index==target.index
return false if moveType != immuneType
battle.pbShowAbilitySplash(target)
if target.canHeal? && target.pbRecoverHP(target.totalhp/4)>0
if PokeBattle_SceneConstants::USE_ABILITY_SPLASH
battle.pbDisplay(_INTL("{1}'s HP was restored.",target.pbThis))
else
battle.pbDisplay(_INTL("{1}'s {2} restored its HP.",target.pbThis,target.abilityName))
end
else
if PokeBattle_SceneConstants::USE_ABILITY_SPLASH
battle.pbDisplay(_INTL("It doesn't affect {1}...",target.pbThis(true)))
else
battle.pbDisplay(_INTL("{1}'s {2} made {3} ineffective!",
target.pbThis,target.abilityName,move.name))
end
end
battle.pbHideAbilitySplash(target)
return true
end
def pbBattleGem(user,type,move,mults,moveType)
# Pledge moves never consume Gems
return if move.is_a?(PokeBattle_PledgeMove)
return if moveType != type
user.effects[PBEffects::GemConsumed] = user.item_id
if Settings::MECHANICS_GENERATION >= 6
mults[:base_damage_multiplier] *= 1.3
else
mults[:base_damage_multiplier] *= 1.5
end
end
def pbBattleTypeWeakingBerry(type,moveType,target,mults)
return if moveType != type
return if Effectiveness.resistant?(target.damageState.typeMod) && moveType != :NORMAL
mults[:final_damage_multiplier] /= 2
target.damageState.berryWeakened = true
target.battle.pbCommonAnimation("EatBerry",target)
end
def pbBattleWeatherAbility(weather,battler,battle,ignorePrimal=false)
return if !ignorePrimal && [:HarshSun, :HeavyRain, :StrongWinds].include?(battle.field.weather)
return if battle.field.weather==weather
battle.pbShowAbilitySplash(battler)
if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH
battle.pbDisplay(_INTL("{1}'s {2} activated!",battler.pbThis,battler.abilityName))
end
fixedDuration = false
fixedDuration = true if Settings::FIXED_DURATION_WEATHER_FROM_ABILITY &&
![:HarshSun, :HeavyRain, :StrongWinds].include?(weather)
battle.pbStartWeather(battler,weather,fixedDuration)
# NOTE: The ability splash is hidden again in def pbStartWeather.
end

View File

@@ -0,0 +1,141 @@
class PokeBattle_Move
attr_reader :battle
attr_reader :realMove
attr_accessor :id
attr_reader :name
attr_reader :function
attr_reader :baseDamage
attr_reader :type
attr_reader :category
attr_reader :accuracy
attr_accessor :pp
attr_writer :total_pp
attr_reader :addlEffect
attr_reader :target
attr_reader :priority
attr_reader :flags
attr_accessor :calcType
attr_accessor :powerBoost
attr_accessor :snatched
def to_int; return @id; end
#=============================================================================
# Creating a move
#=============================================================================
def initialize(battle, move)
@battle = battle
@realMove = move
@id = move.id
@name = move.name # Get the move's name
# Get data on the move
@function = move.function_code
@baseDamage = move.base_damage
@type = move.type
@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
@calcType = nil
@powerBoost = false # For Aerilate, Pixilate, Refrigerate, Galvanize
@snatched = false
end
# This is the code actually used to generate a PokeBattle_Move object. The
# object generated is a subclass of this one which depends on the move's
# function code (found in the script section PokeBattle_MoveEffect).
def PokeBattle_Move.from_pokemon_move(battle, move)
validate move => Pokemon::Move
moveFunction = move.function_code || "000"
className = sprintf("PokeBattle_Move_%s", moveFunction)
if Object.const_defined?(className)
return Object.const_get(className).new(battle, move)
end
return PokeBattle_UnimplementedMove.new(battle, move)
end
#=============================================================================
# About the move
#=============================================================================
def pbTarget(_user); return GameData::Target.get(@target); end
def total_pp
return @total_pp if @total_pp && @total_pp>0 # Usually undefined
return @realMove.total_pp if @realMove
return 0
end
# NOTE: This method is only ever called while using a move (and also by the
# AI), so using @calcType here is acceptable.
def physicalMove?(thisType=nil)
return (@category==0) if Settings::MOVE_CATEGORY_PER_MOVE
thisType ||= @calcType
thisType ||= @type
return true if !thisType
return GameData::Type.get(thisType).physical?
end
# NOTE: This method is only ever called while using a move (and also by the
# AI), so using @calcType here is acceptable.
def specialMove?(thisType=nil)
return (@category==1) if Settings::MOVE_CATEGORY_PER_MOVE
thisType ||= @calcType
thisType ||= @type
return false if !thisType
return GameData::Type.get(thisType).special?
end
def damagingMove?; return @category!=2; end
def statusMove?; return @category==2; end
def usableWhenAsleep?; return false; end
def unusableInGravity?; return false; end
def healingMove?; return false; end
def recoilMove?; return false; end
def flinchingMove?; return false; end
def callsAnotherMove?; return false; end
# Whether the move can/will hit more than once in the same turn (including
# Beat Up which may instead hit just once). Not the same as pbNumHits>1.
def multiHitMove?; return false; end
def chargingTurnMove?; return false; end
def successCheckPerHit?; return false; end
def hitsFlyingTargets?; return false; end
def hitsDiggingTargets?; return false; end
def hitsDivingTargets?; return false; end
def ignoresReflect?; return false; end # For Brick Break
def cannotRedirect?; return false; end # For Future Sight/Doom Desire
def worksWithNoTargets?; return false; end # For Explosion
def damageReducedByBurn?; return true; end # For Facade
def triggersHyperMode?; return false; end
def contactMove?; return @flags[/a/]; end
def canProtectAgainst?; return @flags[/b/]; end
def canMagicCoat?; return @flags[/c/]; end
def canSnatch?; return @flags[/d/]; end
def canMirrorMove?; return @flags[/e/]; end
def canKingsRock?; return @flags[/f/]; end
def thawsUser?; return @flags[/g/]; end
def highCriticalRate?; return @flags[/h/]; end
def bitingMove?; return @flags[/i/]; end
def punchingMove?; return @flags[/j/]; end
def soundMove?; return @flags[/k/]; end
def powderMove?; return @flags[/l/]; end
def pulseMove?; return @flags[/m/]; end
def bombMove?; return @flags[/n/]; end
def danceMove?; return @flags[/o/]; end
# Causes perfect accuracy (param=1) and double damage (param=2).
def tramplesMinimize?(_param=1); return false; end
def nonLethal?(_user,_target); return false; end # For False Swipe
def ignoresSubstitute?(user) # user is the Pokémon using this move
if Settings::MECHANICS_GENERATION >= 6
return true if soundMove?
return true if user && user.hasActiveAbility?(:INFILTRATOR)
end
return false
end
end

View File

@@ -0,0 +1,350 @@
class PokeBattle_Move
#=============================================================================
# Effect methods per move usage
#=============================================================================
def pbCanChooseMove?(user,commandPhase,showMessages); return true; end # For Belch
def pbDisplayChargeMessage(user); end # For Focus Punch/shell Trap/Beak Blast
def pbOnStartUse(user,targets); end
def pbAddTarget(targets,user); end # For Counter, etc. and Bide
# Reset move usage counters (child classes can increment them).
def pbChangeUsageCounters(user,specialUsage)
user.effects[PBEffects::FuryCutter] = 0
user.effects[PBEffects::ParentalBond] = 0
user.effects[PBEffects::ProtectRate] = 1
@battle.field.effects[PBEffects::FusionBolt] = false
@battle.field.effects[PBEffects::FusionFlare] = false
end
def pbDisplayUseMessage(user)
@battle.pbDisplayBrief(_INTL("{1} used {2}!",user.pbThis,@name))
end
def pbMissMessage(user,target); return false; end
#=============================================================================
#
#=============================================================================
# Whether the move is currently in the "charging" turn of a two turn attack.
# Is false if Power Herb or another effect lets a two turn move charge and
# attack in the same turn.
# user.effects[PBEffects::TwoTurnAttack] is set to the move's ID during the
# charging turn, and is nil during the attack turn.
def pbIsChargingTurn?(user); return false; end
def pbDamagingMove?; return damagingMove?; end
def pbContactMove?(user)
return false if user.hasActiveAbility?(:LONGREACH)
return contactMove?
end
# The maximum number of hits in a round this move will actually perform. This
# can be 1 for Beat Up, and can be 2 for any moves affected by Parental Bond.
def pbNumHits(user,targets)
if user.hasActiveAbility?(:PARENTALBOND) && pbDamagingMove? &&
!chargingTurnMove? && targets.length==1
# Record that Parental Bond applies, to weaken the second attack
user.effects[PBEffects::ParentalBond] = 3
return 2
end
return 1
end
#=============================================================================
# Effect methods per hit
#=============================================================================
def pbOverrideSuccessCheckPerHit(user,target); return false; end
def pbCrashDamage(user); end
def pbInitialEffect(user,targets,hitNum); end
def pbShowAnimation(id,user,targets,hitNum=0,showAnimation=true)
return if !showAnimation
if user.effects[PBEffects::ParentalBond]==1
@battle.pbCommonAnimation("ParentalBond",user,targets)
else
@battle.pbAnimation(id,user,targets,hitNum)
end
end
def pbSelfKO(user); end
def pbEffectWhenDealingDamage(user,target); end
def pbEffectAgainstTarget(user,target); end
def pbEffectGeneral(user); end
def pbAdditionalEffect(user,target); end
def pbEffectAfterAllHits(user,target); end # Move effects that occur after all hits
def pbSwitchOutTargetsEffect(user,targets,numHits,switchedBattlers); end
def pbEndOfMoveUsageEffect(user,targets,numHits,switchedBattlers); end
#=============================================================================
# Check if target is immune to the move because of its ability
#=============================================================================
def pbImmunityByAbility(user,target)
return false if @battle.moldBreaker
ret = false
if target.abilityActive?
ret = BattleHandlers.triggerMoveImmunityTargetAbility(target.ability,
user,target,self,@calcType,@battle)
end
return ret
end
#=============================================================================
# Move failure checks
#=============================================================================
# Check whether the move fails completely due to move-specific requirements.
def pbMoveFailed?(user,targets); return false; end
# Checks whether the move will be ineffective against the target.
def pbFailsAgainstTarget?(user,target); return false; end
def pbMoveFailedLastInRound?(user)
unmoved = false
@battle.eachBattler do |b|
next if b.index==user.index
next if @battle.choices[b.index][0]!=:UseMove && @battle.choices[b.index][0]!=:Shift
next if b.movedThisRound?
unmoved = true
break
end
if !unmoved
@battle.pbDisplay(_INTL("But it failed!"))
return true
end
return false
end
def pbMoveFailedTargetAlreadyMoved?(target)
if (@battle.choices[target.index][0]!=:UseMove &&
@battle.choices[target.index][0]!=:Shift) || target.movedThisRound?
@battle.pbDisplay(_INTL("But it failed!"))
return true
end
return false
end
def pbMoveFailedAromaVeil?(user,target,showMessage=true)
return false if @battle.moldBreaker
if target.hasActiveAbility?(:AROMAVEIL)
if showMessage
@battle.pbShowAbilitySplash(target)
if PokeBattle_SceneConstants::USE_ABILITY_SPLASH
@battle.pbDisplay(_INTL("{1} is unaffected!",target.pbThis))
else
@battle.pbDisplay(_INTL("{1} is unaffected because of its {2}!",
target.pbThis,target.abilityName))
end
@battle.pbHideAbilitySplash(target)
end
return true
end
target.eachAlly do |b|
next if !b.hasActiveAbility?(:AROMAVEIL)
if showMessage
@battle.pbShowAbilitySplash(target)
if PokeBattle_SceneConstants::USE_ABILITY_SPLASH
@battle.pbDisplay(_INTL("{1} is unaffected!",target.pbThis))
else
@battle.pbDisplay(_INTL("{1} is unaffected because of {2}'s {3}!",
target.pbThis,b.pbThis(true),b.abilityName))
end
@battle.pbHideAbilitySplash(target)
end
return true
end
return false
end
#=============================================================================
# Weaken the damage dealt (doesn't actually change a battler's HP)
#=============================================================================
def pbCheckDamageAbsorption(user,target)
# Substitute will take the damage
if target.effects[PBEffects::Substitute]>0 && !ignoresSubstitute?(user) &&
(!user || user.index!=target.index)
target.damageState.substitute = true
return
end
# Disguise will take the damage
if !@battle.moldBreaker && target.isSpecies?(:MIMIKYU) &&
target.form==0 && target.ability == :DISGUISE
target.damageState.disguise = true
return
end
end
def pbReduceDamage(user,target)
damage = target.damageState.calcDamage
# Substitute takes the damage
if target.damageState.substitute
damage = target.effects[PBEffects::Substitute] if damage>target.effects[PBEffects::Substitute]
target.damageState.hpLost = damage
target.damageState.totalHPLost += damage
return
end
# Disguise takes the damage
return if target.damageState.disguise
# Target takes the damage
if damage>=target.hp
damage = target.hp
# Survive a lethal hit with 1 HP effects
if nonLethal?(user,target)
damage -= 1
elsif target.effects[PBEffects::Endure]
target.damageState.endured = true
damage -= 1
elsif damage==target.totalhp
if target.hasActiveAbility?(:STURDY) && !@battle.moldBreaker
target.damageState.sturdy = true
damage -= 1
elsif target.hasActiveItem?(:FOCUSSASH) && target.hp==target.totalhp
target.damageState.focusSash = true
damage -= 1
elsif target.hasActiveItem?(:FOCUSBAND) && @battle.pbRandom(100)<10
target.damageState.focusBand = true
damage -= 1
end
end
end
damage = 0 if damage<0
target.damageState.hpLost = damage
target.damageState.totalHPLost += damage
end
#=============================================================================
# Change the target's HP by the amount calculated above
#=============================================================================
def pbInflictHPDamage(target)
if target.damageState.substitute
target.effects[PBEffects::Substitute] -= target.damageState.hpLost
else
target.hp -= target.damageState.hpLost
end
end
#=============================================================================
# Animate the damage dealt, including lowering the HP
#=============================================================================
# Animate being damaged and losing HP (by a move)
def pbAnimateHitAndHPLost(user,targets)
# Animate allies first, then foes
animArray = []
for side in 0...2 # side here means "allies first, then foes"
targets.each do |b|
next if b.damageState.unaffected || b.damageState.hpLost==0
next if (side==0 && b.opposes?(user)) || (side==1 && !b.opposes?(user))
oldHP = b.hp+b.damageState.hpLost
PBDebug.log("[Move damage] #{b.pbThis} lost #{b.damageState.hpLost} HP (#{oldHP}=>#{b.hp})")
effectiveness = 0
if Effectiveness.resistant?(b.damageState.typeMod); effectiveness = 1
elsif Effectiveness.super_effective?(b.damageState.typeMod); effectiveness = 2
end
animArray.push([b,oldHP,effectiveness])
end
if animArray.length>0
@battle.scene.pbHitAndHPLossAnimation(animArray)
animArray.clear
end
end
end
#=============================================================================
# Messages upon being hit
#=============================================================================
def pbEffectivenessMessage(user,target,numTargets=1)
return if target.damageState.disguise
if Effectiveness.super_effective?(target.damageState.typeMod)
if numTargets>1
@battle.pbDisplay(_INTL("It's super effective on {1}!",target.pbThis(true)))
else
@battle.pbDisplay(_INTL("It's super effective!"))
end
elsif Effectiveness.not_very_effective?(target.damageState.typeMod)
if numTargets>1
@battle.pbDisplay(_INTL("It's not very effective on {1}...",target.pbThis(true)))
else
@battle.pbDisplay(_INTL("It's not very effective..."))
end
end
end
def pbHitEffectivenessMessages(user,target,numTargets=1)
return if target.damageState.disguise
if target.damageState.substitute
@battle.pbDisplay(_INTL("The substitute took damage for {1}!",target.pbThis(true)))
end
if target.damageState.critical
if numTargets>1
@battle.pbDisplay(_INTL("A critical hit on {1}!",target.pbThis(true)))
else
@battle.pbDisplay(_INTL("A critical hit!"))
end
end
# Effectiveness message, for moves with 1 hit
if !multiHitMove? && user.effects[PBEffects::ParentalBond]==0
pbEffectivenessMessage(user,target,numTargets)
end
if target.damageState.substitute && target.effects[PBEffects::Substitute]==0
target.effects[PBEffects::Substitute] = 0
@battle.pbDisplay(_INTL("{1}'s substitute faded!",target.pbThis))
end
end
def pbEndureKOMessage(target)
if target.damageState.disguise
@battle.pbShowAbilitySplash(target)
if PokeBattle_SceneConstants::USE_ABILITY_SPLASH
@battle.pbDisplay(_INTL("Its disguise served it as a decoy!"))
else
@battle.pbDisplay(_INTL("{1}'s disguise served it as a decoy!",target.pbThis))
end
@battle.pbHideAbilitySplash(target)
target.pbChangeForm(1,_INTL("{1}'s disguise was busted!",target.pbThis))
elsif target.damageState.endured
@battle.pbDisplay(_INTL("{1} endured the hit!",target.pbThis))
elsif target.damageState.sturdy
@battle.pbShowAbilitySplash(target)
if PokeBattle_SceneConstants::USE_ABILITY_SPLASH
@battle.pbDisplay(_INTL("{1} endured the hit!",target.pbThis))
else
@battle.pbDisplay(_INTL("{1} hung on with Sturdy!",target.pbThis))
end
@battle.pbHideAbilitySplash(target)
elsif target.damageState.focusSash
@battle.pbCommonAnimation("UseItem",target)
@battle.pbDisplay(_INTL("{1} hung on using its Focus Sash!",target.pbThis))
target.pbConsumeItem
elsif target.damageState.focusBand
@battle.pbCommonAnimation("UseItem",target)
@battle.pbDisplay(_INTL("{1} hung on using its Focus Band!",target.pbThis))
end
end
# Used by Counter/Mirror Coat/Metal Burst/Revenge/Focus Punch/Bide/Assurance.
def pbRecordDamageLost(user,target)
damage = target.damageState.hpLost
# NOTE: In Gen 3 where a move's category depends on its type, Hidden Power
# is for some reason countered by Counter rather than Mirror Coat,
# regardless of its calculated type. Hence the following two lines of
# code.
moveType = nil
moveType = :NORMAL if @function=="090" # 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
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.tookDamage = true if damage>0 # For Assurance
target.lastAttacker.push(user.index) # For Revenge
if target.opposes?(user)
target.lastHPLostFromFoe = damage # For Metal Burst
target.lastFoeAttacker.push(user.index) # For Metal Burst
end
end
end

View File

@@ -0,0 +1,491 @@
class PokeBattle_Move
#=============================================================================
# Move's type calculation
#=============================================================================
def pbBaseType(user)
ret = @type
if ret && user.abilityActive?
ret = BattleHandlers.triggerMoveBaseTypeModifierAbility(user.ability,user,self,ret)
end
return ret
end
def pbCalcType(user)
@powerBoost = false
ret = pbBaseType(user)
if ret && GameData::Type.exists?(:ELECTRIC)
if @battle.field.effects[PBEffects::IonDeluge] && ret == :NORMAL
ret = :ELECTRIC
@powerBoost = false
end
if user.effects[PBEffects::Electrify]
ret = :ELECTRIC
@powerBoost = false
end
end
return ret
end
#=============================================================================
# Type effectiveness calculation
#=============================================================================
def pbCalcTypeModSingle(moveType,defType,user,target)
ret = Effectiveness.calculate_one(moveType, defType)
# Ring Target
if target.hasActiveItem?(:RINGTARGET)
ret = Effectiveness::NORMAL_EFFECTIVE_ONE if Effectiveness.ineffective_type?(moveType, defType)
end
# Foresight
if user.hasActiveAbility?(:SCRAPPY) || target.effects[PBEffects::Foresight]
ret = Effectiveness::NORMAL_EFFECTIVE_ONE if defType == :GHOST &&
Effectiveness.ineffective_type?(moveType, defType)
end
# Miracle Eye
if target.effects[PBEffects::MiracleEye]
ret = Effectiveness::NORMAL_EFFECTIVE_ONE if defType == :DARK &&
Effectiveness.ineffective_type?(moveType, defType)
end
# Delta Stream's weather
if @battle.pbWeather == :StrongWinds
ret = Effectiveness::NORMAL_EFFECTIVE_ONE if defType == :FLYING &&
Effectiveness.super_effective_type?(moveType, defType)
end
# Grounded Flying-type Pokémon become susceptible to Ground moves
if !target.airborne?
ret = Effectiveness::NORMAL_EFFECTIVE_ONE if defType == :FLYING && moveType == :GROUND
end
return ret
end
def pbCalcTypeMod(moveType,user,target)
return Effectiveness::NORMAL_EFFECTIVE if !moveType
return Effectiveness::NORMAL_EFFECTIVE if moveType == :GROUND &&
target.pbHasType?(:FLYING) && target.hasActiveItem?(:IRONBALL)
# Determine types
tTypes = target.pbTypes(true)
# Get effectivenesses
typeMods = [Effectiveness::NORMAL_EFFECTIVE_ONE] * 3 # 3 types max
if moveType == :SHADOW
if target.shadowPokemon?
typeMods[0] = Effectiveness::NOT_VERY_EFFECTIVE_ONE
else
typeMods[0] = Effectiveness::SUPER_EFFECTIVE_ONE
end
else
tTypes.each_with_index do |type,i|
typeMods[i] = pbCalcTypeModSingle(moveType,type,user,target)
end
end
# Multiply all effectivenesses together
ret = 1
typeMods.each { |m| ret *= m }
return ret
end
#=============================================================================
# Accuracy check
#=============================================================================
def pbBaseAccuracy(user,target); return @accuracy; end
# Accuracy calculations for one-hit KO moves and "always hit" moves are
# handled elsewhere.
def pbAccuracyCheck(user,target)
# "Always hit" effects and "always hit" accuracy
return true if target.effects[PBEffects::Telekinesis]>0
return true if target.effects[PBEffects::Minimize] && tramplesMinimize?(1)
baseAcc = pbBaseAccuracy(user,target)
return true if baseAcc==0
# Calculate all multiplier 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)
# 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]
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
# Calculation
return @battle.pbRandom(100) < modifiers[:base_accuracy] * accuracy / evasion
end
def pbCalcAccuracyModifiers(user,target,modifiers)
# Ability effects that alter accuracy calculation
if user.abilityActive?
BattleHandlers.triggerAccuracyCalcUserAbility(user.ability,
modifiers,user,target,self,@calcType)
end
user.eachAlly do |b|
next if !b.abilityActive?
BattleHandlers.triggerAccuracyCalcUserAllyAbility(b.ability,
modifiers,user,target,self,@calcType)
end
if target.abilityActive? && !@battle.moldBreaker
BattleHandlers.triggerAccuracyCalcTargetAbility(target.ability,
modifiers,user,target,self,@calcType)
end
# Item effects that alter accuracy calculation
if user.itemActive?
BattleHandlers.triggerAccuracyCalcUserItem(user.item,
modifiers,user,target,self,@calcType)
end
if target.itemActive?
BattleHandlers.triggerAccuracyCalcTargetItem(target.item,
modifiers,user,target,self,@calcType)
end
# Other effects, inc. ones that set accuracy_multiplier or evasion_stage to
# specific values
if @battle.field.effects[PBEffects::Gravity] > 0
modifiers[:accuracy_multiplier] *= 5 / 3.0
end
if user.effects[PBEffects::MicleBerry]
user.effects[PBEffects::MicleBerry] = false
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
#=============================================================================
# Critical hit check
#=============================================================================
# Return values:
# -1: Never a critical hit.
# 0: Calculate normally.
# 1: Always a critical hit.
def pbCritialOverride(user,target); return 0; end
# Returns whether the move will be a critical hit.
def pbIsCritical?(user,target)
return false if target.pbOwnSide.effects[PBEffects::LuckyChant]>0
# Set up the critical hit ratios
ratios = (Settings::NEW_CRITICAL_HIT_RATE_MECHANICS) ? [24,8,2,1] : [16,8,4,3,2]
c = 0
# Ability effects that alter critical hit rate
if c>=0 && user.abilityActive?
c = BattleHandlers.triggerCriticalCalcUserAbility(user.ability,user,target,c)
end
if c>=0 && target.abilityActive? && !@battle.moldBreaker
c = BattleHandlers.triggerCriticalCalcTargetAbility(target.ability,user,target,c)
end
# Item effects that alter critical hit rate
if c>=0 && user.itemActive?
c = BattleHandlers.triggerCriticalCalcUserItem(user.item,user,target,c)
end
if c>=0 && target.itemActive?
c = BattleHandlers.triggerCriticalCalcTargetItem(target.item,user,target,c)
end
return false if c<0
# Move-specific "always/never a critical hit" effects
case pbCritialOverride(user,target)
when 1 then return true
when -1 then return false
end
# Other effects
return true if c>50 # Merciless
return true if user.effects[PBEffects::LaserFocus]>0
c += 1 if highCriticalRate?
c += user.effects[PBEffects::FocusEnergy]
c += 1 if user.inHyperMode? && @type == :SHADOW
c = ratios.length-1 if c>=ratios.length
# Calculation
return @battle.pbRandom(ratios[c])==0
end
#=============================================================================
# Damage calculation
#=============================================================================
def pbBaseDamage(baseDmg,user,target); return baseDmg; end
def pbBaseDamageMultiplier(damageMult,user,target); return damageMult; end
def pbModifyDamage(damageMult,user,target); return damageMult; end
def pbGetAttackStats(user,target)
if specialMove?
return user.spatk, user.stages[:SPECIAL_ATTACK]+6
end
return user.attack, user.stages[:ATTACK]+6
end
def pbGetDefenseStats(user,target)
if specialMove?
return target.spdef, target.stages[:SPECIAL_DEFENSE]+6
end
return target.defense, target.stages[:DEFENSE]+6
end
def pbCalcDamage(user,target,numTargets=1)
return if statusMove?
if target.damageState.disguise
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]
# Get the move's type
type = @calcType # nil is treated as physical
# Calculate whether this hit deals critical damage
target.damageState.critical = pbIsCritical?(user,target)
# Calcuate base power of move
baseDmg = pbBaseDamage(@baseDamage,user,target)
# Calculate user's attack stat
atk, atkStage = pbGetAttackStats(user,target)
if !target.hasActiveAbility?(:UNAWARE) || @battle.moldBreaker
atkStage = 6 if target.damageState.critical && atkStage<6
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
defense = (defense.to_f*stageMul[defStage]/stageDiv[defStage]).floor
end
# Calculate all multiplier effects
multipliers = {
:base_damage_multiplier => 1.0,
:attack_multiplier => 1.0,
:defense_multiplier => 1.0,
:final_damage_multiplier => 1.0
}
pbCalcDamageMultipliers(user,target,numTargets,type,baseDmg,multipliers)
# 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
target.damageState.calcDamage = damage
end
def pbCalcDamageMultipliers(user,target,numTargets,type,baseDmg,multipliers)
# Global abilities
if (@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
# Ability effects that alter damage
if user.abilityActive?
BattleHandlers.triggerDamageCalcUserAbility(user.ability,
user,target,self,multipliers,baseDmg,type)
end
if !@battle.moldBreaker
# NOTE: It's odd that the user's Mold Breaker prevents its partner's
# beneficial abilities (i.e. Flower Gift boosting Atk), but that's
# how it works.
user.eachAlly do |b|
next if !b.abilityActive?
BattleHandlers.triggerDamageCalcUserAllyAbility(b.ability,
user,target,self,multipliers,baseDmg,type)
end
if target.abilityActive?
BattleHandlers.triggerDamageCalcTargetAbility(target.ability,
user,target,self,multipliers,baseDmg,type) if !@battle.moldBreaker
BattleHandlers.triggerDamageCalcTargetAbilityNonIgnorable(target.ability,
user,target,self,multipliers,baseDmg,type)
end
target.eachAlly do |b|
next if !b.abilityActive?
BattleHandlers.triggerDamageCalcTargetAllyAbility(b.ability,
user,target,self,multipliers,baseDmg,type)
end
end
# Item effects that alter damage
if user.itemActive?
BattleHandlers.triggerDamageCalcUserItem(user.item,
user,target,self,multipliers,baseDmg,type)
end
if target.itemActive?
BattleHandlers.triggerDamageCalcTargetItem(target.item,
user,target,self,multipliers,baseDmg,type)
end
# Parental Bond's second attack
if user.effects[PBEffects::ParentalBond]==1
multipliers[:base_damage_multiplier] /= 4
end
# Other
if user.effects[PBEffects::MeFirst]
multipliers[:base_damage_multiplier] *= 1.5
end
if user.effects[PBEffects::HelpingHand] && !self.is_a?(PokeBattle_Confusion)
multipliers[:base_damage_multiplier] *= 1.5
end
if user.effects[PBEffects::Charge]>0 && type == :ELECTRIC
multipliers[:base_damage_multiplier] *= 2
end
# Mud Sport
if type == :ELECTRIC
@battle.eachBattler do |b|
next if !b.effects[PBEffects::MudSport]
multipliers[:base_damage_multiplier] /= 3
break
end
if @battle.field.effects[PBEffects::MudSportField]>0
multipliers[:base_damage_multiplier] /= 3
end
end
# Water Sport
if type == :FIRE
@battle.eachBattler do |b|
next if !b.effects[PBEffects::WaterSport]
multipliers[:base_damage_multiplier] /= 3
break
end
if @battle.field.effects[PBEffects::WaterSportField]>0
multipliers[:base_damage_multiplier] /= 3
end
end
# Terrain moves
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
# Badge multipliers
if @battle.internalBattle
if user.pbOwnedByPlayer?
if physicalMove? && @battle.pbPlayer.badge_count >= Settings::NUM_BADGES_BOOST_ATTACK
multipliers[:attack_multiplier] *= 1.1
elsif specialMove? && @battle.pbPlayer.badge_count >= Settings::NUM_BADGES_BOOST_SPATK
multipliers[:attack_multiplier] *= 1.1
end
end
if target.pbOwnedByPlayer?
if physicalMove? && @battle.pbPlayer.badge_count >= Settings::NUM_BADGES_BOOST_DEFENSE
multipliers[:defense_multiplier] *= 1.1
elsif specialMove? && @battle.pbPlayer.badge_count >= Settings::NUM_BADGES_BOOST_SPDEF
multipliers[:defense_multiplier] *= 1.1
end
end
end
# Multi-targeting attacks
if numTargets>1
multipliers[:final_damage_multiplier] *= 0.75
end
# Weather
case @battle.pbWeather
when :Sun, :HarshSun
if type == :FIRE
multipliers[:final_damage_multiplier] *= 1.5
elsif type == :WATER
multipliers[:final_damage_multiplier] /= 2
end
when :Rain, :HeavyRain
if type == :FIRE
multipliers[:final_damage_multiplier] /= 2
elsif type == :WATER
multipliers[:final_damage_multiplier] *= 1.5
end
when :Sandstorm
if target.pbHasType?(:ROCK) && specialMove? && @function != "122" # Psyshock
multipliers[:defense_multiplier] *= 1.5
end
end
# Critical hits
if target.damageState.critical
if Settings::NEW_CRITICAL_HIT_RATE_MECHANICS
multipliers[:final_damage_multiplier] *= 1.5
else
multipliers[:final_damage_multiplier] *= 2
end
end
# Random variance
if !self.is_a?(PokeBattle_Confusion)
random = 85+@battle.pbRandom(16)
multipliers[:final_damage_multiplier] *= random / 100.0
end
# STAB
if type && user.pbHasType?(type)
if user.hasActiveAbility?(:ADAPTABILITY)
multipliers[:final_damage_multiplier] *= 2
else
multipliers[:final_damage_multiplier] *= 1.5
end
end
# Type effectiveness
multipliers[:final_damage_multiplier] *= target.damageState.typeMod.to_f / Effectiveness::NORMAL_EFFECTIVE
# Burn
if user.status == :BURN && physicalMove? && damageReducedByBurn? &&
!user.hasActiveAbility?(:GUTS)
multipliers[:final_damage_multiplier] /= 2
end
# Aurora Veil, Reflect, Light Screen
if !ignoresReflect? && !target.damageState.critical &&
!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 && physicalMove?
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 && specialMove?
if @battle.pbSideBattlerCount(target) > 1
multipliers[:final_damage_multiplier] *= 2 / 3.0
else
multipliers[:final_damage_multiplier] /= 2
end
end
end
# Minimize
if target.effects[PBEffects::Minimize] && tramplesMinimize?(2)
multipliers[:final_damage_multiplier] *= 2
end
# Move-specific base damage modifiers
multipliers[:base_damage_multiplier] = pbBaseDamageMultiplier(multipliers[:base_damage_multiplier], user, target)
# Move-specific final damage modifiers
multipliers[:final_damage_multiplier] = pbModifyDamage(multipliers[:final_damage_multiplier], user, target)
end
#=============================================================================
# Additional effect chance
#=============================================================================
def pbAdditionalEffectChance(user,target,effectChance=0)
return 0 if target.hasActiveAbility?(:SHIELDDUST) && !@battle.moldBreaker
ret = (effectChance>0) ? effectChance : @addlEffect
if Settings::MECHANICS_GENERATION >= 6 || @function != "0A4" # Secret Power
ret *= 2 if user.hasActiveAbility?(:SERENEGRACE) ||
user.pbOwnSide.effects[PBEffects::Rainbow]>0
end
ret = 100 if $DEBUG && Input.press?(Input::CTRL)
return ret
end
# NOTE: Flinching caused by a move's effect is applied in that move's code,
# not here.
def pbFlinchChance(user,target)
return 0 if flinchingMove?
return 0 if target.hasActiveAbility?(:SHIELDDUST) && !@battle.moldBreaker
ret = 0
if user.hasActiveAbility?(:STENCH,true)
ret = 10
elsif user.hasActiveItem?([:KINGSROCK,:RAZORFANG],true)
ret = 10
end
ret *= 2 if user.hasActiveAbility?(:SERENEGRACE) ||
user.pbOwnSide.effects[PBEffects::Rainbow]>0
return ret
end
end

View File

@@ -0,0 +1,716 @@
#===============================================================================
# Superclass that handles moves using a non-existent function code.
# Damaging moves just do damage with no additional effect.
# Status moves always fail.
#===============================================================================
class PokeBattle_UnimplementedMove < PokeBattle_Move
def pbMoveFailed?(user,targets)
if statusMove?
@battle.pbDisplay(_INTL("But it failed!"))
return true
end
return false
end
end
#===============================================================================
# Pseudomove for confusion damage.
#===============================================================================
class PokeBattle_Confusion < PokeBattle_Move
def initialize(battle,move)
@battle = battle
@realMove = move
@id = 0
@name = ""
@function = "000"
@baseDamage = 40
@type = -1
@category = 0
@accuracy = 100
@pp = -1
@target = 0
@priority = 0
@flags = ""
@addlEffect = 0
@calcType = nil
@powerBoost = false
@snatched = false
end
def physicalMove?(thisType=nil); return true; end
def specialMove?(thisType=nil); return false; end
def pbCritialOverride(user,target); return -1; end
end
#===============================================================================
# Implements the move Struggle.
# For cases where the real move named Struggle is not defined.
#===============================================================================
class PokeBattle_Struggle < PokeBattle_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")
@function = "002"
@baseDamage = 50
@type = -1
@category = 0
@accuracy = 0
@pp = -1
@target = 0
@priority = 0
@flags = ""
@addlEffect = 0
@calcType = nil
@powerBoost = false
@snatched = false
end
def physicalMove?(thisType=nil); return true; end
def specialMove?(thisType=nil); return false; end
def pbEffectAfterAllHits(user,target)
return if target.damageState.unaffected
user.pbReduceHP((user.totalhp/4.0).round,false)
@battle.pbDisplay(_INTL("{1} is damaged by recoil!",user.pbThis))
user.pbItemHPHealCheck
end
end
#===============================================================================
# Generic status problem-inflicting classes.
#===============================================================================
class PokeBattle_SleepMove < PokeBattle_Move
def pbFailsAgainstTarget?(user,target)
return false if damagingMove?
return !target.pbCanSleep?(user,true,self)
end
def pbEffectAgainstTarget(user,target)
return if damagingMove?
target.pbSleep
end
def pbAdditionalEffect(user,target)
return if target.damageState.substitute
target.pbSleep if target.pbCanSleep?(user,false,self)
end
end
class PokeBattle_PoisonMove < PokeBattle_Move
def initialize(battle,move)
super
@toxic = false
end
def pbFailsAgainstTarget?(user,target)
return false if damagingMove?
return !target.pbCanPoison?(user,true,self)
end
def pbEffectAgainstTarget(user,target)
return if damagingMove?
target.pbPoison(user,nil,@toxic)
end
def pbAdditionalEffect(user,target)
return if target.damageState.substitute
target.pbPoison(user,nil,@toxic) if target.pbCanPoison?(user,false,self)
end
end
class PokeBattle_ParalysisMove < PokeBattle_Move
def pbFailsAgainstTarget?(user,target)
return false if damagingMove?
return !target.pbCanParalyze?(user,true,self)
end
def pbEffectAgainstTarget(user,target)
return if damagingMove?
target.pbParalyze(user)
end
def pbAdditionalEffect(user,target)
return if target.damageState.substitute
target.pbParalyze(user) if target.pbCanParalyze?(user,false,self)
end
end
class PokeBattle_BurnMove < PokeBattle_Move
def pbFailsAgainstTarget?(user,target)
return false if damagingMove?
return !target.pbCanBurn?(user,true,self)
end
def pbEffectAgainstTarget(user,target)
return if damagingMove?
target.pbBurn(user)
end
def pbAdditionalEffect(user,target)
return if target.damageState.substitute
target.pbBurn(user) if target.pbCanBurn?(user,false,self)
end
end
class PokeBattle_FreezeMove < PokeBattle_Move
def pbFailsAgainstTarget?(user,target)
return false if damagingMove?
return !target.pbCanFreeze?(user,true,self)
end
def pbEffectAgainstTarget(user,target)
return if damagingMove?
target.pbFreeze
end
def pbAdditionalEffect(user,target)
return if target.damageState.substitute
target.pbFreeze if target.pbCanFreeze?(user,false,self)
end
end
#===============================================================================
# Other problem-causing classes.
#===============================================================================
class PokeBattle_FlinchMove < PokeBattle_Move
def flinchingMove?; return true; end
def pbEffectAgainstTarget(user,target)
return if damagingMove?
target.pbFlinch(user)
end
def pbAdditionalEffect(user,target)
return if target.damageState.substitute
target.pbFlinch(user)
end
end
class PokeBattle_ConfuseMove < PokeBattle_Move
def pbFailsAgainstTarget?(user,target)
return false if damagingMove?
return !target.pbCanConfuse?(user,true,self)
end
def pbEffectAgainstTarget(user,target)
return if damagingMove?
target.pbConfuse
end
def pbAdditionalEffect(user,target)
return if target.damageState.substitute
return if !target.pbCanConfuse?(user,false,self)
target.pbConfuse
end
end
#===============================================================================
# Generic user's stat increase/decrease classes.
#===============================================================================
class PokeBattle_StatUpMove < PokeBattle_Move
def pbMoveFailed?(user,targets)
return false if damagingMove?
return !user.pbCanRaiseStatStage?(@statUp[0],user,self,true)
end
def pbEffectGeneral(user)
return if damagingMove?
user.pbRaiseStatStage(@statUp[0],@statUp[1],user)
end
def pbAdditionalEffect(user,target)
if user.pbCanRaiseStatStage?(@statUp[0],user,self)
user.pbRaiseStatStage(@statUp[0],@statUp[1],user)
end
end
end
class PokeBattle_MultiStatUpMove < PokeBattle_Move
def pbMoveFailed?(user,targets)
return false if damagingMove?
failed = true
for i in 0...@statUp.length/2
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
return false
end
def pbEffectGeneral(user)
return if damagingMove?
showAnim = true
for i in 0...@statUp.length/2
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
def pbAdditionalEffect(user,target)
showAnim = true
for i in 0...@statUp.length/2
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
end
class PokeBattle_StatDownMove < PokeBattle_Move
def pbEffectWhenDealingDamage(user,target)
return if @battle.pbAllFainted?(target.idxOwnSide)
showAnim = true
for i in 0...@statDown.length/2
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
end
end
#===============================================================================
# Generic target's stat increase/decrease classes.
#===============================================================================
class PokeBattle_TargetStatDownMove < PokeBattle_Move
def pbFailsAgainstTarget?(user,target)
return false if damagingMove?
return !target.pbCanLowerStatStage?(@statDown[0],user,self,true)
end
def pbEffectAgainstTarget(user,target)
return if damagingMove?
target.pbLowerStatStage(@statDown[0],@statDown[1],user)
end
def pbAdditionalEffect(user,target)
return if target.damageState.substitute
return if !target.pbCanLowerStatStage?(@statDown[0],user,self)
target.pbLowerStatStage(@statDown[0],@statDown[1],user)
end
end
class PokeBattle_TargetMultiStatDownMove < PokeBattle_Move
def pbFailsAgainstTarget?(user,target)
return false if damagingMove?
failed = true
for i in 0...@statDown.length/2
next if !target.pbCanLowerStatStage?(@statDown[i*2],user,self)
failed = false
break
end
if failed
# NOTE: It's a bit of a faff to make sure the appropriate failure message
# is shown here, I know.
canLower = false
if target.hasActiveAbility?(:CONTRARY) && !@battle.moldBreaker
for i in 0...@statDown.length/2
next if target.statStageAtMax?(@statDown[i*2])
canLower = true
break
end
@battle.pbDisplay(_INTL("{1}'s stats won't go any higher!",user.pbThis)) if !canLower
else
for i in 0...@statDown.length/2
next if target.statStageAtMin?(@statDown[i*2])
canLower = true
break
end
@battle.pbDisplay(_INTL("{1}'s stats won't go any lower!",user.pbThis)) if !canLower
end
if canLower
target.pbCanLowerStatStage?(@statDown[0],user,self,true)
end
return true
end
return false
end
def pbEffectAgainstTarget(user,target)
return if damagingMove?
showAnim = true
for i in 0...@statDown.length/2
next if !target.pbCanLowerStatStage?(@statDown[i*2],user,self)
if target.pbLowerStatStage(@statDown[i*2],@statDown[i*2+1],user,showAnim)
showAnim = false
end
end
end
def pbAdditionalEffect(user,target)
return if target.damageState.substitute
showAnim = true
for i in 0...@statDown.length/2
next if !target.pbCanLowerStatStage?(@statDown[i*2],user,self)
if target.pbLowerStatStage(@statDown[i*2],@statDown[i*2+1],user,showAnim)
showAnim = false
end
end
end
end
#===============================================================================
# Fixed damage-inflicting move.
#===============================================================================
class PokeBattle_FixedDamageMove < PokeBattle_Move
def pbFixedDamage(user,target); return 1; end
def pbCalcDamage(user,target,numTargets=1)
target.damageState.critical = false
target.damageState.calcDamage = pbFixedDamage(user,target)
target.damageState.calcDamage = 1 if target.damageState.calcDamage<1
end
end
#===============================================================================
# Two turn move.
#===============================================================================
class PokeBattle_TwoTurnMove < PokeBattle_Move
def chargingTurnMove?; return true; end
# user.effects[PBEffects::TwoTurnAttack] is set to the move's ID if this
# method returns true, or nil if false.
# Non-nil means the charging turn. nil means the attacking turn.
def pbIsChargingTurn?(user)
@powerHerb = false
@chargingTurn = false # Assume damaging turn by default
@damagingTurn = true
# 0 at start of charging turn, move's ID at start of damaging turn
if !user.effects[PBEffects::TwoTurnAttack]
@powerHerb = user.hasActiveItem?(:POWERHERB)
@chargingTurn = true
@damagingTurn = @powerHerb
end
return !@damagingTurn # Deliberately not "return @chargingTurn"
end
def pbDamagingMove? # Stops damage being dealt in the first (charging) turn
return false if !@damagingTurn
return super
end
def pbAccuracyCheck(user,target)
return true if !@damagingTurn
return super
end
def pbInitialEffect(user,targets,hitNum)
pbChargingTurnMessage(user,targets) if @chargingTurn
if @chargingTurn && @damagingTurn # Move only takes one turn to use
pbShowAnimation(@id,user,targets,1) # Charging anim
targets.each { |b| pbChargingTurnEffect(user,b) }
if @powerHerb
# Moves that would make the user semi-invulnerable will hide the user
# after the charging animation, so the "UseItem" animation shouldn't show
# for it
if !["0C9","0CA","0CB","0CC","0CD","0CE","14D"].include?(@function)
@battle.pbCommonAnimation("UseItem",user)
end
@battle.pbDisplay(_INTL("{1} became fully charged due to its Power Herb!",user.pbThis))
user.pbConsumeItem
end
end
pbAttackingTurnMessage(user,targets) if @damagingTurn
end
def pbChargingTurnMessage(user,targets)
@battle.pbDisplay(_INTL("{1} began charging up!",user.pbThis))
end
def pbAttackingTurnMessage(user,targets)
end
def pbChargingTurnEffect(user,target)
# Skull Bash/Sky Drop are the only two-turn moves with an effect here, and
# the latter just records the target is being Sky Dropped
end
def pbAttackingTurnEffect(user,target)
end
def pbEffectAgainstTarget(user,target)
if @damagingTurn; pbAttackingTurnEffect(user,target)
elsif @chargingTurn; pbChargingTurnEffect(user,target)
end
end
def pbShowAnimation(id,user,targets,hitNum=0,showAnimation=true)
hitNum = 1 if @chargingTurn && !@damagingTurn # Charging anim
super
end
end
#===============================================================================
# Healing move.
#===============================================================================
class PokeBattle_HealingMove < PokeBattle_Move
def healingMove?; return true; end
def pbHealAmount(user); return 1; end
def pbMoveFailed?(user,targets)
if user.hp==user.totalhp
@battle.pbDisplay(_INTL("{1}'s HP is full!",user.pbThis))
return true
end
return false
end
def pbEffectGeneral(user)
amt = pbHealAmount(user)
user.pbRecoverHP(amt)
@battle.pbDisplay(_INTL("{1}'s HP was restored.",user.pbThis))
end
end
#===============================================================================
# Recoil move.
#===============================================================================
class PokeBattle_RecoilMove < PokeBattle_Move
def recoilMove?; return true; end
def pbRecoilDamage(user,target); return 1; end
def pbEffectAfterAllHits(user,target)
return if target.damageState.unaffected
return if !user.takesIndirectDamage?
return if user.hasActiveAbility?(:ROCKHEAD)
amt = pbRecoilDamage(user,target)
amt = 1 if amt<1
user.pbReduceHP(amt,false)
@battle.pbDisplay(_INTL("{1} is damaged by recoil!",user.pbThis))
user.pbItemHPHealCheck
end
end
#===============================================================================
# Protect move.
#===============================================================================
class PokeBattle_ProtectMove < PokeBattle_Move
def initialize(battle,move)
super
@sidedEffect = false
end
def pbChangeUsageCounters(user,specialUsage)
oldVal = user.effects[PBEffects::ProtectRate]
super
user.effects[PBEffects::ProtectRate] = oldVal
end
def pbMoveFailed?(user,targets)
if @sidedEffect
if user.pbOwnSide.effects[@effect]
user.effects[PBEffects::ProtectRate] = 1
@battle.pbDisplay(_INTL("But it failed!"))
return true
end
elsif user.effects[@effect]
user.effects[PBEffects::ProtectRate] = 1
@battle.pbDisplay(_INTL("But it failed!"))
return true
end
if (!@sidedEffect || Settings::MECHANICS_GENERATION <= 5) &&
user.effects[PBEffects::ProtectRate]>1 &&
@battle.pbRandom(user.effects[PBEffects::ProtectRate])!=0
user.effects[PBEffects::ProtectRate] = 1
@battle.pbDisplay(_INTL("But it failed!"))
return true
end
if pbMoveFailedLastInRound?(user)
user.effects[PBEffects::ProtectRate] = 1
return true
end
return false
end
def pbEffectGeneral(user)
if @sidedEffect
user.pbOwnSide.effects[@effect] = true
else
user.effects[@effect] = true
end
user.effects[PBEffects::ProtectRate] *= (Settings::MECHANICS_GENERATION >= 6) ? 3 : 2
pbProtectMessage(user)
end
def pbProtectMessage(user)
if @sidedEffect
@battle.pbDisplay(_INTL("{1} protected {2}!",@name,user.pbTeam(true)))
else
@battle.pbDisplay(_INTL("{1} protected itself!",user.pbThis))
end
end
end
#===============================================================================
# Weather-inducing move.
#===============================================================================
class PokeBattle_WeatherMove < PokeBattle_Move
def initialize(battle,move)
super
@weatherType = :None
end
def pbMoveFailed?(user,targets)
case @battle.field.weather
when :HarshSun
@battle.pbDisplay(_INTL("The extremely harsh sunlight was not lessened at all!"))
return true
when :HeavyRain
@battle.pbDisplay(_INTL("There is no relief from this heavy rain!"))
return true
when :StrongWinds
@battle.pbDisplay(_INTL("The mysterious air current blows on regardless!"))
return true
when @weatherType
@battle.pbDisplay(_INTL("But it failed!"))
return true
end
return false
end
def pbEffectGeneral(user)
@battle.pbStartWeather(user,@weatherType,true,false)
end
end
#===============================================================================
# Pledge move.
#===============================================================================
class PokeBattle_PledgeMove < PokeBattle_Move
def pbOnStartUse(user,targets)
@pledgeSetup = false; @pledgeCombo = false; @pledgeOtherUser = nil
@comboEffect = nil; @overrideType = nil; @overrideAnim = nil
# Check whether this is the use of a combo move
@combos.each do |i|
next if i[0]!=user.effects[PBEffects::FirstPledge]
@battle.pbDisplay(_INTL("The two moves have become one! It's a combined move!"))
@pledgeCombo = true
@comboEffect = i[1]; @overrideType = i[2]; @overrideAnim = i[3]
@overrideType = nil if !GameData::Type.exists?(@overrideType)
break
end
return if @pledgeCombo
# Check whether this is the setup of a combo move
user.eachAlly do |b|
next if @battle.choices[b.index][0]!=:UseMove || b.movedThisRound?
move = @battle.choices[b.index][2]
next if !move
@combos.each do |i|
next if i[0]!=move.function
@pledgeSetup = true
@pledgeOtherUser = b
break
end
break if @pledgeSetup
end
end
def pbDamagingMove?
return false if @pledgeSetup
return super
end
def pbBaseType(user)
return @overrideType if @overrideType!=nil
return super
end
def pbBaseDamage(baseDmg,user,target)
baseDmg *= 2 if @pledgeCombo
return baseDmg
end
def pbEffectGeneral(user)
user.effects[PBEffects::FirstPledge] = 0
return if !@pledgeSetup
@battle.pbDisplay(_INTL("{1} is waiting for {2}'s move...",
user.pbThis,@pledgeOtherUser.pbThis(true)))
@pledgeOtherUser.effects[PBEffects::FirstPledge] = @function
@pledgeOtherUser.effects[PBEffects::MoveNext] = true
user.lastMoveFailed = true # Treated as a failure for Stomping Tantrum
end
def pbEffectAfterAllHits(user,target)
return if !@pledgeCombo
msg = nil; animName = nil
case @comboEffect
when :SeaOfFire # Grass + Fire
if user.pbOpposingSide.effects[PBEffects::SeaOfFire]==0
user.pbOpposingSide.effects[PBEffects::SeaOfFire] = 4
msg = _INTL("A sea of fire enveloped {1}!",user.pbOpposingTeam(true))
animName = (user.opposes?) ? "SeaOfFire" : "SeaOfFireOpp"
end
when :Rainbow # Fire + Water
if user.pbOwnSide.effects[PBEffects::Rainbow]==0
user.pbOwnSide.effects[PBEffects::Rainbow] = 4
msg = _INTL("A rainbow appeared in the sky on {1}'s side!",user.pbTeam(true))
animName = (user.opposes?) ? "RainbowOpp" : "Rainbow"
end
when :Swamp # Water + Grass
if user.pbOpposingSide.effects[PBEffects::Swamp]==0
user.pbOpposingSide.effects[PBEffects::Swamp] = 4
msg = _INTL("A swamp enveloped {1}!",user.pbOpposingTeam(true))
animName = (user.opposes?) ? "Swamp" : "SwampOpp"
end
end
@battle.pbDisplay(msg) if msg
@battle.pbCommonAnimation(animName) if animName
end
def pbShowAnimation(id,user,targets,hitNum=0,showAnimation=true)
return if @pledgeSetup # No animation for setting up
id = @overrideAnim if @overrideAnim
return super
end
end

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,214 @@
module PokeBattle_BattleCommon
#=============================================================================
# Store caught Pokémon
#=============================================================================
def pbStorePokemon(pkmn)
# Nickname the Pokémon (unless it's a Shadow Pokémon)
if !pkmn.shadowPokemon?
if pbDisplayConfirm(_INTL("Would you like to give a nickname to {1}?", pkmn.name))
nickname = @scene.pbNameEntry(_INTL("{1}'s nickname?", pkmn.speciesName), pkmn)
pkmn.name = nickname
end
end
# Store the Pokémon
currentBox = @peer.pbCurrentBox
storedBox = @peer.pbStorePokemon(pbPlayer,pkmn)
if storedBox<0
pbDisplayPaused(_INTL("{1} has been added to your party.",pkmn.name))
@initialItems[0][pbPlayer.party.length-1] = pkmn.item_id if @initialItems
return
end
# Messages saying the Pokémon was stored in a PC box
creator = @peer.pbGetStorageCreatorName
curBoxName = @peer.pbBoxName(currentBox)
boxName = @peer.pbBoxName(storedBox)
if storedBox!=currentBox
if creator
pbDisplayPaused(_INTL("Box \"{1}\" on {2}'s PC was full.",curBoxName,creator))
else
pbDisplayPaused(_INTL("Box \"{1}\" on someone's PC was full.",curBoxName))
end
pbDisplayPaused(_INTL("{1} was transferred to box \"{2}\".",pkmn.name,boxName))
else
if creator
pbDisplayPaused(_INTL("{1} was transferred to {2}'s PC.",pkmn.name,creator))
else
pbDisplayPaused(_INTL("{1} was transferred to someone's PC.",pkmn.name))
end
pbDisplayPaused(_INTL("It was stored in box \"{1}\".",boxName))
end
end
# Register all caught Pokémon in the Pokédex, and store them.
def pbRecordAndStoreCaughtPokemon
@caughtPokemon.each do |pkmn|
pbPlayer.pokedex.register(pkmn) # In case the form changed upon leaving battle
# Record the Pokémon's species as owned in the Pokédex
if !pbPlayer.hasOwned?(pkmn.species)
pbPlayer.pokedex.set_owned(pkmn.species)
if $Trainer.has_pokedex
pbDisplayPaused(_INTL("{1}'s data was added to the Pokédex.",pkmn.name))
@scene.pbShowPokedex(pkmn.species)
end
end
# Record a Shadow Pokémon's species as having been caught
pbPlayer.pokedex.set_shadow_pokemon_owned(pkmn.species) if pkmn.shadowPokemon?
# Store caught Pokémon
pbStorePokemon(pkmn)
end
@caughtPokemon.clear
end
#=============================================================================
# Throw a Poké Ball
#=============================================================================
def pbThrowPokeBall(idxBattler,ball,catch_rate=nil,showPlayer=false)
# Determine which Pokémon you're throwing the Poké Ball at
battler = nil
if opposes?(idxBattler)
battler = @battlers[idxBattler]
else
battler = @battlers[idxBattler].pbDirectOpposing(true)
end
if battler.fainted?
battler.eachAlly do |b|
battler = b
break
end
end
# Messages
itemName = GameData::Item.get(ball).name
if battler.fainted?
if itemName.starts_with_vowel?
pbDisplay(_INTL("{1} threw an {2}!",pbPlayer.name,itemName))
else
pbDisplay(_INTL("{1} threw a {2}!",pbPlayer.name,itemName))
end
pbDisplay(_INTL("But there was no target..."))
return
end
if itemName.starts_with_vowel?
pbDisplayBrief(_INTL("{1} threw an {2}!",pbPlayer.name,itemName))
else
pbDisplayBrief(_INTL("{1} threw a {2}!",pbPlayer.name,itemName))
end
# Animation of opposing trainer blocking Poké Balls (unless it's a Snag Ball
# at a Shadow Pokémon)
if trainerBattle? && !(GameData::Item.get(ball).is_snag_ball? && battler.shadowPokemon?)
@scene.pbThrowAndDeflect(ball,1)
pbDisplay(_INTL("The Trainer blocked your Poké Ball! Don't be a thief!"))
return
end
# Calculate the number of shakes (4=capture)
pkmn = battler.pokemon
@criticalCapture = false
numShakes = pbCaptureCalc(pkmn,battler,catch_rate,ball)
PBDebug.log("[Threw Poké Ball] #{itemName}, #{numShakes} shakes (4=capture)")
# Animation of Ball throw, absorb, shake and capture/burst out
@scene.pbThrow(ball,numShakes,@criticalCapture,battler.index,showPlayer)
# Outcome message
case numShakes
when 0
pbDisplay(_INTL("Oh no! The Pokémon broke free!"))
BallHandlers.onFailCatch(ball,self,battler)
when 1
pbDisplay(_INTL("Aww! It appeared to be caught!"))
BallHandlers.onFailCatch(ball,self,battler)
when 2
pbDisplay(_INTL("Aargh! Almost had it!"))
BallHandlers.onFailCatch(ball,self,battler)
when 3
pbDisplay(_INTL("Gah! It was so close, too!"))
BallHandlers.onFailCatch(ball,self,battler)
when 4
pbDisplayBrief(_INTL("Gotcha! {1} was caught!",pkmn.name))
@scene.pbThrowSuccess # Play capture success jingle
pbRemoveFromParty(battler.index,battler.pokemonIndex)
# Gain Exp
if Settings::GAIN_EXP_FOR_CAPTURE
battler.captured = true
pbGainExp
battler.captured = false
end
battler.pbReset
if pbAllFainted?(battler.index)
@decision = (trainerBattle?) ? 1 : 4 # Battle ended by win/capture
end
# Modify the Pokémon's properties because of the capture
if GameData::Item.get(ball).is_snag_ball?
pkmn.owner = Pokemon::Owner.new_from_trainer(pbPlayer)
end
BallHandlers.onCatch(ball,self,pkmn)
pkmn.poke_ball = ball
pkmn.makeUnmega if pkmn.mega?
pkmn.makeUnprimal
pkmn.update_shadow_moves if pkmn.shadowPokemon?
pkmn.record_first_moves
# Reset form
pkmn.forced_form = nil if MultipleForms.hasFunction?(pkmn.species,"getForm")
@peer.pbOnLeavingBattle(self,pkmn,true,true)
# Make the Poké Ball and data box disappear
@scene.pbHideCaptureBall(idxBattler)
# Save the Pokémon for storage at the end of battle
@caughtPokemon.push(pkmn)
end
end
#=============================================================================
# Calculate how many shakes a thrown Poké Ball will make (4 = capture)
#=============================================================================
def pbCaptureCalc(pkmn,battler,catch_rate,ball)
return 4 if $DEBUG && Input.press?(Input::CTRL)
# Get a catch rate if one wasn't provided
catch_rate = pkmn.species_data.catch_rate if !catch_rate
# Modify catch_rate depending on the Poké Ball's effect
ultraBeast = [:NIHILEGO, :BUZZWOLE, :PHEROMOSA, :XURKITREE, :CELESTEELA,
:KARTANA, :GUZZLORD, :POIPOLE, :NAGANADEL, :STAKATAKA,
:BLACEPHALON].include?(pkmn.species)
if !ultraBeast || ball == :BEASTBALL
catch_rate = BallHandlers.modifyCatchRate(ball,catch_rate,self,battler,ultraBeast)
else
catch_rate /= 10
end
# First half of the shakes calculation
a = battler.totalhp
b = battler.hp
x = ((3*a-2*b)*catch_rate.to_f)/(3*a)
# Calculation modifiers
if battler.status == :SLEEP || battler.status == :FROZEN
x *= 2.5
elsif battler.status != :NONE
x *= 1.5
end
x = x.floor
x = 1 if x<1
# Definite capture, no need to perform randomness checks
return 4 if x>=255 || BallHandlers.isUnconditional?(ball,self,battler)
# Second half of the shakes calculation
y = ( 65536 / ((255.0/x)**0.1875) ).floor
# Critical capture check
if Settings::ENABLE_CRITICAL_CAPTURES
c = 0
numOwned = $Trainer.pokedex.owned_count
if numOwned>600; c = x*5/12
elsif numOwned>450; c = x*4/12
elsif numOwned>300; c = x*3/12
elsif numOwned>150; c = x*2/12
elsif numOwned>30; c = x/12
end
# Calculate the number of shakes
if c>0 && pbRandom(256)<c
@criticalCapture = true
return 4 if pbRandom(65536)<y
return 0
end
end
# Calculate the number of shakes
numShakes = 0
for i in 0...4
break if numShakes<i
numShakes += 1 if pbRandom(65536)<y
end
return numShakes
end
end

View File

@@ -0,0 +1,785 @@
# Results of battle:
# 0 - Undecided or aborted
# 1 - Player won
# 2 - Player lost
# 3 - Player or wild Pokémon ran from battle, or player forfeited the match
# 4 - Wild Pokémon was caught
# 5 - Draw
# Possible actions a battler can take in a round:
# :None
# :UseMove
# :SwitchOut
# :UseItem
# :Call
# :Run
# :Shift
# NOTE: If you want to have more than 3 Pokémon on a side at once, you will need
# to edit some code. Mainly this is to change/add coordinates for the
# sprites, describe the relationships between Pokémon and trainers, and to
# change messages. The methods that will need editing are as follows:
# class PokeBattle_Battle
# def setBattleMode
# def pbGetOwnerIndexFromBattlerIndex
# def pbGetOpposingIndicesInOrder
# def nearBattlers?
# def pbStartBattleSendOut
# def pbEORShiftDistantBattlers
# def pbCanShift?
# def pbEndOfRoundPhase
# class TargetMenuDisplay
# def initialize
# class PokemonDataBox
# def initializeDataBoxGraphic
# module PokeBattle_SceneConstants
# def self.pbBattlerPosition
# def self.pbTrainerPosition
# class PokemonTemp
# def recordBattleRule
# (There is no guarantee that this list is complete.)
class PokeBattle_Battle
attr_reader :scene # Scene object for this battle
attr_reader :peer
attr_reader :field # Effects common to the whole of a battle
attr_reader :sides # Effects common to each side of a battle
attr_reader :positions # Effects that apply to a battler position
attr_reader :battlers # Currently active Pokémon
attr_reader :sideSizes # Array of number of battlers per side
attr_accessor :backdrop # Filename fragment used for background graphics
attr_accessor :backdropBase # Filename fragment used for base graphics
attr_accessor :time # Time of day (0=day, 1=eve, 2=night)
attr_accessor :environment # Battle surroundings (for mechanics purposes)
attr_reader :turnCount
attr_accessor :decision # Decision: 0=undecided; 1=win; 2=loss; 3=escaped; 4=caught
attr_reader :player # Player trainer (or array of trainers)
attr_reader :opponent # Opponent trainer (or array of trainers)
attr_accessor :items # Items held by opponents
attr_accessor :endSpeeches
attr_accessor :endSpeechesWin
attr_accessor :party1starts # Array of start indexes for each player-side trainer's party
attr_accessor :party2starts # Array of start indexes for each opponent-side trainer's party
attr_accessor :internalBattle # Internal battle flag
attr_accessor :debug # Debug flag
attr_accessor :canRun # True if player can run from battle
attr_accessor :canLose # True if player won't black out if they lose
attr_accessor :switchStyle # Switch/Set "battle style" option
attr_accessor :showAnims # "Battle Effects" option
attr_accessor :controlPlayer # Whether player's Pokémon are AI controlled
attr_accessor :expGain # Whether Pokémon can gain Exp/EVs
attr_accessor :moneyGain # Whether the player can gain/lose money
attr_accessor :rules
attr_accessor :choices # Choices made by each Pokémon this round
attr_accessor :megaEvolution # Battle index of each trainer's Pokémon to Mega Evolve
attr_reader :initialItems
attr_reader :recycleItems
attr_reader :belch
attr_reader :battleBond
attr_reader :usedInBattle # Whether each Pokémon was used in battle (for Burmy)
attr_reader :successStates # Success states
attr_accessor :lastMoveUsed # Last move used
attr_accessor :lastMoveUser # Last move user
attr_reader :switching # True if during the switching phase of the round
attr_reader :futureSight # True if Future Sight is hitting
attr_reader :endOfRound # True during the end of round
attr_accessor :moldBreaker # True if Mold Breaker applies
attr_reader :struggle # The Struggle move
include PokeBattle_BattleCommon
def pbRandom(x); return rand(x); end
#=============================================================================
# Creating the battle class
#=============================================================================
def initialize(scene,p1,p2,player,opponent)
if p1.length==0
raise ArgumentError.new(_INTL("Party 1 has no Pokémon."))
elsif p2.length==0
raise ArgumentError.new(_INTL("Party 2 has no Pokémon."))
end
@scene = scene
@peer = PokeBattle_BattlePeer.create
@battleAI = PokeBattle_AI.new(self)
@field = PokeBattle_ActiveField.new # Whole field (gravity/rooms)
@sides = [PokeBattle_ActiveSide.new, # Player's side
PokeBattle_ActiveSide.new] # Foe's side
@positions = [] # Battler positions
@battlers = []
@sideSizes = [1,1] # Single battle, 1v1
@backdrop = ""
@backdropBase = nil
@time = 0
@environment = :None # e.g. Tall grass, cave, still water
@turnCount = 0
@decision = 0
@caughtPokemon = []
player = [player] if !player.nil? && !player.is_a?(Array)
opponent = [opponent] if !opponent.nil? && !opponent.is_a?(Array)
@player = player # Array of Player/NPCTrainer objects, or nil
@opponent = opponent # Array of NPCTrainer objects, or nil
@items = nil
@endSpeeches = []
@endSpeechesWin = []
@party1 = p1
@party2 = p2
@party1order = Array.new(@party1.length) { |i| i }
@party2order = Array.new(@party2.length) { |i| i }
@party1starts = [0]
@party2starts = [0]
@internalBattle = true
@debug = false
@canRun = true
@canLose = false
@switchStyle = true
@showAnims = true
@controlPlayer = false
@expGain = true
@moneyGain = true
@rules = {}
@priority = []
@priorityTrickRoom = false
@choices = []
@megaEvolution = [
[-1] * (@player ? @player.length : 1),
[-1] * (@opponent ? @opponent.length : 1)
]
@initialItems = [
Array.new(@party1.length) { |i| (@party1[i]) ? @party1[i].item_id : nil },
Array.new(@party2.length) { |i| (@party2[i]) ? @party2[i].item_id : nil }
]
@recycleItems = [Array.new(@party1.length, nil), Array.new(@party2.length, nil)]
@belch = [Array.new(@party1.length, false), Array.new(@party2.length, false)]
@battleBond = [Array.new(@party1.length, false), Array.new(@party2.length, false)]
@usedInBattle = [Array.new(@party1.length, false), Array.new(@party2.length, false)]
@successStates = []
@lastMoveUsed = nil
@lastMoveUser = -1
@switching = false
@futureSight = false
@endOfRound = false
@moldBreaker = false
@runCommand = 0
@nextPickupUse = 0
if GameData::Move.exists?(:STRUGGLE)
@struggle = PokeBattle_Move.from_pokemon_move(self, Pokemon::Move.new(:STRUGGLE))
else
@struggle = PokeBattle_Struggle.new(self, nil)
end
end
#=============================================================================
# Information about the type and size of the battle
#=============================================================================
def wildBattle?; return @opponent.nil?; end
def trainerBattle?; return !@opponent.nil?; end
# Sets the number of battler slots on each side of the field independently.
# For "1v2" names, the first number is for the player's side and the second
# number is for the opposing side.
def setBattleMode(mode)
@sideSizes =
case mode
when "triple", "3v3" then [3, 3]
when "3v2" then [3, 2]
when "3v1" then [3, 1]
when "2v3" then [2, 3]
when "double", "2v2" then [2, 2]
when "2v1" then [2, 1]
when "1v3" then [1, 3]
when "1v2" then [1, 2]
else [1, 1] # Single, 1v1 (default)
end
end
def singleBattle?
return pbSideSize(0)==1 && pbSideSize(1)==1
end
def pbSideSize(index)
return @sideSizes[index%2]
end
def maxBattlerIndex
return (pbSideSize(0)>pbSideSize(1)) ? (pbSideSize(0)-1)*2 : pbSideSize(1)*2-1
end
#=============================================================================
# Trainers and owner-related methods
#=============================================================================
def pbPlayer; return @player[0]; end
# Given a battler index, returns the index within @player/@opponent of the
# trainer that controls that battler index.
# NOTE: You shouldn't ever have more trainers on a side than there are battler
# positions on that side. This method doesn't account for if you do.
def pbGetOwnerIndexFromBattlerIndex(idxBattler)
trainer = (opposes?(idxBattler)) ? @opponent : @player
return 0 if !trainer
case trainer.length
when 2
n = pbSideSize(idxBattler%2)
return [0,0,1][idxBattler/2] if n==3
return idxBattler/2 # Same as [0,1][idxBattler/2], i.e. 2 battler slots
when 3
return idxBattler/2
end
return 0
end
def pbGetOwnerFromBattlerIndex(idxBattler)
idxTrainer = pbGetOwnerIndexFromBattlerIndex(idxBattler)
return (opposes?(idxBattler)) ? @opponent[idxTrainer] : @player[idxTrainer]
end
def pbGetOwnerIndexFromPartyIndex(idxBattler,idxParty)
ret = -1
pbPartyStarts(idxBattler).each_with_index do |start,i|
break if start>idxParty
ret = i
end
return ret
end
# Only used for the purpose of an error message when one trainer tries to
# switch another trainer's Pokémon.
def pbGetOwnerFromPartyIndex(idxBattler,idxParty)
idxTrainer = pbGetOwnerIndexFromPartyIndex(idxBattler,idxParty)
return (opposes?(idxBattler)) ? @opponent[idxTrainer] : @player[idxTrainer]
end
def pbGetOwnerName(idxBattler)
idxTrainer = pbGetOwnerIndexFromBattlerIndex(idxBattler)
return @opponent[idxTrainer].full_name if opposes?(idxBattler) # Opponent
return @player[idxTrainer].full_name if idxTrainer>0 # Ally trainer
return @player[idxTrainer].name # Player
end
def pbGetOwnerItems(idxBattler)
return [] if !@items || !opposes?(idxBattler)
return @items[pbGetOwnerIndexFromBattlerIndex(idxBattler)]
end
# Returns whether the battler in position idxBattler is owned by the same
# trainer that owns the Pokémon in party slot idxParty. This assumes that
# both the battler position and the party slot are from the same side.
def pbIsOwner?(idxBattler,idxParty)
idxTrainer1 = pbGetOwnerIndexFromBattlerIndex(idxBattler)
idxTrainer2 = pbGetOwnerIndexFromPartyIndex(idxBattler,idxParty)
return idxTrainer1==idxTrainer2
end
def pbOwnedByPlayer?(idxBattler)
return false if opposes?(idxBattler)
return pbGetOwnerIndexFromBattlerIndex(idxBattler)==0
end
# Returns the number of Pokémon positions controlled by the given trainerIndex
# on the given side of battle.
def pbNumPositions(side,idxTrainer)
ret = 0
for i in 0...pbSideSize(side)
t = pbGetOwnerIndexFromBattlerIndex(i*2+side)
next if t!=idxTrainer
ret += 1
end
return ret
end
#=============================================================================
# Get party information (counts all teams on the same side)
#=============================================================================
def pbParty(idxBattler)
return (opposes?(idxBattler)) ? @party2 : @party1
end
def pbOpposingParty(idxBattler)
return (opposes?(idxBattler)) ? @party1 : @party2
end
def pbPartyOrder(idxBattler)
return (opposes?(idxBattler)) ? @party2order : @party1order
end
def pbPartyStarts(idxBattler)
return (opposes?(idxBattler)) ? @party2starts : @party1starts
end
# Returns the player's team in its display order. Used when showing the party
# screen.
def pbPlayerDisplayParty(idxBattler=0)
partyOrders = pbPartyOrder(idxBattler)
idxStart, _idxEnd = pbTeamIndexRangeFromBattlerIndex(idxBattler)
ret = []
eachInTeamFromBattlerIndex(idxBattler) { |pkmn,i| ret[partyOrders[i]-idxStart] = pkmn }
return ret
end
def pbAbleCount(idxBattler=0)
party = pbParty(idxBattler)
count = 0
party.each { |pkmn| count += 1 if pkmn && pkmn.able? }
return count
end
def pbAbleNonActiveCount(idxBattler=0)
party = pbParty(idxBattler)
inBattleIndices = []
eachSameSideBattler(idxBattler) { |b| inBattleIndices.push(b.pokemonIndex) }
count = 0
party.each_with_index do |pkmn,idxParty|
next if !pkmn || !pkmn.able?
next if inBattleIndices.include?(idxParty)
count += 1
end
return count
end
def pbAllFainted?(idxBattler=0)
return pbAbleCount(idxBattler)==0
end
# For the given side of the field (0=player's, 1=opponent's), returns an array
# containing the number of able Pokémon in each team.
def pbAbleTeamCounts(side)
party = pbParty(side)
partyStarts = pbPartyStarts(side)
ret = []
idxTeam = -1
nextStart = 0
party.each_with_index do |pkmn,i|
if i>=nextStart
idxTeam += 1
nextStart = (idxTeam<partyStarts.length-1) ? partyStarts[idxTeam+1] : party.length
end
next if !pkmn || !pkmn.able?
ret[idxTeam] = 0 if !ret[idxTeam]
ret[idxTeam] += 1
end
return ret
end
#=============================================================================
# Get team information (a team is only the Pokémon owned by a particular
# trainer)
#=============================================================================
def pbTeamIndexRangeFromBattlerIndex(idxBattler)
partyStarts = pbPartyStarts(idxBattler)
idxTrainer = pbGetOwnerIndexFromBattlerIndex(idxBattler)
idxPartyStart = partyStarts[idxTrainer]
idxPartyEnd = (idxTrainer<partyStarts.length-1) ? partyStarts[idxTrainer+1] : pbParty(idxBattler).length
return idxPartyStart, idxPartyEnd
end
def pbTeamLengthFromBattlerIndex(idxBattler)
idxPartyStart, idxPartyEnd = pbTeamIndexRangeFromBattlerIndex(idxBattler)
return idxPartyEnd-idxPartyStart
end
def eachInTeamFromBattlerIndex(idxBattler)
party = pbParty(idxBattler)
idxPartyStart, idxPartyEnd = pbTeamIndexRangeFromBattlerIndex(idxBattler)
party.each_with_index { |pkmn,i| yield pkmn,i if pkmn && i>=idxPartyStart && i<idxPartyEnd }
end
def eachInTeam(side,idxTrainer)
party = pbParty(side)
partyStarts = pbPartyStarts(side)
idxPartyStart = partyStarts[idxTrainer]
idxPartyEnd = (idxTrainer<partyStarts.length-1) ? partyStarts[idxTrainer+1] : party.length
party.each_with_index { |pkmn,i| yield pkmn,i if pkmn && i>=idxPartyStart && i<idxPartyEnd }
end
# Used for Illusion.
# NOTE: This cares about the temporary rearranged order of the team. That is,
# if you do some switching, the last Pokémon in the team could change
# and the Illusion could be a different Pokémon.
def pbLastInTeam(idxBattler)
party = pbParty(idxBattler)
partyOrders = pbPartyOrder(idxBattler)
idxPartyStart, idxPartyEnd = pbTeamIndexRangeFromBattlerIndex(idxBattler)
ret = -1
party.each_with_index do |pkmn,i|
next if i<idxPartyStart || i>=idxPartyEnd # Check the team only
next if !pkmn || !pkmn.able? # Can't copy a non-fainted Pokémon or egg
ret = i if partyOrders[i]>partyOrders[ret]
end
return ret
end
# Used to calculate money gained/lost after winning/losing a battle.
def pbMaxLevelInTeam(side,idxTrainer)
ret = 1
eachInTeam(side,idxTrainer) do |pkmn,_i|
ret = pkmn.level if pkmn.level>ret
end
return ret
end
#=============================================================================
# Iterate through battlers
#=============================================================================
def eachBattler
@battlers.each { |b| yield b if b && !b.fainted? }
end
def eachSameSideBattler(idxBattler=0)
idxBattler = idxBattler.index if idxBattler.respond_to?("index")
@battlers.each { |b| yield b if b && !b.fainted? && !b.opposes?(idxBattler) }
end
def eachOtherSideBattler(idxBattler=0)
idxBattler = idxBattler.index if idxBattler.respond_to?("index")
@battlers.each { |b| yield b if b && !b.fainted? && b.opposes?(idxBattler) }
end
def pbSideBattlerCount(idxBattler=0)
ret = 0
eachSameSideBattler(idxBattler) { |_b| ret += 1 }
return ret
end
def pbOpposingBattlerCount(idxBattler=0)
ret = 0
eachOtherSideBattler(idxBattler) { |_b| ret += 1 }
return ret
end
# This method only counts the player's Pokémon, not a partner trainer's.
def pbPlayerBattlerCount
ret = 0
eachSameSideBattler { |b| ret += 1 if b.pbOwnedByPlayer? }
return ret
end
def pbCheckGlobalAbility(abil)
eachBattler { |b| return b if b.hasActiveAbility?(abil) }
return nil
end
def pbCheckOpposingAbility(abil,idxBattler=0,nearOnly=false)
eachOtherSideBattler(idxBattler) do |b|
next if nearOnly && !b.near?(idxBattler)
return b if b.hasActiveAbility?(abil)
end
return nil
end
# Given a battler index, and using battle side sizes, returns an array of
# battler indices from the opposing side that are in order of most "opposite".
# Used when choosing a target and pressing up/down to move the cursor to the
# opposite side, and also when deciding which target to select first for some
# moves.
def pbGetOpposingIndicesInOrder(idxBattler)
case pbSideSize(0)
when 1
case pbSideSize(1)
when 1 # 1v1 single
return [0] if opposes?(idxBattler)
return [1]
when 2 # 1v2
return [0] if opposes?(idxBattler)
return [3,1]
when 3 # 1v3
return [0] if opposes?(idxBattler)
return [3,5,1]
end
when 2
case pbSideSize(1)
when 1 # 2v1
return [0,2] if opposes?(idxBattler)
return [1]
when 2 # 2v2 double
return [[3,1],[2,0],[1,3],[0,2]][idxBattler]
when 3 # 2v3
return [[5,3,1],[2,0],[3,1,5]][idxBattler] if idxBattler<3
return [0,2]
end
when 3
case pbSideSize(1)
when 1 # 3v1
return [2,0,4] if opposes?(idxBattler)
return [1]
when 2 # 3v2
return [[3,1],[2,4,0],[3,1],[2,0,4],[1,3]][idxBattler]
when 3 # 3v3 triple
return [[5,3,1],[4,2,0],[3,5,1],[2,0,4],[1,3,5],[0,2,4]][idxBattler]
end
end
return [idxBattler]
end
#=============================================================================
# Comparing the positions of two battlers
#=============================================================================
def opposes?(idxBattler1,idxBattler2=0)
idxBattler1 = idxBattler1.index if idxBattler1.respond_to?("index")
idxBattler2 = idxBattler2.index if idxBattler2.respond_to?("index")
return (idxBattler1&1)!=(idxBattler2&1)
end
def nearBattlers?(idxBattler1,idxBattler2)
return false if idxBattler1==idxBattler2
return true if pbSideSize(0)<=2 && pbSideSize(1)<=2
# Get all pairs of battler positions that are not close to each other
pairsArray = [[0,4],[1,5]] # Covers 3v1 and 1v3
case pbSideSize(0)
when 3
case pbSideSize(1)
when 3 # 3v3 (triple)
pairsArray.push([0,1])
pairsArray.push([4,5])
when 2 # 3v2
pairsArray.push([0,1])
pairsArray.push([3,4])
end
when 2 # 2v3
pairsArray.push([0,1])
pairsArray.push([2,5])
end
# See if any pair matches the two battlers being assessed
pairsArray.each do |pair|
return false if pair.include?(idxBattler1) && pair.include?(idxBattler2)
end
return true
end
#=============================================================================
# Altering a party or rearranging battlers
#=============================================================================
def pbRemoveFromParty(idxBattler,idxParty)
party = pbParty(idxBattler)
# Erase the Pokémon from the party
party[idxParty] = nil
# Rearrange the display order of the team to place the erased Pokémon last
# in it (to avoid gaps)
partyOrders = pbPartyOrder(idxBattler)
partyStarts = pbPartyStarts(idxBattler)
idxTrainer = pbGetOwnerIndexFromPartyIndex(idxBattler,idxParty)
idxPartyStart = partyStarts[idxTrainer]
idxPartyEnd = (idxTrainer<partyStarts.length-1) ? partyStarts[idxTrainer+1] : party.length
origPartyPos = partyOrders[idxParty] # Position of erased Pokémon initially
partyOrders[idxParty] = idxPartyEnd # Put erased Pokémon last in the team
party.each_with_index do |_pkmn,i|
next if i<idxPartyStart || i>=idxPartyEnd # Only check the team
next if partyOrders[i]<origPartyPos # Appeared before erased Pokémon
partyOrders[i] -= 1 # Appeared after erased Pokémon; bump it up by 1
end
end
def pbSwapBattlers(idxA,idxB)
return false if !@battlers[idxA] || !@battlers[idxB]
# Can't swap if battlers aren't owned by the same trainer
return false if opposes?(idxA,idxB)
return false if pbGetOwnerIndexFromBattlerIndex(idxA)!=pbGetOwnerIndexFromBattlerIndex(idxB)
@battlers[idxA], @battlers[idxB] = @battlers[idxB], @battlers[idxA]
@battlers[idxA].index, @battlers[idxB].index = @battlers[idxB].index, @battlers[idxA].index
@choices[idxA], @choices[idxB] = @choices[idxB], @choices[idxA]
@scene.pbSwapBattlerSprites(idxA,idxB)
# Swap the target of any battlers' effects that point at either of the
# swapped battlers, to ensure they still point at the correct target
# NOTE: LeechSeed is not swapped, because drained HP goes to whichever
# Pokémon is in the position that Leech Seed was used from.
# NOTE: PerishSongUser doesn't need to change, as it's only used to
# determine which side the Perish Song user was on, and a battler
# can't change sides.
effectsToSwap = [PBEffects::Attract,
PBEffects::BideTarget,
PBEffects::CounterTarget,
PBEffects::LockOnPos,
PBEffects::MeanLook,
PBEffects::MirrorCoatTarget,
PBEffects::SkyDrop,
PBEffects::TrappingUser]
eachBattler do |b|
for i in effectsToSwap
next if b.effects[i]!=idxA && b.effects[i]!=idxB
b.effects[i] = (b.effects[i]==idxA) ? idxB : idxA
end
end
return true
end
#=============================================================================
#
#=============================================================================
# Returns the battler representing the Pokémon at index idxParty in its party,
# on the same side as a battler with battler index of idxBattlerOther.
def pbFindBattler(idxParty,idxBattlerOther=0)
eachSameSideBattler(idxBattlerOther) { |b| return b if b.pokemonIndex==idxParty }
return nil
end
# Only used for Wish, as the Wishing Pokémon will no longer be in battle.
def pbThisEx(idxBattler,idxParty)
party = pbParty(idxBattler)
if opposes?(idxBattler)
return _INTL("The opposing {1}",party[idxParty].name) if trainerBattle?
return _INTL("The wild {1}",party[idxParty].name)
end
return _INTL("The ally {1}",party[idxParty].name) if !pbOwnedByPlayer?(idxBattler)
return party[idxParty].name
end
def pbSetSeen(battler)
return if !battler || !@internalBattle
pbPlayer.pokedex.register(battler.displaySpecies,battler.displayGender,battler.displayForm)
end
def nextPickupUse
@nextPickupUse += 1
return @nextPickupUse
end
#=============================================================================
# Weather and terrain
#=============================================================================
def defaultWeather=(value)
@field.defaultWeather = value
@field.weather = value
@field.weatherDuration = -1
end
# Returns the effective weather (note that weather effects can be negated)
def pbWeather
eachBattler { |b| return :None if b.hasActiveAbility?([:CLOUDNINE, :AIRLOCK]) }
return @field.weather
end
# Used for causing weather by a move or by an ability.
def pbStartWeather(user,newWeather,fixedDuration=false,showAnim=true)
return if @field.weather==newWeather
@field.weather = newWeather
duration = (fixedDuration) ? 5 : -1
if duration>0 && user && user.itemActive?
duration = BattleHandlers.triggerWeatherExtenderItem(user.item,
@field.weather,duration,user,self)
end
@field.weatherDuration = duration
weather_data = GameData::BattleWeather.try_get(@field.weather)
pbCommonAnimation(weather_data.animation) if showAnim && weather_data
pbHideAbilitySplash(user) if user
case @field.weather
when :Sun then pbDisplay(_INTL("The sunlight turned harsh!"))
when :Rain then pbDisplay(_INTL("It started to rain!"))
when :Sandstorm then pbDisplay(_INTL("A sandstorm brewed!"))
when :Hail then pbDisplay(_INTL("It started to hail!"))
when :HarshSun then pbDisplay(_INTL("The sunlight turned extremely harsh!"))
when :HeavyRain then pbDisplay(_INTL("A heavy rain began to fall!"))
when :StrongWinds then pbDisplay(_INTL("Mysterious strong winds are protecting Flying-type Pokémon!"))
when :ShadowSky then pbDisplay(_INTL("A shadow sky appeared!"))
end
# Check for end of primordial weather, and weather-triggered form changes
eachBattler { |b| b.pbCheckFormOnWeatherChange }
pbEndPrimordialWeather
end
def pbEndPrimordialWeather
oldWeather = @field.weather
# End Primordial Sea, Desolate Land, Delta Stream
case @field.weather
when :HarshSun
if !pbCheckGlobalAbility(:DESOLATELAND)
@field.weather = :None
pbDisplay("The harsh sunlight faded!")
end
when :HeavyRain
if !pbCheckGlobalAbility(:PRIMORDIALSEA)
@field.weather = :None
pbDisplay("The heavy rain has lifted!")
end
when :StrongWinds
if !pbCheckGlobalAbility(:DELTASTREAM)
@field.weather = :None
pbDisplay("The mysterious air current has dissipated!")
end
end
if @field.weather!=oldWeather
# Check for form changes caused by the weather changing
eachBattler { |b| b.pbCheckFormOnWeatherChange }
# Start up the default weather
pbStartWeather(nil,@field.defaultWeather) if @field.defaultWeather != :None
end
end
def defaultTerrain=(value)
@field.defaultTerrain = value
@field.terrain = value
@field.terrainDuration = -1
end
def pbStartTerrain(user,newTerrain,fixedDuration=true)
return if @field.terrain==newTerrain
@field.terrain = newTerrain
duration = (fixedDuration) ? 5 : -1
if duration>0 && user && user.itemActive?
duration = BattleHandlers.triggerTerrainExtenderItem(user.item,
newTerrain,duration,user,self)
end
@field.terrainDuration = duration
terrain_data = GameData::BattleTerrain.try_get(@field.terrain)
pbCommonAnimation(terrain_data.animation) if terrain_data
pbHideAbilitySplash(user) if user
case @field.terrain
when :Electric
pbDisplay(_INTL("An electric current runs across the battlefield!"))
when :Grassy
pbDisplay(_INTL("Grass grew to cover the battlefield!"))
when :Misty
pbDisplay(_INTL("Mist swirled about the battlefield!"))
when :Psychic
pbDisplay(_INTL("The battlefield got weird!"))
end
# Check for terrain seeds that boost stats in a terrain
eachBattler { |b| b.pbItemTerrainStatBoostCheck }
end
#=============================================================================
# Messages and animations
#=============================================================================
def pbDisplay(msg,&block)
@scene.pbDisplayMessage(msg,&block)
end
def pbDisplayBrief(msg)
@scene.pbDisplayMessage(msg,true)
end
def pbDisplayPaused(msg,&block)
@scene.pbDisplayPausedMessage(msg,&block)
end
def pbDisplayConfirm(msg)
return @scene.pbDisplayConfirmMessage(msg)
end
def pbShowCommands(msg,commands,canCancel=true)
@scene.pbShowCommands(msg,commands,canCancel)
end
def pbAnimation(move,user,targets,hitNum=0)
@scene.pbAnimation(move,user,targets,hitNum) if @showAnims
end
def pbCommonAnimation(name,user=nil,targets=nil)
@scene.pbCommonAnimation(name,user,targets) if @showAnims
end
def pbShowAbilitySplash(battler,delay=false,logTrigger=true)
PBDebug.log("[Ability triggered] #{battler.pbThis}'s #{battler.abilityName}") if logTrigger
return if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH
@scene.pbShowAbilitySplash(battler)
if delay
Graphics.frame_rate.times { @scene.pbUpdate } # 1 second
end
end
def pbHideAbilitySplash(battler)
return if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH
@scene.pbHideAbilitySplash(battler)
end
def pbReplaceAbilitySplash(battler)
return if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH
@scene.pbReplaceAbilitySplash(battler)
end
end

View File

@@ -0,0 +1,539 @@
class PokeBattle_Battle
class BattleAbortedException < Exception; end
def pbAbort
raise BattleAbortedException.new("Battle aborted")
end
#=============================================================================
# Makes sure all Pokémon exist that need to. Alter the type of battle if
# necessary. Will never try to create battler positions, only delete them
# (except for wild Pokémon whose number of positions are fixed). Reduces the
# size of each side by 1 and tries again. If the side sizes are uneven, only
# the larger side's size will be reduced by 1 each time, until both sides are
# an equal size (then both sides will be reduced equally).
#=============================================================================
def pbEnsureParticipants
# Prevent battles larger than 2v2 if both sides have multiple trainers
# NOTE: This is necessary to ensure that battlers can never become unable to
# hit each other due to being too far away. In such situations,
# battlers will move to the centre position at the end of a round, but
# because they cannot move into a position owned by a different
# trainer, it's possible that battlers will be unable to move close
# enough to hit each other if there are multiple trainers on each
# side.
if trainerBattle? && (@sideSizes[0]>2 || @sideSizes[1]>2) &&
@player.length>1 && @opponent.length>1
raise _INTL("Can't have battles larger than 2v2 where both sides have multiple trainers")
end
# Find out how many Pokémon each trainer has
side1counts = pbAbleTeamCounts(0)
side2counts = pbAbleTeamCounts(1)
# Change the size of the battle depending on how many wild Pokémon there are
if wildBattle? && side2counts[0]!=@sideSizes[1]
if @sideSizes[0]==@sideSizes[1]
# Even number of battlers per side, change both equally
@sideSizes = [side2counts[0],side2counts[0]]
else
# Uneven number of battlers per side, just change wild side's size
@sideSizes[1] = side2counts[0]
end
end
# Check if battle is possible, including changing the number of battlers per
# side if necessary
loop do
needsChanging = false
for side in 0...2 # Each side in turn
next if side==1 && wildBattle? # Wild side's size already checked above
sideCounts = (side==0) ? side1counts : side2counts
requireds = []
# Find out how many Pokémon each trainer on side needs to have
for i in 0...@sideSizes[side]
idxTrainer = pbGetOwnerIndexFromBattlerIndex(i*2+side)
requireds[idxTrainer] = 0 if requireds[idxTrainer].nil?
requireds[idxTrainer] += 1
end
# Compare the have values with the need values
if requireds.length>sideCounts.length
raise _INTL("Error: def pbGetOwnerIndexFromBattlerIndex gives invalid owner index ({1} for battle type {2}v{3}, trainers {4}v{5})",
requireds.length-1,@sideSizes[0],@sideSizes[1],side1counts.length,side2counts.length)
end
sideCounts.each_with_index do |_count,i|
if !requireds[i] || requireds[i]==0
raise _INTL("Player-side trainer {1} has no battler position for their Pokémon to go (trying {2}v{3} battle)",
i+1,@sideSizes[0],@sideSizes[1]) if side==0
raise _INTL("Opposing trainer {1} has no battler position for their Pokémon to go (trying {2}v{3} battle)",
i+1,@sideSizes[0],@sideSizes[1]) if side==1
end
next if requireds[i]<=sideCounts[i] # Trainer has enough Pokémon to fill their positions
if requireds[i]==1
raise _INTL("Player-side trainer {1} has no able Pokémon",i+1) if side==0
raise _INTL("Opposing trainer {1} has no able Pokémon",i+1) if side==1
end
# Not enough Pokémon, try lowering the number of battler positions
needsChanging = true
break
end
break if needsChanging
end
break if !needsChanging
# Reduce one or both side's sizes by 1 and try again
if wildBattle?
PBDebug.log("#{@sideSizes[0]}v#{@sideSizes[1]} battle isn't possible " +
"(#{side1counts} player-side teams versus #{side2counts[0]} wild Pokémon)")
newSize = @sideSizes[0]-1
else
PBDebug.log("#{@sideSizes[0]}v#{@sideSizes[1]} battle isn't possible " +
"(#{side1counts} player-side teams versus #{side2counts} opposing teams)")
newSize = @sideSizes.max-1
end
if newSize==0
raise _INTL("Couldn't lower either side's size any further, battle isn't possible")
end
for side in 0...2
next if side==1 && wildBattle? # Wild Pokémon's side size is fixed
next if @sideSizes[side]==1 || newSize>@sideSizes[side]
@sideSizes[side] = newSize
end
PBDebug.log("Trying #{@sideSizes[0]}v#{@sideSizes[1]} battle instead")
end
end
#=============================================================================
# Set up all battlers
#=============================================================================
def pbCreateBattler(idxBattler,pkmn,idxParty)
if !@battlers[idxBattler].nil?
raise _INTL("Battler index {1} already exists",idxBattler)
end
@battlers[idxBattler] = PokeBattle_Battler.new(self,idxBattler)
@positions[idxBattler] = PokeBattle_ActivePosition.new
pbClearChoice(idxBattler)
@successStates[idxBattler] = PokeBattle_SuccessState.new
@battlers[idxBattler].pbInitialize(pkmn,idxParty)
end
def pbSetUpSides
ret = [[],[]]
for side in 0...2
# Set up wild Pokémon
if side==1 && wildBattle?
pbParty(1).each_with_index do |pkmn,idxPkmn|
pbCreateBattler(2*idxPkmn+side,pkmn,idxPkmn)
# Changes the Pokémon's form upon entering battle (if it should)
@peer.pbOnEnteringBattle(self,pkmn,true)
pbSetSeen(@battlers[2*idxPkmn+side])
@usedInBattle[side][idxPkmn] = true
end
next
end
# Set up player's Pokémon and trainers' Pokémon
trainer = (side==0) ? @player : @opponent
requireds = []
# Find out how many Pokémon each trainer on side needs to have
for i in 0...@sideSizes[side]
idxTrainer = pbGetOwnerIndexFromBattlerIndex(i*2+side)
requireds[idxTrainer] = 0 if requireds[idxTrainer].nil?
requireds[idxTrainer] += 1
end
# For each trainer in turn, find the needed number of Pokémon for them to
# send out, and initialize them
battlerNumber = 0
trainer.each_with_index do |_t,idxTrainer|
ret[side][idxTrainer] = []
eachInTeam(side,idxTrainer) do |pkmn,idxPkmn|
next if !pkmn.able?
idxBattler = 2*battlerNumber+side
pbCreateBattler(idxBattler,pkmn,idxPkmn)
ret[side][idxTrainer].push(idxBattler)
battlerNumber += 1
break if ret[side][idxTrainer].length>=requireds[idxTrainer]
end
end
end
return ret
end
#=============================================================================
# Send out all battlers at the start of battle
#=============================================================================
def pbStartBattleSendOut(sendOuts)
# "Want to battle" messages
if wildBattle?
foeParty = pbParty(1)
case foeParty.length
when 1
pbDisplayPaused(_INTL("Oh! A wild {1} appeared!",foeParty[0].name))
when 2
pbDisplayPaused(_INTL("Oh! A wild {1} and {2} appeared!",foeParty[0].name,
foeParty[1].name))
when 3
pbDisplayPaused(_INTL("Oh! A wild {1}, {2} and {3} appeared!",foeParty[0].name,
foeParty[1].name,foeParty[2].name))
end
else # Trainer battle
case @opponent.length
when 1
pbDisplayPaused(_INTL("You are challenged by {1}!",@opponent[0].full_name))
when 2
pbDisplayPaused(_INTL("You are challenged by {1} and {2}!",@opponent[0].full_name,
@opponent[1].full_name))
when 3
pbDisplayPaused(_INTL("You are challenged by {1}, {2} and {3}!",
@opponent[0].full_name,@opponent[1].full_name,@opponent[2].full_name))
end
end
# Send out Pokémon (opposing trainers first)
for side in [1,0]
next if side==1 && wildBattle?
msg = ""
toSendOut = []
trainers = (side==0) ? @player : @opponent
# Opposing trainers and partner trainers's messages about sending out Pokémon
trainers.each_with_index do |t,i|
next if side==0 && i==0 # The player's message is shown last
msg += "\r\n" if msg.length>0
sent = sendOuts[side][i]
case sent.length
when 1
msg += _INTL("{1} sent out {2}!",t.full_name,@battlers[sent[0]].name)
when 2
msg += _INTL("{1} sent out {2} and {3}!",t.full_name,
@battlers[sent[0]].name,@battlers[sent[1]].name)
when 3
msg += _INTL("{1} sent out {2}, {3} and {4}!",t.full_name,
@battlers[sent[0]].name,@battlers[sent[1]].name,@battlers[sent[2]].name)
end
toSendOut.concat(sent)
end
# The player's message about sending out Pokémon
if side==0
msg += "\r\n" if msg.length>0
sent = sendOuts[side][0]
case sent.length
when 1
msg += _INTL("Go! {1}!",@battlers[sent[0]].name)
when 2
msg += _INTL("Go! {1} and {2}!",@battlers[sent[0]].name,@battlers[sent[1]].name)
when 3
msg += _INTL("Go! {1}, {2} and {3}!",@battlers[sent[0]].name,
@battlers[sent[1]].name,@battlers[sent[2]].name)
end
toSendOut.concat(sent)
end
pbDisplayBrief(msg) if msg.length>0
# The actual sending out of Pokémon
animSendOuts = []
toSendOut.each do |idxBattler|
animSendOuts.push([idxBattler,@battlers[idxBattler].pokemon])
end
pbSendOut(animSendOuts,true)
end
end
#=============================================================================
# Start a battle
#=============================================================================
def pbStartBattle
PBDebug.log("")
PBDebug.log("******************************************")
logMsg = "[Started battle] "
if @sideSizes[0]==1 && @sideSizes[1]==1
logMsg += "Single "
elsif @sideSizes[0]==2 && @sideSizes[1]==2
logMsg += "Double "
elsif @sideSizes[0]==3 && @sideSizes[1]==3
logMsg += "Triple "
else
logMsg += "#{@sideSizes[0]}v#{@sideSizes[1]} "
end
logMsg += "wild " if wildBattle?
logMsg += "trainer " if trainerBattle?
logMsg += "battle (#{@player.length} trainer(s) vs. "
logMsg += "#{pbParty(1).length} wild Pokémon)" if wildBattle?
logMsg += "#{@opponent.length} trainer(s))" if trainerBattle?
PBDebug.log(logMsg)
pbEnsureParticipants
begin
pbStartBattleCore
rescue BattleAbortedException
@decision = 0
@scene.pbEndBattle(@decision)
end
return @decision
end
def pbStartBattleCore
# Set up the battlers on each side
sendOuts = pbSetUpSides
# Create all the sprites and play the battle intro animation
@scene.pbStartBattle(self)
# Show trainers on both sides sending out Pokémon
pbStartBattleSendOut(sendOuts)
# Weather announcement
weather_data = GameData::BattleWeather.try_get(@field.weather)
pbCommonAnimation(weather_data.animation) if weather_data
case @field.weather
when :Sun then pbDisplay(_INTL("The sunlight is strong."))
when :Rain then pbDisplay(_INTL("It is raining."))
when :Sandstorm then pbDisplay(_INTL("A sandstorm is raging."))
when :Hail then pbDisplay(_INTL("Hail is falling."))
when :HarshSun then pbDisplay(_INTL("The sunlight is extremely harsh."))
when :HeavyRain then pbDisplay(_INTL("It is raining heavily."))
when :StrongWinds then pbDisplay(_INTL("The wind is strong."))
when :ShadowSky then pbDisplay(_INTL("The sky is shadowy."))
end
# Terrain announcement
terrain_data = GameData::BattleTerrain.try_get(@field.terrain)
pbCommonAnimation(terrain_data.animation) if terrain_data
case @field.terrain
when :Electric
pbDisplay(_INTL("An electric current runs across the battlefield!"))
when :Grassy
pbDisplay(_INTL("Grass is covering the battlefield!"))
when :Misty
pbDisplay(_INTL("Mist swirls about the battlefield!"))
when :Psychic
pbDisplay(_INTL("The battlefield is weird!"))
end
# Abilities upon entering battle
pbOnActiveAll
# Main battle loop
pbBattleLoop
end
#=============================================================================
# Main battle loop
#=============================================================================
def pbBattleLoop
@turnCount = 0
loop do # Now begin the battle loop
PBDebug.log("")
PBDebug.log("***Round #{@turnCount+1}***")
if @debug && @turnCount>=100
@decision = pbDecisionOnTime
PBDebug.log("")
PBDebug.log("***Undecided after 100 rounds, aborting***")
pbAbort
break
end
PBDebug.log("")
# Command phase
PBDebug.logonerr { pbCommandPhase }
break if @decision>0
# Attack phase
PBDebug.logonerr { pbAttackPhase }
break if @decision>0
# End of round phase
PBDebug.logonerr { pbEndOfRoundPhase }
break if @decision>0
@turnCount += 1
end
pbEndOfBattle
end
#=============================================================================
# End of battle
#=============================================================================
def pbGainMoney
return if !@internalBattle || !@moneyGain
# Money rewarded from opposing trainers
if trainerBattle?
tMoney = 0
@opponent.each_with_index do |t,i|
tMoney += pbMaxLevelInTeam(1, i) * t.base_money
end
tMoney *= 2 if @field.effects[PBEffects::AmuletCoin]
tMoney *= 2 if @field.effects[PBEffects::HappyHour]
oldMoney = pbPlayer.money
pbPlayer.money += tMoney
moneyGained = pbPlayer.money-oldMoney
if moneyGained>0
pbDisplayPaused(_INTL("You got ${1} for winning!",moneyGained.to_s_formatted))
end
end
# Pick up money scattered by Pay Day
if @field.effects[PBEffects::PayDay]>0
@field.effects[PBEffects::PayDay] *= 2 if @field.effects[PBEffects::AmuletCoin]
@field.effects[PBEffects::PayDay] *= 2 if @field.effects[PBEffects::HappyHour]
oldMoney = pbPlayer.money
pbPlayer.money += @field.effects[PBEffects::PayDay]
moneyGained = pbPlayer.money-oldMoney
if moneyGained>0
pbDisplayPaused(_INTL("You picked up ${1}!",moneyGained.to_s_formatted))
end
end
end
def pbLoseMoney
return if !@internalBattle || !@moneyGain
return if $game_switches[Settings::NO_MONEY_LOSS]
maxLevel = pbMaxLevelInTeam(0,0) # Player's Pokémon only, not partner's
multiplier = [8,16,24,36,48,64,80,100,120]
idxMultiplier = [pbPlayer.badge_count, multiplier.length - 1].min
tMoney = maxLevel*multiplier[idxMultiplier]
tMoney = pbPlayer.money if tMoney>pbPlayer.money
oldMoney = pbPlayer.money
pbPlayer.money -= tMoney
moneyLost = oldMoney-pbPlayer.money
if moneyLost>0
if trainerBattle?
pbDisplayPaused(_INTL("You gave ${1} to the winner...",moneyLost.to_s_formatted))
else
pbDisplayPaused(_INTL("You panicked and dropped ${1}...",moneyLost.to_s_formatted))
end
end
end
def pbEndOfBattle
oldDecision = @decision
@decision = 4 if @decision==1 && wildBattle? && @caughtPokemon.length>0
case oldDecision
##### WIN #####
when 1
PBDebug.log("")
PBDebug.log("***Player won***")
if trainerBattle?
@scene.pbTrainerBattleSuccess
case @opponent.length
when 1
pbDisplayPaused(_INTL("You defeated {1}!",@opponent[0].full_name))
when 2
pbDisplayPaused(_INTL("You defeated {1} and {2}!",@opponent[0].full_name,
@opponent[1].full_name))
when 3
pbDisplayPaused(_INTL("You defeated {1}, {2} and {3}!",@opponent[0].full_name,
@opponent[1].full_name,@opponent[2].full_name))
end
@opponent.each_with_index do |_t,i|
@scene.pbShowOpponent(i)
msg = (@endSpeeches[i] && @endSpeeches[i]!="") ? @endSpeeches[i] : "..."
pbDisplayPaused(msg.gsub(/\\[Pp][Nn]/,pbPlayer.name))
end
end
# Gain money from winning a trainer battle, and from Pay Day
pbGainMoney if @decision!=4
# Hide remaining trainer
@scene.pbShowOpponent(@opponent.length) if trainerBattle? && @caughtPokemon.length>0
##### LOSE, DRAW #####
when 2, 5
PBDebug.log("")
PBDebug.log("***Player lost***") if @decision==2
PBDebug.log("***Player drew with opponent***") if @decision==5
if @internalBattle
pbDisplayPaused(_INTL("You have no more Pokémon that can fight!"))
if trainerBattle?
case @opponent.length
when 1
pbDisplayPaused(_INTL("You lost against {1}!",@opponent[0].full_name))
when 2
pbDisplayPaused(_INTL("You lost against {1} and {2}!",
@opponent[0].full_name,@opponent[1].full_name))
when 3
pbDisplayPaused(_INTL("You lost against {1}, {2} and {3}!",
@opponent[0].full_name,@opponent[1].full_name,@opponent[2].full_name))
end
end
# Lose money from losing a battle
pbLoseMoney
pbDisplayPaused(_INTL("You blacked out!")) if !@canLose
elsif @decision==2
if @opponent
@opponent.each_with_index do |_t,i|
@scene.pbShowOpponent(i)
msg = (@endSpeechesWin[i] && @endSpeechesWin[i]!="") ? @endSpeechesWin[i] : "..."
pbDisplayPaused(msg.gsub(/\\[Pp][Nn]/,pbPlayer.name))
end
end
end
##### CAUGHT WILD POKÉMON #####
when 4
@scene.pbWildBattleSuccess if !Settings::GAIN_EXP_FOR_CAPTURE
end
# Register captured Pokémon in the Pokédex, and store them
pbRecordAndStoreCaughtPokemon
# Collect Pay Day money in a wild battle that ended in a capture
pbGainMoney if @decision==4
# Pass on Pokérus within the party
if @internalBattle
infected = []
$Trainer.party.each_with_index do |pkmn,i|
infected.push(i) if pkmn.pokerusStage==1
end
infected.each do |idxParty|
strain = $Trainer.party[idxParty].pokerusStrain
if idxParty>0 && $Trainer.party[idxParty-1].pokerusStage==0
$Trainer.party[idxParty-1].givePokerus(strain) if rand(3)==0 # 33%
end
if idxParty<$Trainer.party.length-1 && $Trainer.party[idxParty+1].pokerusStage==0
$Trainer.party[idxParty+1].givePokerus(strain) if rand(3)==0 # 33%
end
end
end
# Clean up battle stuff
@scene.pbEndBattle(@decision)
@battlers.each do |b|
next if !b
pbCancelChoice(b.index) # Restore unused items to Bag
BattleHandlers.triggerAbilityOnSwitchOut(b.ability,b,true) if b.abilityActive?
end
pbParty(0).each_with_index do |pkmn,i|
next if !pkmn
@peer.pbOnLeavingBattle(self,pkmn,@usedInBattle[0][i],true) # Reset form
pkmn.item = @initialItems[0][i]
end
return @decision
end
#=============================================================================
# Judging
#=============================================================================
def pbJudgeCheckpoint(user,move=nil); end
def pbDecisionOnTime
counts = [0,0]
hpTotals = [0,0]
for side in 0...2
pbParty(side).each do |pkmn|
next if !pkmn || !pkmn.able?
counts[side] += 1
hpTotals[side] += pkmn.hp
end
end
return 1 if counts[0]>counts[1] # Win (player has more able Pokémon)
return 2 if counts[0]<counts[1] # Loss (foe has more able Pokémon)
return 1 if hpTotals[0]>hpTotals[1] # Win (player has more HP in total)
return 2 if hpTotals[0]<hpTotals[1] # Loss (foe has more HP in total)
return 5 # Draw
end
# Unused
def pbDecisionOnTime2
counts = [0,0]
hpTotals = [0,0]
for side in 0...2
pbParty(side).each do |pkmn|
next if !pkmn || !pkmn.able?
counts[side] += 1
hpTotals[side] += 100*pkmn.hp/pkmn.totalhp
end
hpTotals[side] /= counts[side] if counts[side]>1
end
return 1 if counts[0]>counts[1] # Win (player has more able Pokémon)
return 2 if counts[0]<counts[1] # Loss (foe has more able Pokémon)
return 1 if hpTotals[0]>hpTotals[1] # Win (player has a bigger average HP %)
return 2 if hpTotals[0]<hpTotals[1] # Loss (foe has a bigger average HP %)
return 5 # Draw
end
def pbDecisionOnDraw; return 5; end # Draw
def pbJudge
fainted1 = pbAllFainted?(0)
fainted2 = pbAllFainted?(1)
if fainted1 && fainted2; @decision = pbDecisionOnDraw # Draw
elsif fainted1; @decision = 2 # Loss
elsif fainted2; @decision = 1 # Win
end
end
end

View File

@@ -0,0 +1,263 @@
class PokeBattle_Battle
#=============================================================================
# Gaining Experience
#=============================================================================
def pbGainExp
# Play wild victory music if it's the end of the battle (has to be here)
@scene.pbWildBattleSuccess if wildBattle? && pbAllFainted?(1) && !pbAllFainted?(0)
return if !@internalBattle || !@expGain
# Go through each battler in turn to find the Pokémon that participated in
# battle against it, and award those Pokémon Exp/EVs
expAll = (GameData::Item.exists?(:EXPALL) && $PokemonBag.pbHasItem?(:EXPALL))
p1 = pbParty(0)
@battlers.each do |b|
next unless b && b.opposes? # Can only gain Exp from fainted foes
next if b.participants.length==0
next unless b.fainted? || b.captured
# Count the number of participants
numPartic = 0
b.participants.each do |partic|
next unless p1[partic] && p1[partic].able? && pbIsOwner?(0,partic)
numPartic += 1
end
# Find which Pokémon have an Exp Share
expShare = []
if !expAll
eachInTeam(0,0) do |pkmn,i|
next if !pkmn.able?
next if !pkmn.hasItem?(:EXPSHARE) && GameData::Item.try_get(@initialItems[0][i]) != :EXPSHARE
expShare.push(i)
end
end
# Calculate EV and Exp gains for the participants
if numPartic>0 || expShare.length>0 || expAll
# Gain EVs and Exp for participants
eachInTeam(0,0) do |pkmn,i|
next if !pkmn.able?
next unless b.participants.include?(i) || expShare.include?(i)
pbGainEVsOne(i,b)
pbGainExpOne(i,b,numPartic,expShare,expAll)
end
# Gain EVs and Exp for all other Pokémon because of Exp All
if expAll
showMessage = true
eachInTeam(0,0) do |pkmn,i|
next if !pkmn.able?
next if b.participants.include?(i) || expShare.include?(i)
pbDisplayPaused(_INTL("Your party Pokémon in waiting also got Exp. Points!")) if showMessage
showMessage = false
pbGainEVsOne(i,b)
pbGainExpOne(i,b,numPartic,expShare,expAll,false)
end
end
end
# Clear the participants array
b.participants = []
end
end
def pbGainEVsOne(idxParty,defeatedBattler)
pkmn = pbParty(0)[idxParty] # The Pokémon gaining EVs from defeatedBattler
evYield = defeatedBattler.pokemon.evYield
# Num of effort points pkmn already has
evTotal = 0
GameData::Stat.each_main { |s| evTotal += pkmn.ev[s.id] }
# Modify EV yield based on pkmn's held item
if !BattleHandlers.triggerEVGainModifierItem(pkmn.item,pkmn,evYield)
BattleHandlers.triggerEVGainModifierItem(@initialItems[0][idxParty],pkmn,evYield)
end
# Double EV gain because of Pokérus
if pkmn.pokerusStage>=1 # Infected or cured
evYield.each_key { |stat| evYield[stat] *= 2 }
end
# Gain EVs for each stat in turn
if pkmn.shadowPokemon? && pkmn.saved_ev
pkmn.saved_ev.each_value { |e| evTotal += e }
GameData::Stat.each_main do |s|
evGain = evYield[s.id].clamp(0, Pokemon::EV_STAT_LIMIT - pkmn.ev[s.id] - pkmn.saved_ev[s.id])
evGain = evGain.clamp(0, Pokemon::EV_LIMIT - evTotal)
pkmn.saved_ev[s.id] += evGain
evTotal += evGain
end
else
GameData::Stat.each_main do |s|
evGain = evYield[s.id].clamp(0, Pokemon::EV_STAT_LIMIT - pkmn.ev[s.id])
evGain = evGain.clamp(0, Pokemon::EV_LIMIT - evTotal)
pkmn.ev[s.id] += evGain
evTotal += evGain
end
end
end
def pbGainExpOne(idxParty,defeatedBattler,numPartic,expShare,expAll,showMessages=true)
pkmn = pbParty(0)[idxParty] # The Pokémon gaining EVs from defeatedBattler
growth_rate = pkmn.growth_rate
# Don't bother calculating if gainer is already at max Exp
if pkmn.exp>=growth_rate.maximum_exp
pkmn.calc_stats # To ensure new EVs still have an effect
return
end
isPartic = defeatedBattler.participants.include?(idxParty)
hasExpShare = expShare.include?(idxParty)
level = defeatedBattler.level
# Main Exp calculation
exp = 0
a = level*defeatedBattler.pokemon.base_exp
if expShare.length>0 && (isPartic || hasExpShare)
if numPartic==0 # No participants, all Exp goes to Exp Share holders
exp = a / (Settings::SPLIT_EXP_BETWEEN_GAINERS ? expShare.length : 1)
elsif Settings::SPLIT_EXP_BETWEEN_GAINERS # Gain from participating and/or Exp Share
exp = a/(2*numPartic) if isPartic
exp += a/(2*expShare.length) if hasExpShare
else # Gain from participating and/or Exp Share (Exp not split)
exp = (isPartic) ? a : a/2
end
elsif isPartic # Participated in battle, no Exp Shares held by anyone
exp = a / (Settings::SPLIT_EXP_BETWEEN_GAINERS ? numPartic : 1)
elsif expAll # Didn't participate in battle, gaining Exp due to Exp All
# NOTE: Exp All works like the Exp Share from Gen 6+, not like the Exp All
# from Gen 1, i.e. Exp isn't split between all Pokémon gaining it.
exp = a/2
end
return if exp<=0
# Pokémon gain more Exp from trainer battles
exp = (exp*1.5).floor if trainerBattle?
# Scale the gained Exp based on the gainer's level (or not)
if Settings::SCALED_EXP_FORMULA
exp /= 5
levelAdjust = (2*level+10.0)/(pkmn.level+level+10.0)
levelAdjust = levelAdjust**5
levelAdjust = Math.sqrt(levelAdjust)
exp *= levelAdjust
exp = exp.floor
exp += 1 if isPartic || hasExpShare
else
exp /= 7
end
# Foreign Pokémon gain more Exp
isOutsider = (pkmn.owner.id != pbPlayer.id ||
(pkmn.owner.language != 0 && pkmn.owner.language != pbPlayer.language))
if isOutsider
if pkmn.owner.language != 0 && pkmn.owner.language != pbPlayer.language
exp = (exp*1.7).floor
else
exp = (exp*1.5).floor
end
end
# Modify Exp gain based on pkmn's held item
i = BattleHandlers.triggerExpGainModifierItem(pkmn.item,pkmn,exp)
if i<0
i = BattleHandlers.triggerExpGainModifierItem(@initialItems[0][idxParty],pkmn,exp)
end
exp = i if i>=0
# Make sure Exp doesn't exceed the maximum
expFinal = growth_rate.add_exp(pkmn.exp, exp)
expGained = expFinal-pkmn.exp
return if expGained<=0
# "Exp gained" message
if showMessages
if isOutsider
pbDisplayPaused(_INTL("{1} got a boosted {2} Exp. Points!",pkmn.name,expGained))
else
pbDisplayPaused(_INTL("{1} got {2} Exp. Points!",pkmn.name,expGained))
end
end
curLevel = pkmn.level
newLevel = growth_rate.level_from_exp(expFinal)
if newLevel<curLevel
debugInfo = "Levels: #{curLevel}->#{newLevel} | Exp: #{pkmn.exp}->#{expFinal} | gain: #{expGained}"
raise RuntimeError.new(
_INTL("{1}'s new level is less than its\r\ncurrent level, which shouldn't happen.\r\n[Debug: {2}]",
pkmn.name,debugInfo))
end
# Give Exp
if pkmn.shadowPokemon?
pkmn.exp += expGained
return
end
tempExp1 = pkmn.exp
battler = pbFindBattler(idxParty)
loop do # For each level gained in turn...
# EXP Bar animation
levelMinExp = growth_rate.minimum_exp_for_level(curLevel)
levelMaxExp = growth_rate.minimum_exp_for_level(curLevel + 1)
tempExp2 = (levelMaxExp<expFinal) ? levelMaxExp : expFinal
pkmn.exp = tempExp2
@scene.pbEXPBar(battler,levelMinExp,levelMaxExp,tempExp1,tempExp2)
tempExp1 = tempExp2
curLevel += 1
if curLevel>newLevel
# Gained all the Exp now, end the animation
pkmn.calc_stats
battler.pbUpdate(false) if battler
@scene.pbRefreshOne(battler.index) if battler
break
end
# Levelled up
pbCommonAnimation("LevelUp",battler) if battler
oldTotalHP = pkmn.totalhp
oldAttack = pkmn.attack
oldDefense = pkmn.defense
oldSpAtk = pkmn.spatk
oldSpDef = pkmn.spdef
oldSpeed = pkmn.speed
if battler && battler.pokemon
battler.pokemon.changeHappiness("levelup")
end
pkmn.calc_stats
battler.pbUpdate(false) if battler
@scene.pbRefreshOne(battler.index) if battler
pbDisplayPaused(_INTL("{1} grew to Lv. {2}!",pkmn.name,curLevel))
@scene.pbLevelUp(pkmn,battler,oldTotalHP,oldAttack,oldDefense,
oldSpAtk,oldSpDef,oldSpeed)
# Learn all moves learned at this level
moveList = pkmn.getMoveList
moveList.each { |m| pbLearnMove(idxParty,m[1]) if m[0]==curLevel }
end
end
#=============================================================================
# Learning a move
#=============================================================================
def pbLearnMove(idxParty,newMove)
pkmn = pbParty(0)[idxParty]
return if !pkmn
pkmnName = pkmn.name
battler = pbFindBattler(idxParty)
moveName = GameData::Move.get(newMove).name
# Find a space for the new move in pkmn's moveset and learn it
for i in 0...Pokemon::MAX_MOVES
m = pkmn.moves[i]
return if m && m.id==newMove # Already knows the new move
pkmn.moves[i] = Pokemon::Move.new(newMove)
battler.moves[i] = PokeBattle_Move.from_pokemon_move(self, pkmn.moves[i]) if battler
pbDisplay(_INTL("{1} learned {2}!",pkmnName,moveName)) { pbSEPlay("Pkmn move learnt") }
battler.pbCheckFormOnMovesetChange if battler
return
end
# pkmn already knows four moves, need to forget one to learn newMove
loop do
pbDisplayPaused(_INTL("{1} wants to learn {2}, but it already knows four moves.",pkmnName,moveName))
if pbDisplayConfirm(_INTL("Forget a move to learn {1}?",moveName))
pbDisplayPaused(_INTL("Which move should be forgotten?"))
forgetMove = @scene.pbForgetMove(pkmn,newMove)
if forgetMove>=0
oldMoveName = pkmn.moves[forgetMove].name
pkmn.moves[forgetMove] = Pokemon::Move.new(newMove) # Replaces current/total PP
battler.moves[forgetMove] = PokeBattle_Move.from_pokemon_move(self, pkmn.moves[forgetMove]) if battler
pbDisplayPaused(_INTL("1, 2, and... ... ... Ta-da!"))
pbDisplayPaused(_INTL("{1} forgot how to use {2}. And...",pkmnName,oldMoveName))
pbDisplay(_INTL("{1} learned {2}!",pkmnName,moveName)) { pbSEPlay("Pkmn move learnt") }
battler.pbCheckFormOnMovesetChange if battler
break
elsif pbDisplayConfirm(_INTL("Give up on learning {1}?",moveName))
pbDisplay(_INTL("{1} did not learn {2}.",pkmnName,moveName))
break
end
elsif pbDisplayConfirm(_INTL("Give up on learning {1}?",moveName))
pbDisplay(_INTL("{1} did not learn {2}.",pkmnName,moveName))
break
end
end
end
end

View File

@@ -0,0 +1,249 @@
class PokeBattle_Battle
#=============================================================================
# Choosing a move/target
#=============================================================================
def pbCanChooseMove?(idxBattler,idxMove,showMessages,sleepTalk=false)
battler = @battlers[idxBattler]
move = battler.moves[idxMove]
return false unless move
if move.pp==0 && move.total_pp>0 && !sleepTalk
pbDisplayPaused(_INTL("There's no PP left for this move!")) if showMessages
return false
end
if battler.effects[PBEffects::Encore]>0
idxEncoredMove = battler.pbEncoredMoveIndex
return false if idxEncoredMove>=0 && idxMove!=idxEncoredMove
end
return battler.pbCanChooseMove?(move,true,showMessages,sleepTalk)
end
def pbCanChooseAnyMove?(idxBattler,sleepTalk=false)
battler = @battlers[idxBattler]
battler.eachMoveWithIndex do |m,i|
next if m.pp==0 && m.total_pp>0 && !sleepTalk
if battler.effects[PBEffects::Encore]>0
idxEncoredMove = battler.pbEncoredMoveIndex
next if idxEncoredMove>=0 && i!=idxEncoredMove
end
next if !battler.pbCanChooseMove?(m,true,false,sleepTalk)
return true
end
return false
end
# Called when the Pokémon is Encored, or if it can't use any of its moves.
# Makes the Pokémon use the Encored move (if Encored), or Struggle.
def pbAutoChooseMove(idxBattler,showMessages=true)
battler = @battlers[idxBattler]
if battler.fainted?
pbClearChoice(idxBattler)
return true
end
# Encore
idxEncoredMove = battler.pbEncoredMoveIndex
if idxEncoredMove>=0 && pbCanChooseMove?(idxBattler,idxEncoredMove,false)
encoreMove = battler.moves[idxEncoredMove]
@choices[idxBattler][0] = :UseMove # "Use move"
@choices[idxBattler][1] = idxEncoredMove # Index of move to be used
@choices[idxBattler][2] = encoreMove # PokeBattle_Move object
@choices[idxBattler][3] = -1 # No target chosen yet
return true if singleBattle?
if pbOwnedByPlayer?(idxBattler)
if showMessages
pbDisplayPaused(_INTL("{1} has to use {2}!",battler.name,encoreMove.name))
end
return pbChooseTarget(battler,encoreMove)
end
return true
end
# Struggle
if pbOwnedByPlayer?(idxBattler) && showMessages
pbDisplayPaused(_INTL("{1} has no moves left!",battler.name))
end
@choices[idxBattler][0] = :UseMove # "Use move"
@choices[idxBattler][1] = -1 # Index of move to be used
@choices[idxBattler][2] = @struggle # Struggle PokeBattle_Move object
@choices[idxBattler][3] = -1 # No target chosen yet
return true
end
def pbRegisterMove(idxBattler,idxMove,showMessages=true)
battler = @battlers[idxBattler]
move = battler.moves[idxMove]
return false if !pbCanChooseMove?(idxBattler,idxMove,showMessages)
@choices[idxBattler][0] = :UseMove # "Use move"
@choices[idxBattler][1] = idxMove # Index of move to be used
@choices[idxBattler][2] = move # PokeBattle_Move object
@choices[idxBattler][3] = -1 # No target chosen yet
return true
end
def pbChoseMove?(idxBattler,moveID)
return false if !@battlers[idxBattler] || @battlers[idxBattler].fainted?
if @choices[idxBattler][0]==:UseMove && @choices[idxBattler][1]
return @choices[idxBattler][2].id == moveID
end
return false
end
def pbChoseMoveFunctionCode?(idxBattler,code)
return false if @battlers[idxBattler].fainted?
idxMove = @choices[idxBattler][1]
if @choices[idxBattler][0]==:UseMove && @choices[idxBattler][1]
return @choices[idxBattler][2].function == code
end
return false
end
def pbRegisterTarget(idxBattler,idxTarget)
@choices[idxBattler][3] = idxTarget # Set target of move
end
# Returns whether the idxTarget will be targeted by a move with target_data
# used by a battler in idxUser.
def pbMoveCanTarget?(idxUser,idxTarget,target_data)
return false if target_data.num_targets == 0
case target_data.id
when :NearAlly
return false if opposes?(idxUser,idxTarget)
return false if !nearBattlers?(idxUser,idxTarget)
when :UserOrNearAlly
return true if idxUser==idxTarget
return false if opposes?(idxUser,idxTarget)
return false if !nearBattlers?(idxUser,idxTarget)
when :UserAndAllies
return false if opposes?(idxUser,idxTarget)
when :NearFoe, :RandomNearFoe, :AllNearFoes
return false if !opposes?(idxUser,idxTarget)
return false if !nearBattlers?(idxUser,idxTarget)
when :Foe
return false if !opposes?(idxUser,idxTarget)
when :AllFoes
return false if !opposes?(idxUser,idxTarget)
when :NearOther, :AllNearOthers
return false if !nearBattlers?(idxUser,idxTarget)
when :Other
return false if idxUser==idxTarget
end
return true
end
#=============================================================================
# Turn order calculation (priority)
#=============================================================================
def pbCalculatePriority(fullCalc=false,indexArray=nil)
needRearranging = false
if fullCalc
@priorityTrickRoom = (@field.effects[PBEffects::TrickRoom]>0)
# Recalculate everything from scratch
randomOrder = Array.new(maxBattlerIndex+1) { |i| i }
(randomOrder.length-1).times do |i| # Can't use shuffle! here
r = i+pbRandom(randomOrder.length-i)
randomOrder[i], randomOrder[r] = randomOrder[r], randomOrder[i]
end
@priority.clear
for i in 0..maxBattlerIndex
b = @battlers[i]
next if !b
# [battler, speed, sub-priority, priority, tie-breaker order]
bArray = [b,b.pbSpeed,0,0,randomOrder[i]]
if @choices[b.index][0]==:UseMove || @choices[b.index][0]==:Shift
# Calculate move's priority
if @choices[b.index][0]==:UseMove
move = @choices[b.index][2]
pri = move.priority
if b.abilityActive?
pri = BattleHandlers.triggerPriorityChangeAbility(b.ability,b,move,pri)
end
bArray[3] = pri
@choices[b.index][4] = pri
end
# Calculate sub-priority (first/last within priority bracket)
# NOTE: Going fast beats going slow. A Pokémon with Stall and Quick
# Claw will go first in its priority bracket if Quick Claw
# triggers, regardless of Stall.
subPri = 0
# Abilities (Stall)
if b.abilityActive?
newSubPri = BattleHandlers.triggerPriorityBracketChangeAbility(b.ability,
b,subPri,self)
if subPri!=newSubPri
subPri = newSubPri
b.effects[PBEffects::PriorityAbility] = true
b.effects[PBEffects::PriorityItem] = false
end
end
# Items (Quick Claw, Custap Berry, Lagging Tail, Full Incense)
if b.itemActive?
newSubPri = BattleHandlers.triggerPriorityBracketChangeItem(b.item,
b,subPri,self)
if subPri!=newSubPri
subPri = newSubPri
b.effects[PBEffects::PriorityAbility] = false
b.effects[PBEffects::PriorityItem] = true
end
end
bArray[2] = subPri
end
@priority.push(bArray)
end
needRearranging = true
else
if (@field.effects[PBEffects::TrickRoom]>0)!=@priorityTrickRoom
needRearranging = true
@priorityTrickRoom = (@field.effects[PBEffects::TrickRoom]>0)
end
# Just recheck all battler speeds
@priority.each do |orderArray|
next if !orderArray
next if indexArray && !indexArray.include?(orderArray[0].index)
oldSpeed = orderArray[1]
orderArray[1] = orderArray[0].pbSpeed
needRearranging = true if orderArray[1]!=oldSpeed
end
end
# Reorder the priority array
if needRearranging
@priority.sort! { |a,b|
if a[3]!=b[3]
# Sort by priority (highest value first)
b[3]<=>a[3]
elsif a[2]!=b[2]
# Sort by sub-priority (highest value first)
b[2]<=>a[2]
elsif @priorityTrickRoom
# Sort by speed (lowest first), and use tie-breaker if necessary
(a[1]==b[1]) ? b[4]<=>a[4] : a[1]<=>b[1]
else
# Sort by speed (highest first), and use tie-breaker if necessary
(a[1]==b[1]) ? b[4]<=>a[4] : b[1]<=>a[1]
end
}
# Write the priority order to the debug log
logMsg = (fullCalc) ? "[Round order] " : "[Round order recalculated] "
comma = false
@priority.each do |orderArray|
logMsg += ", " if comma
logMsg += "#{orderArray[0].pbThis(comma)} (#{orderArray[0].index})"
comma = true
end
PBDebug.log(logMsg)
end
end
def pbPriority(onlySpeedSort=false)
ret = []
if onlySpeedSort
# Sort battlers by their speed stats and tie-breaker order only.
tempArray = []
@priority.each { |pArray| tempArray.push([pArray[0],pArray[1],pArray[4]]) }
tempArray.sort! { |a,b| (a[1]==b[1]) ? b[2]<=>a[2] : b[1]<=>a[1] }
tempArray.each { |tArray| ret.push(tArray[0]) }
else
# Sort battlers by priority, sub-priority and their speed. Ties are
# resolved in the same way each time this method is called in a round.
@priority.each { |pArray| ret.push(pArray[0]) if !pArray[0].fainted? }
end
return ret
end
end

View File

@@ -0,0 +1,412 @@
class PokeBattle_Battle
#=============================================================================
# Choosing Pokémon to switch
#=============================================================================
# Checks whether the replacement Pokémon (at party index idxParty) can enter
# 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)
return true if idxParty<0
party = pbParty(idxBattler)
return false if idxParty>=party.length
return false if !party[idxParty]
if party[idxParty].egg?
partyScene.pbDisplay(_INTL("An Egg can't battle!")) if partyScene
return false
end
if !pbIsOwner?(idxBattler,idxParty)
owner = pbGetOwnerFromPartyIndex(idxBattler,idxParty)
partyScene.pbDisplay(_INTL("You can't switch {1}'s Pokémon with one of yours!",
owner.name)) if partyScene
return false
end
if party[idxParty].fainted?
partyScene.pbDisplay(_INTL("{1} has no energy left to battle!",
party[idxParty].name)) if partyScene
return false
end
if pbFindBattler(idxParty,idxBattler)
partyScene.pbDisplay(_INTL("{1} is already in battle!",
party[idxParty].name)) if partyScene
return false
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 !pbCanSwitchLax?(idxBattler,idxParty,partyScene)
# Make sure another battler isn't already choosing to switch to the party
# Pokémon
eachSameSideBattler(idxBattler) 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)) if partyScene
return false
end
# Check whether battler can switch out
battler = @battlers[idxBattler]
return true if battler.fainted?
# Ability/item effects that allow switching no matter what
if battler.abilityActive?
if BattleHandlers.triggerCertainSwitchingUserAbility(battler.ability,battler,self)
return true
end
end
if battler.itemActive?
if BattleHandlers.triggerCertainSwitchingUserItem(battler.item,battler,self)
return true
end
end
# Other certain switching effects
return true if Settings::MORE_TYPE_EFFECTS && battler.pbHasType?(:GHOST)
# Other certain trapping effects
if battler.effects[PBEffects::Trapping]>0 ||
battler.effects[PBEffects::MeanLook]>=0 ||
battler.effects[PBEffects::Ingrain] ||
@field.effects[PBEffects::FairyLock]>0
partyScene.pbDisplay(_INTL("{1} can't be switched out!",battler.pbThis)) if partyScene
return false
end
# Trapping abilities/items
eachOtherSideBattler(idxBattler) do |b|
next if !b.abilityActive?
if BattleHandlers.triggerTrappingTargetAbility(b.ability,battler,b,self)
partyScene.pbDisplay(_INTL("{1}'s {2} prevents switching!",
b.pbThis,b.abilityName)) if partyScene
return false
end
end
eachOtherSideBattler(idxBattler) do |b|
next if !b.itemActive?
if BattleHandlers.triggerTrappingTargetItem(b.item,battler,b,self)
partyScene.pbDisplay(_INTL("{1}'s {2} prevents switching!",
b.pbThis,b.itemName)) if partyScene
return false
end
end
return true
end
def pbCanChooseNonActive?(idxBattler)
pbParty(idxBattler).each_with_index do |_pkmn,i|
return true if pbCanSwitchLax?(idxBattler,i)
end
return false
end
def pbRegisterSwitch(idxBattler,idxParty)
return false if !pbCanSwitch?(idxBattler,idxParty)
@choices[idxBattler][0] = :SwitchOut
@choices[idxBattler][1] = idxParty # Party index of Pokémon to switch in
@choices[idxBattler][2] = nil
return true
end
#=============================================================================
# Open the party screen and potentially pick a replacement Pokémon (or AI
# chooses replacement)
#=============================================================================
# Open party screen and potentially choose a Pokémon to switch with. Used in
# all instances where the party screen is opened.
def pbPartyScreen(idxBattler,checkLaxOnly=false,canCancel=false,shouldRegister=false)
ret = -1
@scene.pbPartyScreen(idxBattler,canCancel) { |idxParty,partyScene|
if checkLaxOnly
next false if !pbCanSwitchLax?(idxBattler,idxParty,partyScene)
else
next false if !pbCanSwitch?(idxBattler,idxParty,partyScene)
end
if shouldRegister
next false if idxParty<0 || !pbRegisterSwitch(idxBattler,idxParty)
end
ret = idxParty
next true
}
return ret
end
# For choosing a replacement Pokémon when prompted in the middle of other
# things happening (U-turn, Baton Pass, in def pbSwitch).
def pbSwitchInBetween(idxBattler,checkLaxOnly=false,canCancel=false)
return pbPartyScreen(idxBattler,checkLaxOnly,canCancel) if pbOwnedByPlayer?(idxBattler)
return @battleAI.pbDefaultChooseNewEnemy(idxBattler,pbParty(idxBattler))
end
#=============================================================================
# Switching Pokémon
#=============================================================================
# General switching method that checks if any Pokémon need to be sent out and,
# if so, does. Called at the end of each round.
def pbEORSwitch(favorDraws=false)
return if @decision>0 && !favorDraws
return if @decision==5 && favorDraws
pbJudge
return if @decision>0
# Check through each fainted battler to see if that spot can be filled.
switched = []
loop do
switched.clear
@battlers.each do |b|
next if !b || !b.fainted?
idxBattler = b.index
next if !pbCanChooseNonActive?(idxBattler)
if !pbOwnedByPlayer?(idxBattler) # Opponent/ally is switching in
next if wildBattle? && opposes?(idxBattler) # Wild Pokémon can't switch
idxPartyNew = pbSwitchInBetween(idxBattler)
opponent = pbGetOwnerFromBattlerIndex(idxBattler)
# NOTE: The player is only offered the chance to switch their own
# Pokémon when an opponent replaces a fainted Pokémon in single
# battles. In double battles, etc. there is no such offer.
if @internalBattle && @switchStyle && trainerBattle? && pbSideSize(0)==1 &&
opposes?(idxBattler) && !@battlers[0].fainted? && !switched.include?(0) &&
pbCanChooseNonActive?(0) && @battlers[0].effects[PBEffects::Outrage]==0
idxPartyForName = idxPartyNew
enemyParty = pbParty(idxBattler)
if enemyParty[idxPartyNew].ability == :ILLUSION
new_index = pbLastInTeam(idxBattler)
idxPartyForName = new_index if new_index >= 0
end
if pbDisplayConfirm(_INTL("{1} is about to send in {2}. Will you switch your Pokémon?",
opponent.full_name, enemyParty[idxPartyForName].name))
idxPlayerPartyNew = pbSwitchInBetween(0,false,true)
if idxPlayerPartyNew>=0
pbMessageOnRecall(@battlers[0])
pbRecallAndReplace(0,idxPlayerPartyNew)
switched.push(0)
end
end
end
pbRecallAndReplace(idxBattler,idxPartyNew)
switched.push(idxBattler)
elsif trainerBattle? # Player switches in in a trainer battle
idxPlayerPartyNew = pbGetReplacementPokemonIndex(idxBattler) # Owner chooses
pbRecallAndReplace(idxBattler,idxPlayerPartyNew)
switched.push(idxBattler)
else # Player's Pokémon has fainted in a wild battle
switch = false
if !pbDisplayConfirm(_INTL("Use next Pokémon?"))
switch = (pbRun(idxBattler,true)<=0)
else
switch = true
end
if switch
idxPlayerPartyNew = pbGetReplacementPokemonIndex(idxBattler) # Owner chooses
pbRecallAndReplace(idxBattler,idxPlayerPartyNew)
switched.push(idxBattler)
end
end
end
break if switched.length==0
pbPriority(true).each do |b|
b.pbEffectsOnSwitchIn(true) if switched.include?(b.index)
end
end
end
def pbGetReplacementPokemonIndex(idxBattler,random=false)
if random
return -1 if !pbCanSwitch?(idxBattler) # Can battler switch out?
choices = [] # Find all Pokémon that can switch in
eachInTeamFromBattlerIndex(idxBattler) do |_pkmn,i|
choices.push(i) if pbCanSwitchLax?(idxBattler,i)
end
return -1 if choices.length==0
return choices[pbRandom(choices.length)]
else
return pbSwitchInBetween(idxBattler,true)
end
end
# Actually performs the recalling and sending out in all situations.
def pbRecallAndReplace(idxBattler,idxParty,randomReplacement=false,batonPass=false)
@scene.pbRecall(idxBattler) if !@battlers[idxBattler].fainted?
@battlers[idxBattler].pbAbilitiesOnSwitchOut # Inc. primordial weather check
@scene.pbShowPartyLineup(idxBattler&1) if pbSideSize(idxBattler)==1
pbMessagesOnReplace(idxBattler,idxParty) if !randomReplacement
pbReplace(idxBattler,idxParty,batonPass)
end
def pbMessageOnRecall(battler)
if battler.pbOwnedByPlayer?
if battler.hp<=battler.totalhp/4
pbDisplayBrief(_INTL("Good job, {1}! Come back!",battler.name))
elsif battler.hp<=battler.totalhp/2
pbDisplayBrief(_INTL("OK, {1}! Come back!",battler.name))
elsif battler.turnCount>=5
pbDisplayBrief(_INTL("{1}, that's enough! Come back!",battler.name))
elsif battler.turnCount>=2
pbDisplayBrief(_INTL("{1}, come back!",battler.name))
else
pbDisplayBrief(_INTL("{1}, switch out! Come back!",battler.name))
end
else
owner = pbGetOwnerName(battler.index)
pbDisplayBrief(_INTL("{1} withdrew {2}!",owner,battler.name))
end
end
# Only called from def pbRecallAndReplace and Battle Arena's def pbSwitch.
def pbMessagesOnReplace(idxBattler,idxParty)
party = pbParty(idxBattler)
newPkmnName = party[idxParty].name
if party[idxParty].ability == :ILLUSION
new_index = pbLastInTeam(idxBattler)
newPkmnName = party[new_index].name if new_index >= 0
end
if pbOwnedByPlayer?(idxBattler)
opposing = @battlers[idxBattler].pbDirectOpposing
if opposing.fainted? || opposing.hp==opposing.totalhp
pbDisplayBrief(_INTL("You're in charge, {1}!",newPkmnName))
elsif opposing.hp>=opposing.totalhp/2
pbDisplayBrief(_INTL("Go for it, {1}!",newPkmnName))
elsif opposing.hp>=opposing.totalhp/4
pbDisplayBrief(_INTL("Just a little more! Hang in there, {1}!",newPkmnName))
else
pbDisplayBrief(_INTL("Your opponent's weak! Get 'em, {1}!",newPkmnName))
end
else
owner = pbGetOwnerFromBattlerIndex(idxBattler)
pbDisplayBrief(_INTL("{1} sent out {2}!",owner.full_name,newPkmnName))
end
end
# Only called from def pbRecallAndReplace above and Battle Arena's def
# pbSwitch.
def pbReplace(idxBattler,idxParty,batonPass=false)
party = pbParty(idxBattler)
idxPartyOld = @battlers[idxBattler].pokemonIndex
# Initialise the new Pokémon
@battlers[idxBattler].pbInitialize(party[idxParty],idxParty,batonPass)
# Reorder the party for this battle
partyOrder = pbPartyOrder(idxBattler)
partyOrder[idxParty],partyOrder[idxPartyOld] = partyOrder[idxPartyOld],partyOrder[idxParty]
# Send out the new Pokémon
pbSendOut([[idxBattler,party[idxParty]]])
pbCalculatePriority(false,[idxBattler]) if Settings::RECALCULATE_TURN_ORDER_AFTER_SPEED_CHANGES
end
# Called from def pbReplace above and at the start of battle.
# sendOuts is an array; each element is itself an array: [idxBattler,pkmn]
def pbSendOut(sendOuts,startBattle=false)
sendOuts.each { |b| @peer.pbOnEnteringBattle(self,b[1]) }
@scene.pbSendOutBattlers(sendOuts,startBattle)
sendOuts.each do |b|
@scene.pbResetMoveIndex(b[0])
pbSetSeen(@battlers[b[0]])
@usedInBattle[b[0]&1][b[0]/2] = true
end
end
#=============================================================================
# Effects upon a Pokémon entering battle
#=============================================================================
# Called at the start of battle only.
def pbOnActiveAll
# Weather-inducing abilities, Trace, Imposter, etc.
pbCalculatePriority(true)
pbPriority(true).each { |b| b.pbEffectsOnSwitchIn(true) }
pbCalculatePriority
# Check forms are correct
eachBattler { |b| b.pbCheckForm }
end
# Called when a Pokémon switches in (entry effects, entry hazards).
def pbOnActiveOne(battler)
return false if battler.fainted?
# Introduce Shadow Pokémon
if battler.opposes? && battler.shadowPokemon?
pbCommonAnimation("Shadow",battler)
pbDisplay(_INTL("Oh!\nA Shadow Pokémon!"))
end
# Record money-doubling effect of Amulet Coin/Luck Incense
if !battler.opposes? && [:AMULETCOIN, :LUCKINCENSE].include?(battler.item_id)
@field.effects[PBEffects::AmuletCoin] = true
end
# Update battlers' participants (who will gain Exp/EVs when a battler faints)
eachBattler { |b| b.pbUpdateParticipants }
# Healing Wish
if @positions[battler.index].effects[PBEffects::HealingWish]
pbCommonAnimation("HealingWish",battler)
pbDisplay(_INTL("The healing wish came true for {1}!",battler.pbThis(true)))
battler.pbRecoverHP(battler.totalhp)
battler.pbCureStatus(false)
@positions[battler.index].effects[PBEffects::HealingWish] = false
end
# Lunar Dance
if @positions[battler.index].effects[PBEffects::LunarDance]
pbCommonAnimation("LunarDance",battler)
pbDisplay(_INTL("{1} became cloaked in mystical moonlight!",battler.pbThis))
battler.pbRecoverHP(battler.totalhp)
battler.pbCureStatus(false)
battler.eachMove { |m| m.pp = m.total_pp }
@positions[battler.index].effects[PBEffects::LunarDance] = false
end
# Entry hazards
# Stealth Rock
if battler.pbOwnSide.effects[PBEffects::StealthRock] && battler.takesIndirectDamage? &&
GameData::Type.exists?(:ROCK)
bTypes = battler.pbTypes(true)
eff = Effectiveness.calculate(:ROCK, bTypes[0], bTypes[1], bTypes[2])
if !Effectiveness.ineffective?(eff)
eff = eff.to_f / Effectiveness::NORMAL_EFFECTIVE
oldHP = battler.hp
battler.pbReduceHP(battler.totalhp*eff/8,false)
pbDisplay(_INTL("Pointed stones dug into {1}!",battler.pbThis))
battler.pbItemHPHealCheck
if battler.pbAbilitiesOnDamageTaken(oldHP) # Switched out
return pbOnActiveOne(battler) # For replacement battler
end
end
end
# Spikes
if battler.pbOwnSide.effects[PBEffects::Spikes]>0 && battler.takesIndirectDamage? &&
!battler.airborne?
spikesDiv = [8,6,4][battler.pbOwnSide.effects[PBEffects::Spikes]-1]
oldHP = battler.hp
battler.pbReduceHP(battler.totalhp/spikesDiv,false)
pbDisplay(_INTL("{1} is hurt by the spikes!",battler.pbThis))
battler.pbItemHPHealCheck
if battler.pbAbilitiesOnDamageTaken(oldHP) # Switched out
return pbOnActiveOne(battler) # For replacement battler
end
end
# Toxic Spikes
if battler.pbOwnSide.effects[PBEffects::ToxicSpikes]>0 && !battler.fainted? &&
!battler.airborne?
if battler.pbHasType?(:POISON)
battler.pbOwnSide.effects[PBEffects::ToxicSpikes] = 0
pbDisplay(_INTL("{1} absorbed the poison spikes!",battler.pbThis))
elsif battler.pbCanPoison?(nil,false)
if battler.pbOwnSide.effects[PBEffects::ToxicSpikes]==2
battler.pbPoison(nil,_INTL("{1} was badly poisoned by the poison spikes!",battler.pbThis),true)
else
battler.pbPoison(nil,_INTL("{1} was poisoned by the poison spikes!",battler.pbThis))
end
end
end
# Sticky Web
if battler.pbOwnSide.effects[PBEffects::StickyWeb] && !battler.fainted? &&
!battler.airborne?
pbDisplay(_INTL("{1} was caught in a sticky web!",battler.pbThis))
if battler.pbCanLowerStatStage?(:SPEED)
battler.pbLowerStatStage(:SPEED,1,nil)
battler.pbItemStatRestoreCheck
end
end
# Battler faints if it is knocked out because of an entry hazard above
if battler.fainted?
battler.pbFaint
pbGainExp
pbJudge
return false
end
battler.pbCheckForm
return true
end
end

View File

@@ -0,0 +1,148 @@
class PokeBattle_Battle
#=============================================================================
# Choosing to use an item
#=============================================================================
def pbCanUseItemOnPokemon?(item,pkmn,battler,scene,showMessages=true)
if !pkmn || pkmn.egg?
scene.pbDisplay(_INTL("It won't have any effect.")) if showMessages
return false
end
# Embargo
if battler && battler.effects[PBEffects::Embargo]>0
scene.pbDisplay(_INTL("Embargo's effect prevents the item's use on {1}!",
battler.pbThis(true))) if showMessages
return false
end
return true
end
# NOTE: Using a Poké Ball consumes all your actions for the round. The method
# below is one half of making this happen; the other half is in the
# ItemHandlers::CanUseInBattle for Poké Balls.
def pbItemUsesAllActions?(item)
return true if GameData::Item.get(item).is_poke_ball?
return false
end
def pbRegisterItem(idxBattler,item,idxTarget=nil,idxMove=nil)
# Register for use of item on a Pokémon in the party
@choices[idxBattler][0] = :UseItem
@choices[idxBattler][1] = item # ID of item to be used
@choices[idxBattler][2] = idxTarget # Party index of Pokémon to use item on
@choices[idxBattler][3] = idxMove # Index of move to recharge (Ethers)
# Delete the item from the Bag. If it turns out it will have no effect, it
# will be re-added to the Bag later.
pbConsumeItemInBag(item,idxBattler)
return true
end
#=============================================================================
# Using an item
#=============================================================================
def pbConsumeItemInBag(item,idxBattler)
return if !item
useType = GameData::Item.get(item).battle_use
return if useType==0 || (useType>=6 && useType<=10) # Not consumed upon use
if pbOwnedByPlayer?(idxBattler)
if !$PokemonBag.pbDeleteItem(item)
raise _INTL("Tried to consume item that wasn't in the Bag somehow.")
end
else
items = pbGetOwnerItems(idxBattler)
items.each_with_index do |thisItem,i|
next if thisItem!=item
items[i] = nil
break
end
items.compact!
end
end
def pbReturnUnusedItemToBag(item,idxBattler)
return if item!
useType = GameData::Item.get(item).battle_use
return if useType==0 || (useType>=6 && useType<=10) # Not consumed upon use
if pbOwnedByPlayer?(idxBattler)
if $PokemonBag && $PokemonBag.pbCanStore?(item)
$PokemonBag.pbStoreItem(item)
else
raise _INTL("Couldn't return unused item to Bag somehow.")
end
else
items = pbGetOwnerItems(idxBattler)
items.push(item) if items
end
end
def pbUseItemMessage(item,trainerName)
itemName = GameData::Item.get(item).name
if itemName.starts_with_vowel?
pbDisplayBrief(_INTL("{1} used an {2}.",trainerName,itemName))
else
pbDisplayBrief(_INTL("{1} used a {2}.",trainerName,itemName))
end
end
# Uses an item on a Pokémon in the trainer's party.
def pbUseItemOnPokemon(item,idxParty,userBattler)
trainerName = pbGetOwnerName(userBattler.index)
pbUseItemMessage(item,trainerName)
pkmn = pbParty(userBattler.index)[idxParty]
battler = pbFindBattler(idxParty,userBattler.index)
ch = @choices[userBattler.index]
if ItemHandlers.triggerCanUseInBattle(item,pkmn,battler,ch[3],true,self,@scene,false)
ItemHandlers.triggerBattleUseOnPokemon(item,pkmn,battler,ch,@scene)
ch[1] = nil # Delete item from choice
return
end
pbDisplay(_INTL("But it had no effect!"))
# Return unused item to Bag
pbReturnUnusedItemToBag(item,userBattler.index)
end
# Uses an item on a Pokémon in battle that belongs to the trainer.
def pbUseItemOnBattler(item,idxParty,userBattler)
trainerName = pbGetOwnerName(userBattler.index)
pbUseItemMessage(item,trainerName)
battler = pbFindBattler(idxParty,userBattler.index)
ch = @choices[userBattler.index]
if battler
if ItemHandlers.triggerCanUseInBattle(item,battler.pokemon,battler,ch[3],true,self,@scene,false)
ItemHandlers.triggerBattleUseOnBattler(item,battler,@scene)
ch[1] = nil # Delete item from choice
return
else
pbDisplay(_INTL("But it had no effect!"))
end
else
pbDisplay(_INTL("But it's not where this item can be used!"))
end
# Return unused item to Bag
pbReturnUnusedItemToBag(item,userBattler.index)
end
# Uses a Poké Ball in battle directly.
def pbUsePokeBallInBattle(item,idxBattler,userBattler)
idxBattler = userBattler.index if idxBattler<0
battler = @battlers[idxBattler]
ItemHandlers.triggerUseInBattle(item,battler,self)
@choices[userBattler.index][1] = nil # Delete item from choice
end
# Uses an item in battle directly.
def pbUseItemInBattle(item,idxBattler,userBattler)
trainerName = pbGetOwnerName(userBattler.index)
pbUseItemMessage(item,trainerName)
battler = (idxBattler<0) ? userBattler : @battlers[idxBattler]
pkmn = battler.pokemon
ch = @choices[userBattler.index]
if ItemHandlers.triggerCanUseInBattle(item,pkmn,battler,ch[3],true,self,@scene,false)
ItemHandlers.triggerUseInBattle(item,battler,self)
ch[1] = nil # Delete item from choice
return
end
pbDisplay(_INTL("But it had no effect!"))
# Return unused item to Bag
pbReturnUnusedItemToBag(item,userBattler.index)
end
end

View File

@@ -0,0 +1,151 @@
class PokeBattle_Battle
#=============================================================================
# Running from battle
#=============================================================================
def pbCanRun?(idxBattler)
return false if trainerBattle?
battler = @battlers[idxBattler]
return false if !@canRun && !battler.opposes?
return true if battler.pbHasType?(:GHOST) && Settings::MORE_TYPE_EFFECTS
return true if battler.abilityActive? &&
BattleHandlers.triggerRunFromBattleAbility(battler.ability,battler)
return true if battler.itemActive? &&
BattleHandlers.triggerRunFromBattleItem(battler.item,battler)
return false if battler.effects[PBEffects::Trapping]>0 ||
battler.effects[PBEffects::MeanLook]>=0 ||
battler.effects[PBEffects::Ingrain] ||
@field.effects[PBEffects::FairyLock]>0
eachOtherSideBattler(idxBattler) do |b|
return false if b.abilityActive? &&
BattleHandlers.triggerTrappingTargetAbility(b.ability,battler,b,self)
return false if b.itemActive? &&
BattleHandlers.triggerTrappingTargetItem(b.item,battler,b,self)
end
return true
end
# Return values:
# -1: Failed fleeing
# 0: Wasn't possible to attempt fleeing, continue choosing action for the round
# 1: Succeeded at fleeing, battle will end
# duringBattle is true for replacing a fainted Pokémon during the End Of Round
# phase, and false for choosing the Run command.
def pbRun(idxBattler,duringBattle=false)
battler = @battlers[idxBattler]
if battler.opposes?
return 0 if trainerBattle?
@choices[idxBattler][0] = :Run
@choices[idxBattler][1] = 0
@choices[idxBattler][2] = nil
return -1
end
# Fleeing from trainer battles
if trainerBattle?
if $DEBUG && Input.press?(Input::CTRL)
if pbDisplayConfirm(_INTL("Treat this battle as a win?"))
@decision = 1
return 1
elsif pbDisplayConfirm(_INTL("Treat this battle as a loss?"))
@decision = 2
return 1
end
elsif @internalBattle
pbDisplayPaused(_INTL("No! There's no running from a Trainer battle!"))
elsif pbDisplayConfirm(_INTL("Would you like to forfeit the match and quit now?"))
pbSEPlay("Battle flee")
pbDisplay(_INTL("{1} forfeited the match!",self.pbPlayer.name))
@decision = 3
return 1
end
return 0
end
# Fleeing from wild battles
if $DEBUG && Input.press?(Input::CTRL)
pbSEPlay("Battle flee")
pbDisplayPaused(_INTL("You got away safely!"))
@decision = 3
return 1
end
if !@canRun
pbDisplayPaused(_INTL("You can't escape!"))
return 0
end
if !duringBattle
if battler.pbHasType?(:GHOST) && Settings::MORE_TYPE_EFFECTS
pbSEPlay("Battle flee")
pbDisplayPaused(_INTL("You got away safely!"))
@decision = 3
return 1
end
# Abilities that guarantee escape
if battler.abilityActive?
if BattleHandlers.triggerRunFromBattleAbility(battler.ability,battler)
pbShowAbilitySplash(battler,true)
pbHideAbilitySplash(battler)
pbSEPlay("Battle flee")
pbDisplayPaused(_INTL("You got away safely!"))
@decision = 3
return 1
end
end
# Held items that guarantee escape
if battler.itemActive?
if BattleHandlers.triggerRunFromBattleItem(battler.item,battler)
pbSEPlay("Battle flee")
pbDisplayPaused(_INTL("{1} fled using its {2}!",
battler.pbThis,battler.itemName))
@decision = 3
return 1
end
end
# Other certain trapping effects
if battler.effects[PBEffects::Trapping]>0 ||
battler.effects[PBEffects::MeanLook]>=0 ||
battler.effects[PBEffects::Ingrain] ||
@field.effects[PBEffects::FairyLock]>0
pbDisplayPaused(_INTL("You can't escape!"))
return 0
end
# Trapping abilities/items
eachOtherSideBattler(idxBattler) do |b|
next if !b.abilityActive?
if BattleHandlers.triggerTrappingTargetAbility(b.ability,battler,b,self)
pbDisplayPaused(_INTL("{1} prevents escape with {2}!",b.pbThis,b.abilityName))
return 0
end
end
eachOtherSideBattler(idxBattler) do |b|
next if !b.itemActive?
if BattleHandlers.triggerTrappingTargetItem(b.item,battler,b,self)
pbDisplayPaused(_INTL("{1} prevents escape with {2}!",b.pbThis,b.itemName))
return 0
end
end
end
# Fleeing calculation
# Get the speeds of the Pokémon fleeing and the fastest opponent
# NOTE: Not pbSpeed, because using unmodified Speed.
@runCommand += 1 if !duringBattle # Make it easier to flee next time
speedPlayer = @battlers[idxBattler].speed
speedEnemy = 1
eachOtherSideBattler(idxBattler) do |b|
speed = b.speed
speedEnemy = speed if speedEnemy<speed
end
# Compare speeds and perform fleeing calculation
if speedPlayer>speedEnemy
rate = 256
else
rate = (speedPlayer*128)/speedEnemy
rate += @runCommand*30
end
if rate>=256 || @battleAI.pbAIRandom(256)<rate
pbSEPlay("Battle flee")
pbDisplayPaused(_INTL("You got away safely!"))
@decision = 3
return 1
end
pbDisplayPaused(_INTL("You couldn't get away!"))
return -1
end
end

View File

@@ -0,0 +1,189 @@
class PokeBattle_Battle
#=============================================================================
# Shifting a battler to another position in a battle larger than double
#=============================================================================
def pbCanShift?(idxBattler)
return false if pbSideSize(0)<=2 && pbSideSize(1)<=2 # Double battle or smaller
idxOther = -1
case pbSideSize(idxBattler)
when 1
return false # Only one battler on that side
when 2
idxOther = (idxBattler+2)%4
when 3
return false if idxBattler==2 || idxBattler==3 # In middle spot already
idxOther = ((idxBattler%2)==0) ? 2 : 3
end
return false if pbGetOwnerIndexFromBattlerIndex(idxBattler)!=pbGetOwnerIndexFromBattlerIndex(idxOther)
return true
end
def pbRegisterShift(idxBattler)
@choices[idxBattler][0] = :Shift
@choices[idxBattler][1] = 0
@choices[idxBattler][2] = nil
return true
end
#=============================================================================
# Calling at a battler
#=============================================================================
def pbRegisterCall(idxBattler)
@choices[idxBattler][0] = :Call
@choices[idxBattler][1] = 0
@choices[idxBattler][2] = nil
return true
end
def pbCall(idxBattler)
battler = @battlers[idxBattler]
trainerName = pbGetOwnerName(idxBattler)
pbDisplay(_INTL("{1} called {2}!",trainerName,battler.pbThis(true)))
pbDisplay(_INTL("{1}!",battler.name))
if battler.shadowPokemon?
if battler.inHyperMode?
battler.pokemon.hyper_mode = false
battler.pokemon.adjustHeart(-300)
pbDisplay(_INTL("{1} came to its senses from the Trainer's call!",battler.pbThis))
else
pbDisplay(_INTL("But nothing happened!"))
end
elsif battler.status == :SLEEP
battler.pbCureStatus
elsif battler.pbCanRaiseStatStage?(:ACCURACY,battler)
battler.pbRaiseStatStage(:ACCURACY,1,battler)
else
pbDisplay(_INTL("But nothing happened!"))
end
end
#=============================================================================
# Choosing to Mega Evolve a battler
#=============================================================================
def pbHasMegaRing?(idxBattler)
return true if !pbOwnedByPlayer?(idxBattler) # Assume AI trainer have a ring
Settings::MEGA_RINGS.each { |item| return true if $PokemonBag.pbHasItem?(item) }
return false
end
def pbGetMegaRingName(idxBattler)
if pbOwnedByPlayer?(idxBattler)
Settings::MEGA_RINGS.each do |item|
return GameData::Item.get(item).name if $PokemonBag.pbHasItem?(item)
end
end
# NOTE: Add your own Mega objects for particular NPC trainers here.
# if pbGetOwnerFromBattlerIndex(idxBattler).trainer_type == :BUGCATCHER
# return _INTL("Mega Net")
# end
return _INTL("Mega Ring")
end
def pbCanMegaEvolve?(idxBattler)
return false if $game_switches[Settings::NO_MEGA_EVOLUTION]
return false if !@battlers[idxBattler].hasMega?
return false if wildBattle? && opposes?(idxBattler)
return true if $DEBUG && Input.press?(Input::CTRL)
return false if @battlers[idxBattler].effects[PBEffects::SkyDrop]>=0
return false if !pbHasMegaRing?(idxBattler)
side = @battlers[idxBattler].idxOwnSide
owner = pbGetOwnerIndexFromBattlerIndex(idxBattler)
return @megaEvolution[side][owner]==-1
end
def pbRegisterMegaEvolution(idxBattler)
side = @battlers[idxBattler].idxOwnSide
owner = pbGetOwnerIndexFromBattlerIndex(idxBattler)
@megaEvolution[side][owner] = idxBattler
end
def pbUnregisterMegaEvolution(idxBattler)
side = @battlers[idxBattler].idxOwnSide
owner = pbGetOwnerIndexFromBattlerIndex(idxBattler)
@megaEvolution[side][owner] = -1 if @megaEvolution[side][owner]==idxBattler
end
def pbToggleRegisteredMegaEvolution(idxBattler)
side = @battlers[idxBattler].idxOwnSide
owner = pbGetOwnerIndexFromBattlerIndex(idxBattler)
if @megaEvolution[side][owner]==idxBattler
@megaEvolution[side][owner] = -1
else
@megaEvolution[side][owner] = idxBattler
end
end
def pbRegisteredMegaEvolution?(idxBattler)
side = @battlers[idxBattler].idxOwnSide
owner = pbGetOwnerIndexFromBattlerIndex(idxBattler)
return @megaEvolution[side][owner]==idxBattler
end
#=============================================================================
# Mega Evolving a battler
#=============================================================================
def pbMegaEvolve(idxBattler)
battler = @battlers[idxBattler]
return if !battler || !battler.pokemon
return if !battler.hasMega? || battler.mega?
trainerName = pbGetOwnerName(idxBattler)
# Break Illusion
if battler.hasActiveAbility?(:ILLUSION)
BattleHandlers.triggerTargetAbilityOnHit(battler.ability,nil,battler,nil,self)
end
# Mega Evolve
case battler.pokemon.megaMessage
when 1 # Rayquaza
pbDisplay(_INTL("{1}'s fervent wish has reached {2}!",trainerName,battler.pbThis))
else
pbDisplay(_INTL("{1}'s {2} is reacting to {3}'s {4}!",
battler.pbThis,battler.itemName,trainerName,pbGetMegaRingName(idxBattler)))
end
pbCommonAnimation("MegaEvolution",battler)
battler.pokemon.makeMega
battler.form = battler.pokemon.form
battler.pbUpdate(true)
@scene.pbChangePokemon(battler,battler.pokemon)
@scene.pbRefreshOne(idxBattler)
pbCommonAnimation("MegaEvolution2",battler)
megaName = battler.pokemon.megaName
if !megaName || megaName==""
megaName = _INTL("Mega {1}", battler.pokemon.speciesName)
end
pbDisplay(_INTL("{1} has Mega Evolved into {2}!",battler.pbThis,megaName))
side = battler.idxOwnSide
owner = pbGetOwnerIndexFromBattlerIndex(idxBattler)
@megaEvolution[side][owner] = -2
if battler.isSpecies?(:GENGAR) && battler.mega?
battler.effects[PBEffects::Telekinesis] = 0
end
pbCalculatePriority(false,[idxBattler]) if Settings::RECALCULATE_TURN_ORDER_AFTER_MEGA_EVOLUTION
# Trigger ability
battler.pbEffectsOnSwitchIn
end
#=============================================================================
# Primal Reverting a battler
#=============================================================================
def pbPrimalReversion(idxBattler)
battler = @battlers[idxBattler]
return if !battler || !battler.pokemon
return if !battler.hasPrimal? || battler.primal?
if battler.isSpecies?(:KYOGRE)
pbCommonAnimation("PrimalKyogre",battler)
elsif battler.isSpecies?(:GROUDON)
pbCommonAnimation("PrimalGroudon",battler)
end
battler.pokemon.makePrimal
battler.form = battler.pokemon.form
battler.pbUpdate(true)
@scene.pbChangePokemon(battler,battler.pokemon)
@scene.pbRefreshOne(idxBattler)
if battler.isSpecies?(:KYOGRE)
pbCommonAnimation("PrimalKyogre2",battler)
elsif battler.isSpecies?(:GROUDON)
pbCommonAnimation("PrimalGroudon2",battler)
end
pbDisplay(_INTL("{1}'s Primal Reversion!\nIt reverted to its primal form!",battler.pbThis))
end
end

View File

@@ -0,0 +1,250 @@
class PokeBattle_Battle
#=============================================================================
# Clear commands
#=============================================================================
def pbClearChoice(idxBattler)
@choices[idxBattler] = [] if !@choices[idxBattler]
@choices[idxBattler][0] = :None
@choices[idxBattler][1] = 0
@choices[idxBattler][2] = nil
@choices[idxBattler][3] = -1
end
def pbCancelChoice(idxBattler)
# If idxBattler's choice was to use an item, return that item to the Bag
if @choices[idxBattler][0] == :UseItem
item = @choices[idxBattler][1]
pbReturnUnusedItemToBag(item, idxBattler) if item
end
# If idxBattler chose to Mega Evolve, cancel it
pbUnregisterMegaEvolution(idxBattler)
# Clear idxBattler's choice
pbClearChoice(idxBattler)
end
#=============================================================================
# Use main command menu (Fight/Pokémon/Bag/Run)
#=============================================================================
def pbCommandMenu(idxBattler,firstAction)
return @scene.pbCommandMenu(idxBattler,firstAction)
end
#=============================================================================
# Check whether actions can be taken
#=============================================================================
def pbCanShowCommands?(idxBattler)
battler = @battlers[idxBattler]
return false if !battler || battler.fainted?
return false if battler.usingMultiTurnAttack?
return true
end
def pbCanShowFightMenu?(idxBattler)
battler = @battlers[idxBattler]
# Encore
return false if battler.effects[PBEffects::Encore]>0
# No moves that can be chosen (will Struggle instead)
usable = false
battler.eachMoveWithIndex do |_m,i|
next if !pbCanChooseMove?(idxBattler,i,false)
usable = true
break
end
return usable
end
#=============================================================================
# Use sub-menus to choose an action, and register it if is allowed
#=============================================================================
# Returns true if a choice was made, false if cancelled.
def pbFightMenu(idxBattler)
# Auto-use Encored move or no moves choosable, so auto-use Struggle
return pbAutoChooseMove(idxBattler) if !pbCanShowFightMenu?(idxBattler)
# Battle Palace only
return true if pbAutoFightMenu(idxBattler)
# Regular move selection
ret = false
@scene.pbFightMenu(idxBattler,pbCanMegaEvolve?(idxBattler)) { |cmd|
case cmd
when -1 # Cancel
when -2 # Toggle Mega Evolution
pbToggleRegisteredMegaEvolution(idxBattler)
next false
when -3 # Shift
pbUnregisterMegaEvolution(idxBattler)
pbRegisterShift(idxBattler)
ret = true
else # Chose a move to use
next false if cmd<0 || !@battlers[idxBattler].moves[cmd] ||
!@battlers[idxBattler].moves[cmd].id
next false if !pbRegisterMove(idxBattler,cmd)
next false if !singleBattle? &&
!pbChooseTarget(@battlers[idxBattler],@battlers[idxBattler].moves[cmd])
ret = true
end
next true
}
return ret
end
def pbAutoFightMenu(idxBattler); return false; end
def pbChooseTarget(battler,move)
target_data = move.pbTarget(battler)
idxTarget = @scene.pbChooseTarget(battler.index,target_data)
return false if idxTarget<0
pbRegisterTarget(battler.index,idxTarget)
return true
end
def pbItemMenu(idxBattler,firstAction)
if !@internalBattle
pbDisplay(_INTL("Items can't be used here."))
return false
end
ret = false
@scene.pbItemMenu(idxBattler,firstAction) { |item,useType,idxPkmn,idxMove,itemScene|
next false if !item
battler = pkmn = nil
case useType
when 1, 2, 6, 7 # Use on Pokémon/Pokémon's move
next false if !ItemHandlers.hasBattleUseOnPokemon(item)
battler = pbFindBattler(idxPkmn,idxBattler)
pkmn = pbParty(idxBattler)[idxPkmn]
next false if !pbCanUseItemOnPokemon?(item,pkmn,battler,itemScene)
when 3, 8 # Use on battler
next false if !ItemHandlers.hasBattleUseOnBattler(item)
battler = pbFindBattler(idxPkmn,idxBattler)
pkmn = battler.pokemon if battler
next false if !pbCanUseItemOnPokemon?(item,pkmn,battler,itemScene)
when 4, 9 # Poké Balls
next false if idxPkmn<0
battler = @battlers[idxPkmn]
pkmn = battler.pokemon if battler
when 5, 10 # No target (Poké Doll, Guard Spec., Launcher items)
battler = @battlers[idxBattler]
pkmn = battler.pokemon if battler
else
next false
end
next false if !pkmn
next false if !ItemHandlers.triggerCanUseInBattle(item,
pkmn,battler,idxMove,firstAction,self,itemScene)
next false if !pbRegisterItem(idxBattler,item,idxPkmn,idxMove)
ret = true
next true
}
return ret
end
def pbPartyMenu(idxBattler)
ret = -1
if @debug
ret = @battleAI.pbDefaultChooseNewEnemy(idxBattler,pbParty(idxBattler))
else
ret = pbPartyScreen(idxBattler,false,true,true)
end
return ret>=0
end
def pbRunMenu(idxBattler)
# Regardless of succeeding or failing to run, stop choosing actions
return pbRun(idxBattler)!=0
end
def pbCallMenu(idxBattler)
return pbRegisterCall(idxBattler)
end
def pbDebugMenu
# NOTE: This doesn't do anything yet. Maybe you can write your own debugging
# options!
end
#=============================================================================
# Command phase
#=============================================================================
def pbCommandPhase
@scene.pbBeginCommandPhase
# Reset choices if commands can be shown
@battlers.each_with_index do |b,i|
next if !b
pbClearChoice(i) if pbCanShowCommands?(i)
end
# Reset choices to perform Mega Evolution if it wasn't done somehow
for side in 0...2
@megaEvolution[side].each_with_index do |megaEvo,i|
@megaEvolution[side][i] = -1 if megaEvo>=0
end
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
pbCommandPhaseLoop(false) # AI chooses their actions
end
def pbCommandPhaseLoop(isPlayer)
# NOTE: Doing some things (e.g. running, throwing a Poké Ball) takes up all
# your actions in a round.
actioned = []
idxBattler = -1
loop do
break if @decision!=0 # Battle ended, stop choosing actions
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
# AI controls this battler
if @controlPlayer || !pbOwnedByPlayer?(idxBattler)
@battleAI.pbDefaultChooseEnemyCommand(idxBattler)
next
end
# Player chooses an action
actioned.push(idxBattler)
commandsEnd = false # Whether to cancel choosing all other actions this round
loop do
cmd = pbCommandMenu(idxBattler,actioned.length==1)
# If being Sky Dropped, can't do anything except use a move
if cmd>0 && @battlers[idxBattler].effects[PBEffects::SkyDrop]>=0
pbDisplay(_INTL("Sky Drop won't let {1} go!",@battlers[idxBattler].pbThis(true)))
next
end
case cmd
when 0 # Fight
break if pbFightMenu(idxBattler)
when 1 # Bag
if pbItemMenu(idxBattler,actioned.length==1)
commandsEnd = true if pbItemUsesAllActions?(@choices[idxBattler][1])
break
end
when 2 # Pokémon
break if pbPartyMenu(idxBattler)
when 3 # Run
# NOTE: "Run" is only an available option for the first battler the
# player chooses an action for in a round. Attempting to run
# from battle prevents you from choosing any other actions in
# that round.
if pbRunMenu(idxBattler)
commandsEnd = true
break
end
when 4 # Call
break if pbCallMenu(idxBattler)
when -2 # Debug
pbDebugMenu
next
when -1 # Go back to previous battler's action choice
next if actioned.length<=1
actioned.pop # Forget this battler was done
idxBattler = actioned.last-1
pbCancelChoice(idxBattler+1) # Clear the previous battler's choice
actioned.pop # Forget the previous battler was done
break
end
pbCancelChoice(idxBattler)
end
break if commandsEnd
end
end
end

View File

@@ -0,0 +1,190 @@
class PokeBattle_Battle
#=============================================================================
# Attack phase actions
#=============================================================================
# Quick Claw, Custap Berry's "X let it move first!" message.
def pbAttackPhasePriorityChangeMessages
pbPriority.each do |b|
if b.effects[PBEffects::PriorityAbility] && b.abilityActive?
BattleHandlers.triggerPriorityBracketUseAbility(b.ability,b,self)
elsif b.effects[PBEffects::PriorityItem] && b.itemActive?
BattleHandlers.triggerPriorityBracketUseItem(b.item,b,self)
end
end
end
def pbAttackPhaseCall
pbPriority.each do |b|
next unless @choices[b.index][0]==:Call && !b.fainted?
b.lastMoveFailed = false # Counts as a successful move for Stomping Tantrum
pbCall(b.index)
end
end
def pbPursuit(idxSwitcher)
@switching = true
pbPriority.each do |b|
next if b.fainted? || !b.opposes?(idxSwitcher) # Shouldn't hit an ally
next if b.movedThisRound? || !pbChoseMoveFunctionCode?(b.index,"088") # Pursuit
# Check whether Pursuit can be used
next unless pbMoveCanTarget?(b.index,idxSwitcher,@choices[b.index][2].pbTarget(b))
next unless pbCanChooseMove?(b.index,@choices[b.index][1],false)
next if b.status == :SLEEP || b.status == :FROZEN
next if b.effects[PBEffects::SkyDrop]>=0
next if b.hasActiveAbility?(:TRUANT) && b.effects[PBEffects::Truant]
# Mega Evolve
if !wildBattle? || !b.opposes?
owner = pbGetOwnerIndexFromBattlerIndex(b.index)
pbMegaEvolve(b.index) if @megaEvolution[b.idxOwnSide][owner]==b.index
end
# Use Pursuit
@choices[b.index][3] = idxSwitcher # Change Pursuit's target
if b.pbProcessTurn(@choices[b.index],false)
b.effects[PBEffects::Pursuit] = true
end
break if @decision>0 || @battlers[idxSwitcher].fainted?
end
@switching = false
end
def pbAttackPhaseSwitch
pbPriority.each do |b|
next unless @choices[b.index][0]==:SwitchOut && !b.fainted?
idxNewPkmn = @choices[b.index][1] # Party index of Pokémon to switch to
b.lastMoveFailed = false # Counts as a successful move for Stomping Tantrum
@lastMoveUser = b.index
# Switching message
pbMessageOnRecall(b)
# Pursuit interrupts switching
pbPursuit(b.index)
return if @decision>0
# Switch Pokémon
pbRecallAndReplace(b.index,idxNewPkmn)
b.pbEffectsOnSwitchIn(true)
end
end
def pbAttackPhaseItems
pbPriority.each do |b|
next unless @choices[b.index][0] == :UseItem && !b.fainted?
b.lastMoveFailed = false # Counts as a successful move for Stomping Tantrum
item = @choices[b.index][1]
next if !item
case GameData::Item.get(item).battle_use
when 1, 2, 6, 7 # Use on Pokémon/Pokémon's move
pbUseItemOnPokemon(item, @choices[b.index][2], b) if @choices[b.index][2] >= 0
when 3, 8 # Use on battler
pbUseItemOnBattler(item, @choices[b.index][2], b)
when 4, 9 # Use Poké Ball
pbUsePokeBallInBattle(item, @choices[b.index][2], b)
when 5, 10 # Use directly
pbUseItemInBattle(item, @choices[b.index][2], b)
else
next
end
return if @decision > 0
end
pbCalculatePriority if Settings::RECALCULATE_TURN_ORDER_AFTER_SPEED_CHANGES
end
def pbAttackPhaseMegaEvolution
pbPriority.each do |b|
next if wildBattle? && b.opposes?
next unless @choices[b.index][0]==:UseMove && !b.fainted?
owner = pbGetOwnerIndexFromBattlerIndex(b.index)
next if @megaEvolution[b.idxOwnSide][owner]!=b.index
pbMegaEvolve(b.index)
end
end
def pbAttackPhaseMoves
# Show charging messages (Focus Punch)
pbPriority.each do |b|
next unless @choices[b.index][0]==:UseMove && !b.fainted?
next if b.movedThisRound?
@choices[b.index][2].pbDisplayChargeMessage(b)
end
# Main move processing loop
loop do
priority = pbPriority
# Forced to go next
advance = false
priority.each do |b|
next unless b.effects[PBEffects::MoveNext] && !b.fainted?
next unless @choices[b.index][0]==:UseMove || @choices[b.index][0]==:Shift
next if b.movedThisRound?
advance = b.pbProcessTurn(@choices[b.index])
break if advance
end
return if @decision>0
next if advance
# Regular priority order
priority.each do |b|
next if b.effects[PBEffects::Quash]>0 || b.fainted?
next unless @choices[b.index][0]==:UseMove || @choices[b.index][0]==:Shift
next if b.movedThisRound?
advance = b.pbProcessTurn(@choices[b.index])
break if advance
end
return if @decision>0
next if advance
# Quashed
quashLevel = 0
loop do
quashLevel += 1
moreQuash = false
priority.each do |b|
moreQuash = true if b.effects[PBEffects::Quash]>quashLevel
next unless b.effects[PBEffects::Quash]==quashLevel && !b.fainted?
next unless @choices[b.index][0]==:UseMove || @choices[b.index][0]==:Shift
next if b.movedThisRound?
advance = b.pbProcessTurn(@choices[b.index])
break
end
break if advance || !moreQuash
end
return if @decision>0
next if advance
# Check for all done
priority.each do |b|
if !b.fainted? && !b.movedThisRound?
advance = true if @choices[b.index][0]==:UseMove || @choices[b.index][0]==:Shift
end
break if advance
end
next if advance
# All Pokémon have moved; end the loop
break
end
end
#=============================================================================
# Attack phase
#=============================================================================
def pbAttackPhase
@scene.pbBeginAttackPhase
# Reset certain effects
@battlers.each_with_index do |b,i|
next if !b
b.turnCount += 1 if !b.fainted?
@successStates[i].clear
if @choices[i][0]!=:UseMove && @choices[i][0]!=:Shift && @choices[i][0]!=:SwitchOut
b.effects[PBEffects::DestinyBond] = false
b.effects[PBEffects::Grudge] = false
end
b.effects[PBEffects::Rage] = false if !pbChoseMoveFunctionCode?(i,"093") # Rage
end
PBDebug.log("")
# Calculate move order for this round
pbCalculatePriority(true)
# Perform actions
pbAttackPhasePriorityChangeMessages
pbAttackPhaseCall
pbAttackPhaseSwitch
return if @decision>0
pbAttackPhaseItems
return if @decision>0
pbAttackPhaseMegaEvolution
pbAttackPhaseMoves
end
end

View File

@@ -0,0 +1,666 @@
class PokeBattle_Battle
#=============================================================================
# Decrement effect counters
#=============================================================================
def pbEORCountDownBattlerEffect(priority,effect)
priority.each do |b|
next if b.fainted? || b.effects[effect]==0
b.effects[effect] -= 1
yield b if block_given? && b.effects[effect]==0
end
end
def pbEORCountDownSideEffect(side,effect,msg)
if @sides[side].effects[effect]>0
@sides[side].effects[effect] -= 1
pbDisplay(msg) if @sides[side].effects[effect]==0
end
end
def pbEORCountDownFieldEffect(effect,msg)
if @field.effects[effect]>0
@field.effects[effect] -= 1
if @field.effects[effect]==0
pbDisplay(msg)
if effect==PBEffects::MagicRoom
pbPriority(true).each { |b| b.pbItemTerrainStatBoostCheck }
end
end
end
end
#=============================================================================
# End Of Round weather
#=============================================================================
def pbEORWeather(priority)
# NOTE: Primordial weather doesn't need to be checked here, because if it
# could wear off here, it will have worn off already.
# Count down weather duration
@field.weatherDuration -= 1 if @field.weatherDuration>0
# Weather wears off
if @field.weatherDuration==0
case @field.weather
when :Sun then pbDisplay(_INTL("The sunlight faded."))
when :Rain then pbDisplay(_INTL("The rain stopped."))
when :Sandstorm then pbDisplay(_INTL("The sandstorm subsided."))
when :Hail then pbDisplay(_INTL("The hail stopped."))
when :ShadowSky then pbDisplay(_INTL("The shadow sky faded."))
end
@field.weather = :None
# Check for form changes caused by the weather changing
eachBattler { |b| b.pbCheckFormOnWeatherChange }
# Start up the default weather
pbStartWeather(nil,@field.defaultWeather) if @field.defaultWeather != :None
return if @field.weather == :None
end
# Weather continues
weather_data = GameData::BattleWeather.try_get(@field.weather)
pbCommonAnimation(weather_data.animation) if weather_data
case @field.weather
# when :Sun then pbDisplay(_INTL("The sunlight is strong."))
# when :Rain then pbDisplay(_INTL("Rain continues to fall."))
when :Sandstorm then pbDisplay(_INTL("The sandstorm is raging."))
when :Hail then pbDisplay(_INTL("The hail is crashing down."))
# when :HarshSun then pbDisplay(_INTL("The sunlight is extremely harsh."))
# when :HeavyRain then pbDisplay(_INTL("It is raining heavily."))
# when :StrongWinds then pbDisplay(_INTL("The wind is strong."))
when :ShadowSky then pbDisplay(_INTL("The shadow sky continues."))
end
# Effects due to weather
curWeather = pbWeather
priority.each do |b|
# Weather-related abilities
if b.abilityActive?
BattleHandlers.triggerEORWeatherAbility(b.ability,curWeather,b,self)
b.pbFaint if b.fainted?
end
# Weather damage
# NOTE:
case curWeather
when :Sandstorm
next if !b.takesSandstormDamage?
pbDisplay(_INTL("{1} is buffeted by the sandstorm!",b.pbThis))
@scene.pbDamageAnimation(b)
b.pbReduceHP(b.totalhp/16,false)
b.pbItemHPHealCheck
b.pbFaint if b.fainted?
when :Hail
next if !b.takesHailDamage?
pbDisplay(_INTL("{1} is buffeted by the hail!",b.pbThis))
@scene.pbDamageAnimation(b)
b.pbReduceHP(b.totalhp/16,false)
b.pbItemHPHealCheck
b.pbFaint if b.fainted?
when :ShadowSky
next if !b.takesShadowSkyDamage?
pbDisplay(_INTL("{1} is hurt by the shadow sky!",b.pbThis))
@scene.pbDamageAnimation(b)
b.pbReduceHP(b.totalhp/16,false)
b.pbItemHPHealCheck
b.pbFaint if b.fainted?
end
end
end
#=============================================================================
# End Of Round terrain
#=============================================================================
def pbEORTerrain
# Count down terrain duration
@field.terrainDuration -= 1 if @field.terrainDuration>0
# Terrain wears off
if @field.terrain != :None && @field.terrainDuration == 0
case @field.terrain
when :Electric
pbDisplay(_INTL("The electric current disappeared from the battlefield!"))
when :Grassy
pbDisplay(_INTL("The grass disappeared from the battlefield!"))
when :Misty
pbDisplay(_INTL("The mist disappeared from the battlefield!"))
when :Psychic
pbDisplay(_INTL("The weirdness disappeared from the battlefield!"))
end
@field.terrain = :None
# Start up the default terrain
pbStartTerrain(nil, @field.defaultTerrain, false) if @field.defaultTerrain != :None
return if @field.terrain == :None
end
# Terrain continues
terrain_data = GameData::BattleTerrain.try_get(@field.terrain)
pbCommonAnimation(terrain_data.animation) if terrain_data
case @field.terrain
when :Electric then pbDisplay(_INTL("An electric current is running across the battlefield."))
when :Grassy then pbDisplay(_INTL("Grass is covering the battlefield."))
when :Misty then pbDisplay(_INTL("Mist is swirling about the battlefield."))
when :Psychic then pbDisplay(_INTL("The battlefield is weird."))
end
end
#=============================================================================
# End Of Round shift distant battlers to middle positions
#=============================================================================
def pbEORShiftDistantBattlers
# Move battlers around if none are near to each other
# NOTE: This code assumes each side has a maximum of 3 battlers on it, and
# is not generalised to larger side sizes.
if !singleBattle?
swaps = [] # Each element is an array of two battler indices to swap
for side in 0...2
next if pbSideSize(side)==1 # Only battlers on sides of size 2+ need to move
# Check if any battler on this side is near any battler on the other side
anyNear = false
eachSameSideBattler(side) do |b|
eachOtherSideBattler(b) do |otherB|
next if !nearBattlers?(otherB.index,b.index)
anyNear = true
break
end
break if anyNear
end
break if anyNear
# No battlers on this side are near any battlers on the other side; try
# to move them
# NOTE: If we get to here (assuming both sides are of size 3 or less),
# there is definitely only 1 able battler on this side, so we
# don't need to worry about multiple battlers trying to move into
# the same position. If you add support for a side of size 4+,
# this code will need revising to account for that, as well as to
# add more complex code to ensure battlers will end up near each
# other.
eachSameSideBattler(side) do |b|
# Get the position to move to
pos = -1
case pbSideSize(side)
when 2 then pos = [2,3,0,1][b.index] # The unoccupied position
when 3 then pos = (side==0) ? 2 : 3 # The centre position
end
next if pos<0
# Can't move if the same trainer doesn't control both positions
idxOwner = pbGetOwnerIndexFromBattlerIndex(b.index)
next if pbGetOwnerIndexFromBattlerIndex(pos)!=idxOwner
swaps.push([b.index,pos])
end
end
# Move battlers around
swaps.each do |pair|
next if pbSideSize(pair[0])==2 && swaps.length>1
next if !pbSwapBattlers(pair[0],pair[1])
case pbSideSize(side)
when 2
pbDisplay(_INTL("{1} moved across!",@battlers[pair[1]].pbThis))
when 3
pbDisplay(_INTL("{1} moved to the center!",@battlers[pair[1]].pbThis))
end
end
end
end
#=============================================================================
# End Of Round phase
#=============================================================================
def pbEndOfRoundPhase
PBDebug.log("")
PBDebug.log("[End of round]")
@endOfRound = true
@scene.pbBeginEndOfRoundPhase
pbCalculatePriority # recalculate speeds
priority = pbPriority(true) # in order of fastest -> slowest speeds only
# Weather
pbEORWeather(priority)
# Future Sight/Doom Desire
@positions.each_with_index do |pos,idxPos|
next if !pos || pos.effects[PBEffects::FutureSightCounter]==0
pos.effects[PBEffects::FutureSightCounter] -= 1
next if pos.effects[PBEffects::FutureSightCounter]>0
next if !@battlers[idxPos] || @battlers[idxPos].fainted? # No target
moveUser = nil
eachBattler do |b|
next if b.opposes?(pos.effects[PBEffects::FutureSightUserIndex])
next if b.pokemonIndex!=pos.effects[PBEffects::FutureSightUserPartyIndex]
moveUser = b
break
end
next if moveUser && moveUser.index==idxPos # Target is the user
if !moveUser # User isn't in battle, get it from the party
party = pbParty(pos.effects[PBEffects::FutureSightUserIndex])
pkmn = party[pos.effects[PBEffects::FutureSightUserPartyIndex]]
if pkmn && pkmn.able?
moveUser = PokeBattle_Battler.new(self,pos.effects[PBEffects::FutureSightUserIndex])
moveUser.pbInitDummyPokemon(pkmn,pos.effects[PBEffects::FutureSightUserPartyIndex])
end
end
next if !moveUser # User is fainted
move = pos.effects[PBEffects::FutureSightMove]
pbDisplay(_INTL("{1} took the {2} attack!",@battlers[idxPos].pbThis,
GameData::Move.get(move).name))
# NOTE: Future Sight failing against the target here doesn't count towards
# Stomping Tantrum.
userLastMoveFailed = moveUser.lastMoveFailed
@futureSight = true
moveUser.pbUseMoveSimple(move,idxPos)
@futureSight = false
moveUser.lastMoveFailed = userLastMoveFailed
@battlers[idxPos].pbFaint if @battlers[idxPos].fainted?
pos.effects[PBEffects::FutureSightCounter] = 0
pos.effects[PBEffects::FutureSightMove] = nil
pos.effects[PBEffects::FutureSightUserIndex] = -1
pos.effects[PBEffects::FutureSightUserPartyIndex] = -1
end
# Wish
@positions.each_with_index do |pos,idxPos|
next if !pos || pos.effects[PBEffects::Wish]==0
pos.effects[PBEffects::Wish] -= 1
next if pos.effects[PBEffects::Wish]>0
next if !@battlers[idxPos] || !@battlers[idxPos].canHeal?
wishMaker = pbThisEx(idxPos,pos.effects[PBEffects::WishMaker])
@battlers[idxPos].pbRecoverHP(pos.effects[PBEffects::WishAmount])
pbDisplay(_INTL("{1}'s wish came true!",wishMaker))
end
# Sea of Fire damage (Fire Pledge + Grass Pledge combination)
curWeather = pbWeather
for side in 0...2
next if sides[side].effects[PBEffects::SeaOfFire]==0
next if [:Rain, :HeavyRain].include?(curWeather)
@battle.pbCommonAnimation("SeaOfFire") if side==0
@battle.pbCommonAnimation("SeaOfFireOpp") if side==1
priority.each do |b|
next if b.opposes?(side)
next if !b.takesIndirectDamage? || b.pbHasType?(:FIRE)
oldHP = b.hp
@scene.pbDamageAnimation(b)
b.pbReduceHP(b.totalhp/8,false)
pbDisplay(_INTL("{1} is hurt by the sea of fire!",b.pbThis))
b.pbItemHPHealCheck
b.pbAbilitiesOnDamageTaken(oldHP)
b.pbFaint if b.fainted?
end
end
# Status-curing effects/abilities and HP-healing items
priority.each do |b|
next if b.fainted?
# Grassy Terrain (healing)
if @field.terrain == :Grassy && b.affectedByTerrain? && b.canHeal?
PBDebug.log("[Lingering effect] Grassy Terrain heals #{b.pbThis(true)}")
b.pbRecoverHP(b.totalhp/16)
pbDisplay(_INTL("{1}'s HP was restored.",b.pbThis))
end
# Healer, Hydration, Shed Skin
BattleHandlers.triggerEORHealingAbility(b.ability,b,self) if b.abilityActive?
# Black Sludge, Leftovers
BattleHandlers.triggerEORHealingItem(b.item,b,self) if b.itemActive?
end
# Aqua Ring
priority.each do |b|
next if !b.effects[PBEffects::AquaRing]
next if !b.canHeal?
hpGain = b.totalhp/16
hpGain = (hpGain*1.3).floor if b.hasActiveItem?(:BIGROOT)
b.pbRecoverHP(hpGain)
pbDisplay(_INTL("Aqua Ring restored {1}'s HP!",b.pbThis(true)))
end
# Ingrain
priority.each do |b|
next if !b.effects[PBEffects::Ingrain]
next if !b.canHeal?
hpGain = b.totalhp/16
hpGain = (hpGain*1.3).floor if b.hasActiveItem?(:BIGROOT)
b.pbRecoverHP(hpGain)
pbDisplay(_INTL("{1} absorbed nutrients with its roots!",b.pbThis))
end
# Leech Seed
priority.each do |b|
next if b.effects[PBEffects::LeechSeed]<0
next if !b.takesIndirectDamage?
recipient = @battlers[b.effects[PBEffects::LeechSeed]]
next if !recipient || recipient.fainted?
oldHP = b.hp
oldHPRecipient = recipient.hp
pbCommonAnimation("LeechSeed",recipient,b)
hpLoss = b.pbReduceHP(b.totalhp/8)
recipient.pbRecoverHPFromDrain(hpLoss,b,
_INTL("{1}'s health is sapped by Leech Seed!",b.pbThis))
recipient.pbAbilitiesOnDamageTaken(oldHPRecipient) if recipient.hp<oldHPRecipient
b.pbItemHPHealCheck
b.pbAbilitiesOnDamageTaken(oldHP)
b.pbFaint if b.fainted?
recipient.pbFaint if recipient.fainted?
end
# Damage from Hyper Mode (Shadow Pokémon)
priority.each do |b|
next if !b.inHyperMode? || @choices[b.index][0]!=:UseMove
hpLoss = b.totalhp/24
@scene.pbDamageAnimation(b)
b.pbReduceHP(hpLoss,false)
pbDisplay(_INTL("The Hyper Mode attack hurts {1}!",b.pbThis(true)))
b.pbFaint if b.fainted?
end
# Damage from poisoning
priority.each do |b|
next if b.fainted?
next if b.status != :POISON
if b.statusCount>0
b.effects[PBEffects::Toxic] += 1
b.effects[PBEffects::Toxic] = 15 if b.effects[PBEffects::Toxic]>15
end
if b.hasActiveAbility?(:POISONHEAL)
if b.canHeal?
anim_name = GameData::Status.get(:POISON).animation
pbCommonAnimation(anim_name, b) if anim_name
pbShowAbilitySplash(b)
b.pbRecoverHP(b.totalhp/8)
if PokeBattle_SceneConstants::USE_ABILITY_SPLASH
pbDisplay(_INTL("{1}'s HP was restored.",b.pbThis))
else
pbDisplay(_INTL("{1}'s {2} restored its HP.",b.pbThis,b.abilityName))
end
pbHideAbilitySplash(b)
end
elsif b.takesIndirectDamage?
oldHP = b.hp
dmg = (b.statusCount==0) ? b.totalhp/8 : b.totalhp*b.effects[PBEffects::Toxic]/16
b.pbContinueStatus { b.pbReduceHP(dmg,false) }
b.pbItemHPHealCheck
b.pbAbilitiesOnDamageTaken(oldHP)
b.pbFaint if b.fainted?
end
end
# Damage from burn
priority.each do |b|
next if b.status != :BURN || !b.takesIndirectDamage?
oldHP = b.hp
dmg = (Settings::MECHANICS_GENERATION >= 7) ? b.totalhp/16 : b.totalhp/8
dmg = (dmg/2.0).round if b.hasActiveAbility?(:HEATPROOF)
b.pbContinueStatus { b.pbReduceHP(dmg,false) }
b.pbItemHPHealCheck
b.pbAbilitiesOnDamageTaken(oldHP)
b.pbFaint if b.fainted?
end
# Damage from sleep (Nightmare)
priority.each do |b|
b.effects[PBEffects::Nightmare] = false if !b.asleep?
next if !b.effects[PBEffects::Nightmare] || !b.takesIndirectDamage?
oldHP = b.hp
b.pbReduceHP(b.totalhp/4)
pbDisplay(_INTL("{1} is locked in a nightmare!",b.pbThis))
b.pbItemHPHealCheck
b.pbAbilitiesOnDamageTaken(oldHP)
b.pbFaint if b.fainted?
end
# Curse
priority.each do |b|
next if !b.effects[PBEffects::Curse] || !b.takesIndirectDamage?
oldHP = b.hp
b.pbReduceHP(b.totalhp/4)
pbDisplay(_INTL("{1} is afflicted by the curse!",b.pbThis))
b.pbItemHPHealCheck
b.pbAbilitiesOnDamageTaken(oldHP)
b.pbFaint if b.fainted?
end
# Trapping attacks (Bind/Clamp/Fire Spin/Magma Storm/Sand Tomb/Whirlpool/Wrap)
priority.each do |b|
next if b.fainted? || b.effects[PBEffects::Trapping]==0
b.effects[PBEffects::Trapping] -= 1
moveName = GameData::Move.get(b.effects[PBEffects::TrappingMove]).name
if b.effects[PBEffects::Trapping]==0
pbDisplay(_INTL("{1} was freed from {2}!",b.pbThis,moveName))
else
case b.effects[PBEffects::TrappingMove]
when :BIND then pbCommonAnimation("Bind", b)
when :CLAMP then pbCommonAnimation("Clamp", b)
when :FIRESPIN then pbCommonAnimation("FireSpin", b)
when :MAGMASTORM then pbCommonAnimation("MagmaStorm", b)
when :SANDTOMB then pbCommonAnimation("SandTomb", b)
when :WRAP then pbCommonAnimation("Wrap", b)
when :INFESTATION then pbCommonAnimation("Infestation", b)
else pbCommonAnimation("Wrap", b)
end
if b.takesIndirectDamage?
hpLoss = (Settings::MECHANICS_GENERATION >= 6) ? b.totalhp/8 : b.totalhp/16
if @battlers[b.effects[PBEffects::TrappingUser]].hasActiveItem?(:BINDINGBAND)
hpLoss = (Settings::MECHANICS_GENERATION >= 6) ? b.totalhp/6 : b.totalhp/8
end
@scene.pbDamageAnimation(b)
b.pbReduceHP(hpLoss,false)
pbDisplay(_INTL("{1} is hurt by {2}!",b.pbThis,moveName))
b.pbItemHPHealCheck
# NOTE: No need to call pbAbilitiesOnDamageTaken as b can't switch out.
b.pbFaint if b.fainted?
end
end
end
# Taunt
pbEORCountDownBattlerEffect(priority,PBEffects::Taunt) { |battler|
pbDisplay(_INTL("{1}'s taunt wore off!",battler.pbThis))
}
# Encore
priority.each do |b|
next if b.fainted? || b.effects[PBEffects::Encore]==0
idxEncoreMove = b.pbEncoredMoveIndex
if idxEncoreMove>=0
b.effects[PBEffects::Encore] -= 1
if b.effects[PBEffects::Encore]==0 || b.moves[idxEncoreMove].pp==0
b.effects[PBEffects::Encore] = 0
pbDisplay(_INTL("{1}'s encore ended!",b.pbThis))
end
else
PBDebug.log("[End of effect] #{b.pbThis}'s encore ended (encored move no longer known)")
b.effects[PBEffects::Encore] = 0
b.effects[PBEffects::EncoreMove] = nil
end
end
# Disable/Cursed Body
pbEORCountDownBattlerEffect(priority,PBEffects::Disable) { |battler|
battler.effects[PBEffects::DisableMove] = nil
pbDisplay(_INTL("{1} is no longer disabled!",battler.pbThis))
}
# Magnet Rise
pbEORCountDownBattlerEffect(priority,PBEffects::MagnetRise) { |battler|
pbDisplay(_INTL("{1}'s electromagnetism wore off!",battler.pbThis))
}
# Telekinesis
pbEORCountDownBattlerEffect(priority,PBEffects::Telekinesis) { |battler|
pbDisplay(_INTL("{1} was freed from the telekinesis!",battler.pbThis))
}
# Heal Block
pbEORCountDownBattlerEffect(priority,PBEffects::HealBlock) { |battler|
pbDisplay(_INTL("{1}'s Heal Block wore off!",battler.pbThis))
}
# Embargo
pbEORCountDownBattlerEffect(priority,PBEffects::Embargo) { |battler|
pbDisplay(_INTL("{1} can use items again!",battler.pbThis))
battler.pbItemTerrainStatBoostCheck
}
# Yawn
pbEORCountDownBattlerEffect(priority,PBEffects::Yawn) { |battler|
if battler.pbCanSleepYawn?
PBDebug.log("[Lingering effect] #{battler.pbThis} fell asleep because of Yawn")
battler.pbSleep
end
}
# Perish Song
perishSongUsers = []
priority.each do |b|
next if b.fainted? || b.effects[PBEffects::PerishSong]==0
b.effects[PBEffects::PerishSong] -= 1
pbDisplay(_INTL("{1}'s perish count fell to {2}!",b.pbThis,b.effects[PBEffects::PerishSong]))
if b.effects[PBEffects::PerishSong]==0
perishSongUsers.push(b.effects[PBEffects::PerishSongUser])
b.pbReduceHP(b.hp)
end
b.pbItemHPHealCheck
b.pbFaint if b.fainted?
end
if perishSongUsers.length>0
# If all remaining Pokemon fainted by a Perish Song triggered by a single side
if (perishSongUsers.find_all { |idxBattler| opposes?(idxBattler) }.length==perishSongUsers.length) ||
(perishSongUsers.find_all { |idxBattler| !opposes?(idxBattler) }.length==perishSongUsers.length)
pbJudgeCheckpoint(@battlers[perishSongUsers[0]])
end
end
# Check for end of battle
if @decision>0
pbGainExp
return
end
for side in 0...2
# Reflect
pbEORCountDownSideEffect(side,PBEffects::Reflect,
_INTL("{1}'s Reflect wore off!",@battlers[side].pbTeam))
# Light Screen
pbEORCountDownSideEffect(side,PBEffects::LightScreen,
_INTL("{1}'s Light Screen wore off!",@battlers[side].pbTeam))
# Safeguard
pbEORCountDownSideEffect(side,PBEffects::Safeguard,
_INTL("{1} is no longer protected by Safeguard!",@battlers[side].pbTeam))
# Mist
pbEORCountDownSideEffect(side,PBEffects::Mist,
_INTL("{1} is no longer protected by mist!",@battlers[side].pbTeam))
# Tailwind
pbEORCountDownSideEffect(side,PBEffects::Tailwind,
_INTL("{1}'s Tailwind petered out!",@battlers[side].pbTeam))
# Lucky Chant
pbEORCountDownSideEffect(side,PBEffects::LuckyChant,
_INTL("{1}'s Lucky Chant wore off!",@battlers[side].pbTeam))
# Pledge Rainbow
pbEORCountDownSideEffect(side,PBEffects::Rainbow,
_INTL("The rainbow on {1}'s side disappeared!",@battlers[side].pbTeam(true)))
# Pledge Sea of Fire
pbEORCountDownSideEffect(side,PBEffects::SeaOfFire,
_INTL("The sea of fire around {1} disappeared!",@battlers[side].pbTeam(true)))
# Pledge Swamp
pbEORCountDownSideEffect(side,PBEffects::Swamp,
_INTL("The swamp around {1} disappeared!",@battlers[side].pbTeam(true)))
# Aurora Veil
pbEORCountDownSideEffect(side,PBEffects::AuroraVeil,
_INTL("{1}'s Aurora Veil wore off!",@battlers[side].pbTeam(true)))
end
# Trick Room
pbEORCountDownFieldEffect(PBEffects::TrickRoom,
_INTL("The twisted dimensions returned to normal!"))
# Gravity
pbEORCountDownFieldEffect(PBEffects::Gravity,
_INTL("Gravity returned to normal!"))
# Water Sport
pbEORCountDownFieldEffect(PBEffects::WaterSportField,
_INTL("The effects of Water Sport have faded."))
# Mud Sport
pbEORCountDownFieldEffect(PBEffects::MudSportField,
_INTL("The effects of Mud Sport have faded."))
# Wonder Room
pbEORCountDownFieldEffect(PBEffects::WonderRoom,
_INTL("Wonder Room wore off, and Defense and Sp. Def stats returned to normal!"))
# Magic Room
pbEORCountDownFieldEffect(PBEffects::MagicRoom,
_INTL("Magic Room wore off, and held items' effects returned to normal!"))
# End of terrains
pbEORTerrain
priority.each do |b|
next if b.fainted?
# Hyper Mode (Shadow Pokémon)
if b.inHyperMode?
if pbRandom(100)<10
b.pokemon.hyper_mode = false
b.pokemon.adjustHeart(-50)
pbDisplay(_INTL("{1} came to its senses!",b.pbThis))
else
pbDisplay(_INTL("{1} is in Hyper Mode!",b.pbThis))
end
end
# Uproar
if b.effects[PBEffects::Uproar]>0
b.effects[PBEffects::Uproar] -= 1
if b.effects[PBEffects::Uproar]==0
pbDisplay(_INTL("{1} calmed down.",b.pbThis))
else
pbDisplay(_INTL("{1} is making an uproar!",b.pbThis))
end
end
# Slow Start's end message
if b.effects[PBEffects::SlowStart]>0
b.effects[PBEffects::SlowStart] -= 1
if b.effects[PBEffects::SlowStart]==0
pbDisplay(_INTL("{1} finally got its act together!",b.pbThis))
end
end
# Bad Dreams, Moody, Speed Boost
BattleHandlers.triggerEOREffectAbility(b.ability,b,self) if b.abilityActive?
# Flame Orb, Sticky Barb, Toxic Orb
BattleHandlers.triggerEOREffectItem(b.item,b,self) if b.itemActive?
# Harvest, Pickup
BattleHandlers.triggerEORGainItemAbility(b.ability,b,self) if b.abilityActive?
end
pbGainExp
return if @decision>0
# Form checks
priority.each { |b| b.pbCheckForm(true) }
# Switch Pokémon in if possible
pbEORSwitch
return if @decision>0
# In battles with at least one side of size 3+, move battlers around if none
# are near to any foes
pbEORShiftDistantBattlers
# Try to make Trace work, check for end of primordial weather
priority.each { |b| b.pbContinualAbilityChecks }
# Reset/count down battler-specific effects (no messages)
eachBattler do |b|
b.effects[PBEffects::BanefulBunker] = false
b.effects[PBEffects::Charge] -= 1 if b.effects[PBEffects::Charge]>0
b.effects[PBEffects::Counter] = -1
b.effects[PBEffects::CounterTarget] = -1
b.effects[PBEffects::Electrify] = false
b.effects[PBEffects::Endure] = false
b.effects[PBEffects::FirstPledge] = 0
b.effects[PBEffects::Flinch] = false
b.effects[PBEffects::FocusPunch] = false
b.effects[PBEffects::FollowMe] = 0
b.effects[PBEffects::HelpingHand] = false
b.effects[PBEffects::HyperBeam] -= 1 if b.effects[PBEffects::HyperBeam]>0
b.effects[PBEffects::KingsShield] = false
b.effects[PBEffects::LaserFocus] -= 1 if b.effects[PBEffects::LaserFocus]>0
if b.effects[PBEffects::LockOn]>0 # Also Mind Reader
b.effects[PBEffects::LockOn] -= 1
b.effects[PBEffects::LockOnPos] = -1 if b.effects[PBEffects::LockOn]==0
end
b.effects[PBEffects::MagicBounce] = false
b.effects[PBEffects::MagicCoat] = false
b.effects[PBEffects::MirrorCoat] = -1
b.effects[PBEffects::MirrorCoatTarget] = -1
b.effects[PBEffects::Powder] = false
b.effects[PBEffects::Prankster] = false
b.effects[PBEffects::PriorityAbility] = false
b.effects[PBEffects::PriorityItem] = false
b.effects[PBEffects::Protect] = false
b.effects[PBEffects::RagePowder] = false
b.effects[PBEffects::Roost] = false
b.effects[PBEffects::Snatch] = 0
b.effects[PBEffects::SpikyShield] = false
b.effects[PBEffects::Spotlight] = 0
b.effects[PBEffects::ThroatChop] -= 1 if b.effects[PBEffects::ThroatChop]>0
b.lastHPLost = 0
b.lastHPLostFromFoe = 0
b.tookDamage = false
b.tookPhysicalHit = false
b.lastRoundMoveFailed = b.lastMoveFailed
b.lastAttacker.clear
b.lastFoeAttacker.clear
end
# Reset/count down side-specific effects (no messages)
for side in 0...2
@sides[side].effects[PBEffects::CraftyShield] = false
if !@sides[side].effects[PBEffects::EchoedVoiceUsed]
@sides[side].effects[PBEffects::EchoedVoiceCounter] = 0
end
@sides[side].effects[PBEffects::EchoedVoiceUsed] = false
@sides[side].effects[PBEffects::MatBlock] = false
@sides[side].effects[PBEffects::QuickGuard] = false
@sides[side].effects[PBEffects::Round] = false
@sides[side].effects[PBEffects::WideGuard] = false
end
# Reset/count down field-specific effects (no messages)
@field.effects[PBEffects::IonDeluge] = false
@field.effects[PBEffects::FairyLock] -= 1 if @field.effects[PBEffects::FairyLock]>0
@field.effects[PBEffects::FusionBolt] = false
@field.effects[PBEffects::FusionFlare] = false
@endOfRound = false
end
end

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,69 @@
# 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 PokeBattle_AI
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
end
return 0 if n<2
mean = sum.to_f/n.to_f
varianceTimesN = 0
choices.each do |c|
next if c[1]<=0
deviation = c[1].to_f-mean
varianceTimesN += deviation*deviation
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)
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
end
#=============================================================================
# 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)
end
end

View File

@@ -0,0 +1,171 @@
class PokeBattle_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, 6, 7, 8].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 => 50,
:HYPERPOTION => 200,
:MAXPOTION => 999,
:BERRYJUICE => 20,
:SWEETHEART => 20,
:FRESHWATER => 50,
:SODAPOP => 60,
:LEMONADE => 80,
:MOOMOOMILK => 100,
:ORANBERRY => 10,
:SITRUSBERRY => battler.totalhp/4,
:ENERGYPOWDER => 50,
:ENERGYROOT => 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, :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 losthp > 0 || battler.status != :NONE
if fullRestoreItems.include?(i)
usableHPItems.push([i, (preferFullRestore) ? 3 : 7, 999])
usableStatusItems.push([i, (preferFullRestore) ? 3 : 9])
next
end
end
# Log single status-curing items
if oneStatusItems.include?(i)
usableStatusItems.push([i, 5])
next
end
# Log Full Heal-type items
if allStatusItems.include?(i)
usableStatusItems.push([i, 7])
next
end
# Log stat-raising items
if xItems[i]
data = xItems[i]
usableXItems.push([i, battler.stages[data[0]], data[1]])
next
end
end
# Prioritise using a HP restoration item
if usableHPItems.length>0 && (battler.hp<=battler.totalhp/4 ||
(battler.hp<=battler.totalhp/2 && pbAIRandom(100)<30))
usableHPItems.sort! { |a,b| (a[1]==b[1]) ? a[2]<=>b[2] : a[1]<=>b[1] }
prevItem = nil
usableHPItems.each do |i|
return i[0], idxTarget if i[2]>=losthp
prevItem = i
end
return prevItem[0], idxTarget
end
# Next prioritise using a status-curing item
if usableStatusItems.length>0 && pbAIRandom(100)<40
usableStatusItems.sort! { |a,b| a[1]<=>b[1] }
return usableStatusItems[0][0], idxTarget
end
# Next try using an X item
if usableXItems.length>0 && pbAIRandom(100)<30
usableXItems.sort! { |a,b| (a[1]==b[1]) ? a[2]<=>b[2] : a[1]<=>b[1] }
prevItem = nil
usableXItems.each do |i|
break if prevItem && i[1]>prevItem[1]
return i[0], idxTarget if i[1]+i[2]>=6
prevItem = i
end
return prevItem[0], idxTarget
end
return nil
end
end

View File

@@ -0,0 +1,178 @@
class PokeBattle_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 = -1
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!="0ED" # 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
shouldSwitch = true if pbAIRandom(100)<80
end
end
# Pokémon is Encored into an unfavourable move
if battler.effects[PBEffects::Encore]>0 && skill>=PBTrainerAI.mediumSkill
idxEncoredMove = battler.pbEncoredMoveIndex
if idxEncoredMove>=0
scoreSum = 0
scoreCount = 0
battler.eachOpposing do |b|
scoreSum += pbGetMoveScore(battler.moves[idxEncoredMove],battler,b,skill)
scoreCount += 1
end
if scoreCount>0 && scoreSum/scoreCount<=20
shouldSwitch = true if pbAIRandom(100)<80
end
end
end
# If there is a single foe and it is resting after Hyper Beam or is
# Truanting (i.e. free turn)
if @battle.pbSideSize(battler.index+1)==1 &&
!battler.pbDirectOpposing.fainted? && skill>=PBTrainerAI.highSkill
opp = battler.pbDirectOpposing
if opp.effects[PBEffects::HyperBeam]>0 ||
(opp.hasActiveAbility?(:TRUANT) && opp.effects[PBEffects::Truant])
shouldSwitch = false if pbAIRandom(100)<80
end
end
# Sudden Death rule - I'm not sure what this means
if @battle.rules["suddendeath"] && battler.turnCount>0
if battler.hp<=battler.totalhp/4 && pbAIRandom(100)<30
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 = []
@battle.pbParty(idxBattler).each_with_index do |pkmn,i|
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]
if pkmn.hp<=pkmn.totalhp/spikesDmg
next if !pkmn.hasType?(:FLYING) && !pkmn.hasActiveAbility?(:LEVITATE)
end
end
end
# moveType is the type of the target's last used move
if moveType>=0 && 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>=0 && 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 = []
party.each_with_index do |_p,i|
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].eachOpposing do |b|
bTypes = b.pbTypes(true)
sum += Effectiveness.calculate(m.type, bTypes[0], bTypes[1], bTypes[2])
end
end
if best==-1 || sum>bestSum
best = i
bestSum = sum
end
end
return best
end
end

View File

@@ -0,0 +1,291 @@
class PokeBattle_AI
#=============================================================================
# Main move-choosing method (moves with higher scores are more likely to be
# chosen)
#=============================================================================
def pbChooseMoves(idxBattler)
user = @battle.battlers[idxBattler]
wildBattler = (@battle.wildBattle? && @battle.opposes?(idxBattler))
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)
badMoves = true if pbAIRandom(100)<80
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 target_data.num_targets > 1
# If move affects multiple battlers and you don't choose a particular one
totalScore = 0
@battle.eachBattler 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
elsif target_data.num_targets == 0
# If move has no targets, affects the user, a side or the whole field
score = pbGetMoveScore(move,user,user,skill)
choices.push([idxMove,score,-1]) if score>0
else
# If move affects one battler and you have to choose which one
scoresAndTargets = []
@battle.eachBattler 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
if !(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
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
miss = false if move.hitsFlyingTargets?
else
if target.inTwoTurnAttack?("0C9","0CC","0CE") # Fly, Bounce, Sky Drop
miss = false if move.hitsFlyingTargets?
elsif target.inTwoTurnAttack?("0CA") # Dig
miss = false if move.hitsDiggingTargets?
elsif target.inTwoTurnAttack?("0CB") # Dive
miss = false if move.hitsDivingTargets?
end
end
end
score -= 80 if miss
end
# Pick a good move for the Choice items
if user.hasActiveItem?([:CHOICEBAND,:CHOICESPECS,:CHOICESCARF])
if move.baseDamage>=60; score += 60
elsif move.damagingMove?; score += 30
elsif move.function=="0F2"; 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
# 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)
# Don't prefer moves that are ineffective because of abilities or effects
return 0 if score<=0 || pbCheckMoveImmunity(score,move,user,target,skill)
# 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=="0C2" # 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
if !target.hasActiveAbility?(:INNERFOCUS) &&
!target.hasActiveAbility?(:SHIELDDUST) &&
target.effects[PBEffects::Substitute]==0
canFlinch = false
if move.canKingsRock? && user.hasActiveItem?([:KINGSROCK,:RAZORFANG])
canFlinch = true
end
if user.hasActiveAbility?(:STENCH) && !move.flinchingMove?
canFlinch = true
end
realDamage *= 1.3 if canFlinch
end
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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,669 @@
class PokeBattle_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 :UserAndAllies
@battle.eachSameSideBattler(user) { |_b| num_targets += 1 }
when :AllNearFoes
@battle.eachOtherSideBattler(user) { |b| num_targets += 1 if b.near?(user) }
when :AllFoes
@battle.eachOtherSideBattler(user) { |_b| num_targets += 1 }
when :AllNearOthers
@battle.eachBattler { |b| num_targets += 1 if b.near?(user) }
when :AllBattlers
@battle.eachBattler { |_b| num_targets += 1 }
end
return num_targets > 1
end
#=============================================================================
# Move's type effectiveness
#=============================================================================
def pbCalcTypeModSingle(moveType,defType,user,target)
ret = Effectiveness.calculate_one(moveType,defType)
# Ring Target
if target.hasActiveItem?(:RINGTARGET)
ret = Effectiveness::NORMAL_EFFECTIVE_ONE if Effectiveness.ineffective_type?(moveType, defType)
end
# Foresight
if user.hasActiveAbility?(:SCRAPPY) || target.effects[PBEffects::Foresight]
ret = Effectiveness::NORMAL_EFFECTIVE_ONE if defType == :GHOST &&
Effectiveness.ineffective_type?(moveType, defType)
end
# Miracle Eye
if target.effects[PBEffects::MiracleEye]
ret = Effectiveness::NORMAL_EFFECTIVE_ONE if defType == :DARK &&
Effectiveness.ineffective_type?(moveType, defType)
end
# Delta Stream's weather
if @battle.pbWeather == :StrongWinds
ret = Effectiveness::NORMAL_EFFECTIVE_ONE if defType == :FLYING &&
Effectiveness.super_effective_type?(moveType, defType)
end
# Grounded Flying-type Pokémon become susceptible to Ground moves
if !target.airborne?
ret = Effectiveness::NORMAL_EFFECTIVE_ONE if defType == :FLYING && moveType == :GROUND
end
return ret
end
def pbCalcTypeMod(moveType,user,target)
return Effectiveness::NORMAL_EFFECTIVE if !moveType
return Effectiveness::NORMAL_EFFECTIVE if moveType == :GROUND &&
target.pbHasType?(:FLYING) && target.hasActiveItem?(:IRONBALL)
# Determine types
tTypes = target.pbTypes(true)
# Get effectivenesses
typeMods = [Effectiveness::NORMAL_EFFECTIVE_ONE] * 3 # 3 types max
if moveType == :SHADOW
if target.shadowPokemon?
typeMods[0] = Effectiveness::NOT_VERY_EFFECTIVE_ONE
else
typeMods[0] = Effectiveness::SUPER_EFFECTIVE_ONE
end
else
tTypes.each_with_index do |type,i|
typeMods[i] = pbCalcTypeModSingle(moveType,type,user,target)
end
end
# Multiply all effectivenesses together
ret = 1
typeMods.each { |m| ret *= m }
return ret
end
# For switching. Determines the effectiveness of a potential switch-in against
# an opposing battler.
def pbCalcTypeModPokemon(battlerThis,_battlerOther)
mod1 = Effectiveness.calculate(battlerThis.type1,target.type1,target.type2)
mod2 = Effectiveness::NORMAL_EFFECTIVE
if battlerThis.type1!=battlerThis.type2
mod2 = Effectiveness.calculate(battlerThis.type2,target.type1,target.type2)
mod2 = mod2.to_f / Effectivenesss::NORMAL_EFFECTIVE
end
return mod1*mod2
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 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 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.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 target.effects[PBEffects::Substitute]>0 && move.statusMove? &&
!move.ignoresSubstitute?(user) && user.index!=target.index
return true if 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
when "010" # Stomp
baseDmg *= 2 if skill>=PBTrainerAI.mediumSkill && target.effects[PBEffects::Minimize]
# Sonic Boom, Dragon Rage, Super Fang, Night Shade, Endeavor
when "06A", "06B", "06C", "06D", "06E"
baseDmg = move.pbFixedDamage(user,target)
when "06F" # Psywave
baseDmg = user.level
when "070" # OHKO
baseDmg = 200
when "071", "072", "073" # Counter, Mirror Coat, Metal Burst
baseDmg = 60
when "075", "076", "0D0", "12D" # Surf, Earthquake, Whirlpool, Shadow Storm
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 "077", "078", "07B", "07C", "07D", "07E", "07F", "080", "085", "087",
"089", "08A", "08B", "08C", "08E", "08F", "090", "091", "092", "097",
"098", "099", "09A", "0F7", "113"
baseDmg = move.pbBaseDamage(baseDmg,user,target)
when "086" # Acrobatics
baseDmg *= 2 if !user.item || user.hasActiveItem?(:FLYINGGEM)
when "08D" # Gyro Ball
targetSpeed = pbRoughStat(target,:SPEED,skill)
userSpeed = pbRoughStat(user,:SPEED,skill)
baseDmg = [[(25*targetSpeed/userSpeed).floor,150].min,1].max
when "094" # Present
baseDmg = 50
when "095" # Magnitude
baseDmg = 71
baseDmg *= 2 if target.inTwoTurnAttack?("0CA") # Dig
when "096" # Natural Gift
baseDmg = move.pbNaturalGiftBaseDamage(user.item_id)
when "09B" # Heavy Slam
baseDmg = move.pbBaseDamage(baseDmg,user,target)
baseDmg *= 2 if Settings::MECHANICS_GENERATION >= 7 && skill>=PBTrainerAI.mediumSkill &&
target.effects[PBEffects::Minimize]
when "0A0", "0BD", "0BE" # Frost Breath, Double Kick, Twineedle
baseDmg *= 2
when "0BF" # Triple Kick
baseDmg *= 6 # Hits do x1, x2, x3 baseDmg in turn, for x6 in total
when "0C0" # Fury Attack
if user.hasActiveAbility?(:SKILLLINK)
baseDmg *= 5
else
baseDmg = (baseDmg*19/6).floor # Average damage dealt
end
when "0C1" # Beat Up
mult = 0
@battle.eachInTeamFromBattlerIndex(user.index) do |pkmn,_i|
mult += 1 if pkmn && pkmn.able? && pkmn.status == :NONE
end
baseDmg *= mult
when "0C4" # Solar Beam
baseDmg = move.pbBaseDamageMultiplier(baseDmg,user,target)
when "0D3" # Rollout
baseDmg *= 2 if user.effects[PBEffects::DefenseCurl]
when "0D4" # Bide
baseDmg = 40
when "0E1" # Final Gambit
baseDmg = user.hp
when "144" # Flying Press
if GameData::Type.exists?(:FLYING)
if skill>=PBTrainerAI.highSkill
targetTypes = target.pbTypes(true)
mult = Effectiveness.calculate(:FLYING,
targetTypes[0],targetTypes[1],targetTypes[2])
baseDmg = (baseDmg.to_f*mult/Effectiveness::NORMAL_EFFECTIVE).round
else
mult = Effectiveness.calculate(:FLYING,
target.type1,target.type2,target.effects[PBEffects::Type3])
baseDmg = (baseDmg.to_f*mult/Effectiveness::NORMAL_EFFECTIVE).round
end
end
baseDmg *= 2 if skill>=PBTrainerAI.mediumSkill && target.effects[PBEffects::Minimize]
when "166" # Stomping Tantrum
baseDmg *= 2 if user.lastRoundMoveFailed
when "175" # 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?(PokeBattle_FixedDamageMove)
# Get the move's type
type = pbRoughType(move,user,skill)
##### Calculate user's attack stat #####
atk = pbRoughStat(user,:ATTACK,skill)
if move.function=="121" # Foul Play
atk = pbRoughStat(target,:ATTACK,skill)
elsif move.specialMove?(type)
if move.function=="121" # 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!="122" # 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
BattleHandlers.triggerDamageCalcUserAbility(user.ability,
user,target,move,multipliers,baseDmg,type)
end
end
if skill>=PBTrainerAI.mediumSkill && !moldBreaker
user.eachAlly do |b|
next if !b.abilityActive?
BattleHandlers.triggerDamageCalcUserAllyAbility(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
BattleHandlers.triggerDamageCalcTargetAbility(target.ability,
user,target,move,multipliers,baseDmg,type)
end
end
if skill>=PBTrainerAI.bestSkill && !moldBreaker
target.eachAlly do |b|
next if !b.abilityActive?
BattleHandlers.triggerDamageCalcTargetAllyAbility(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)
BattleHandlers.triggerDamageCalcUserItem(user.item,
user,target,move,multipliers,baseDmg,type)
end
end
if skill>=PBTrainerAI.bestSkill && target.itemActive?
# NOTE: Type-weakening berries aren't suitable for checking at the start
# of the round.
if !target.item.is_berry?
BattleHandlers.triggerDamageCalcTargetItem(target.item,
user,target,move,multipliers,baseDmg,type)
end
end
# Global abilities
if skill>=PBTrainerAI.mediumSkill
if (@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
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
if user.effects[PBEffects::Charge]>0 && type == :ELECTRIC
multipliers[:base_damage_multiplier] *= 2
end
end
# Mud Sport and Water Sport
if skill>=PBTrainerAI.mediumSkill
if type == :ELECTRIC
@battle.eachBattler do |b|
next if !b.effects[PBEffects::MudSport]
multipliers[:base_damage_multiplier] /= 3
break
end
if @battle.field.effects[PBEffects::MudSportField]>0
multipliers[:base_damage_multiplier] /= 3
end
end
if type == :FIRE
@battle.eachBattler do |b|
next if !b.effects[PBEffects::WaterSport]
multipliers[:base_damage_multiplier] /= 3
break
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
if @battle.internalBattle
# Don't need to check the Atk/Sp Atk-boosting badges because the AI
# won't control the player's Pokémon.
if 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
end
end
# Multi-targeting attacks
if skill>=PBTrainerAI.highSkill
if pbTargetsMultiple?(move,user)
multipliers[:final_damage_multiplier] *= 0.75
end
end
# Weather
if skill>=PBTrainerAI.mediumSkill
case @battle.pbWeather
when :Sun, :HarshSun
if type == :FIRE
multipliers[:final_damage_multiplier] *= 1.5
elsif type == :WATER
multipliers[:final_damage_multiplier] /= 2
end
when :Rain, :HeavyRain
if type == :FIRE
multipliers[:final_damage_multiplier] /= 2
elsif type == :WATER
multipliers[:final_damage_multiplier] *= 1.5
end
when :Sandstorm
if target.pbHasType?(:ROCK) && move.specialMove?(type) && move.function != "122" # Psyshock
multipliers[:defense_multiplier] *= 1.5
end
end
end
# Critical hits - n/a
# Random variance - n/a
# STAB
if skill>=PBTrainerAI.mediumSkill
if type && user.pbHasType?(type)
if user.hasActiveAbility?(:ADAPTABILITY)
multipliers[:final_damage_multiplier] *= 2
else
multipliers[:final_damage_multiplier] *= 1.5
end
end
end
# Type effectiveness
if skill>=PBTrainerAI.mediumSkill
typemod = pbCalcTypeMod(type,user,target)
multipliers[:final_damage_multiplier] *= typemod.to_f / Effectiveness::NORMAL_EFFECTIVE
end
# Burn
if skill>=PBTrainerAI.highSkill
if user.status == :BURN && move.physicalMove?(type) &&
!user.hasActiveAbility?(:GUTS) &&
!(Settings::MECHANICS_GENERATION >= 6 && move.function == "07E") # Facade
multipliers[:final_damage_multiplier] /= 2
end
end
# Aurora Veil, Reflect, Light Screen
if skill>=PBTrainerAI.highSkill
if !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
end
# Minimize
if skill>=PBTrainerAI.highSkill
if target.effects[PBEffects::Minimize] && move.tramplesMinimize?(2)
multipliers[:final_damage_multiplier] *= 2
end
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 = BattleHandlers.triggerCriticalCalcUserAbility(user.ability,user,target,c)
end
if skill>=PBTrainerAI.bestSkill
if c>=0 && !moldBreaker && target.abilityActive?
c = BattleHandlers.triggerCriticalCalcTargetAbility(target.ability,user,target,c)
end
end
# Item effects that alter critical hit rate
if c>=0 && user.itemActive?
c = BattleHandlers.triggerCriticalCalcUserItem(user.item,user,target,c)
end
if skill>=PBTrainerAI.bestSkill
if c>=0 && target.itemActive?
c = BattleHandlers.triggerCriticalCalcTargetItem(target.item,user,target,c)
end
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?(1)
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?
BattleHandlers.triggerAccuracyCalcUserAbility(user.ability,
modifiers,user,target,move,type)
end
user.eachAlly do |b|
next if !b.abilityActive?
BattleHandlers.triggerAccuracyCalcUserAllyAbility(b.ability,
modifiers,user,target,move,type)
end
end
if skill>=PBTrainerAI.bestSkill
if target.abilityActive? && !moldBreaker
BattleHandlers.triggerAccuracyCalcTargetAbility(target.ability,
modifiers,user,target,move,type)
end
end
# Item effects that alter accuracy calculation
if skill>=PBTrainerAI.mediumSkill
if user.itemActive?
BattleHandlers.triggerAccuracyCalcUserItem(user.item,
modifiers,user,target,move,type)
end
end
if skill>=PBTrainerAI.bestSkill
if target.itemActive?
BattleHandlers.triggerAccuracyCalcTargetItem(target.item,
modifiers,user,target,move,type)
end
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 == "0A9" # Chip Away
modifiers[:base_accuracy] = 0 if ["0A5", "139", "13A", "13B", "13C", # "Always hit"
"147"].include?(move.function)
modifiers[:base_accuracy] = 0 if user.effects[PBEffects::LockOn]>0 &&
user.effects[PBEffects::LockOnPos]==target.index
end
if skill>=PBTrainerAI.highSkill
if move.function=="006" # Toxic
modifiers[:base_accuracy] = 0 if Settings::MORE_TYPE_EFFECTS && move.statusMove? &&
user.pbHasType?(:POISON)
end
if move.function=="070" # OHKO moves
modifiers[:base_accuracy] = move.accuracy + user.level - target.level
modifiers[:accuracy_multiplier] = 0 if target.level > user.level
if skill>=PBTrainerAI.bestSkill
modifiers[:accuracy_multiplier] = 0 if target.hasActiveAbility?(:STURDY)
end
end
end
end
end

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,236 @@
$BallTypes = {
0 => :POKEBALL,
1 => :GREATBALL,
2 => :SAFARIBALL,
3 => :ULTRABALL,
4 => :MASTERBALL,
5 => :NETBALL,
6 => :DIVEBALL,
7 => :NESTBALL,
8 => :REPEATBALL,
9 => :TIMERBALL,
10 => :LUXURYBALL,
11 => :PREMIERBALL,
12 => :DUSKBALL,
13 => :HEALBALL,
14 => :QUICKBALL,
15 => :CHERISHBALL,
16 => :FASTBALL,
17 => :LEVELBALL,
18 => :LUREBALL,
19 => :HEAVYBALL,
20 => :LOVEBALL,
21 => :FRIENDBALL,
22 => :MOONBALL,
23 => :SPORTBALL,
24 => :DREAMBALL,
25 => :BEASTBALL
}
def pbBallTypeToItem(ball_type)
ret = GameData::Item.try_get($BallTypes[ball_type])
return ret if ret
ret = GameData::Item.try_get($BallTypes[0])
return ret if ret
return GameData::Item.get(:POKEBALL)
end
def pbGetBallType(ball)
ball = GameData::Item.try_get(ball)
$BallTypes.keys.each do |key|
return key if ball == $BallTypes[key]
end
return 0
end
#===============================================================================
#
#===============================================================================
module BallHandlers
IsUnconditional = ItemHandlerHash.new
ModifyCatchRate = ItemHandlerHash.new
OnCatch = ItemHandlerHash.new
OnFailCatch = ItemHandlerHash.new
def self.isUnconditional?(ball,battle,battler)
ret = IsUnconditional.trigger(ball,battle,battler)
return (ret!=nil) ? ret : false
end
def self.modifyCatchRate(ball,catchRate,battle,battler,ultraBeast)
ret = ModifyCatchRate.trigger(ball,catchRate,battle,battler,ultraBeast)
return (ret!=nil) ? ret : catchRate
end
def self.onCatch(ball,battle,pkmn)
OnCatch.trigger(ball,battle,pkmn)
end
def self.onFailCatch(ball,battle,battler)
OnFailCatch.trigger(ball,battle,battler)
end
end
#===============================================================================
# IsUnconditional
#===============================================================================
BallHandlers::IsUnconditional.add(:MASTERBALL,proc { |ball,battle,battler|
next true
})
#===============================================================================
# ModifyCatchRate
# NOTE: This code is not called if the battler is an Ultra Beast (except if the
# Ball is a Beast Ball). In this case, all Balls' catch rates are set
# elsewhere to 0.1x.
#===============================================================================
BallHandlers::ModifyCatchRate.add(:GREATBALL,proc { |ball,catchRate,battle,battler,ultraBeast|
next catchRate*1.5
})
BallHandlers::ModifyCatchRate.add(:ULTRABALL,proc { |ball,catchRate,battle,battler,ultraBeast|
next catchRate*2
})
BallHandlers::ModifyCatchRate.add(:SAFARIBALL,proc { |ball,catchRate,battle,battler,ultraBeast|
next catchRate*1.5
})
BallHandlers::ModifyCatchRate.add(:NETBALL,proc { |ball,catchRate,battle,battler,ultraBeast|
multiplier = (Settings::NEW_POKE_BALL_CATCH_RATES) ? 3.5 : 3
catchRate *= multiplier if battler.pbHasType?(:BUG) || battler.pbHasType?(:WATER)
next catchRate
})
BallHandlers::ModifyCatchRate.add(:DIVEBALL,proc { |ball,catchRate,battle,battler,ultraBeast|
catchRate *= 3.5 if battle.environment == :Underwater
next catchRate
})
BallHandlers::ModifyCatchRate.add(:NESTBALL,proc { |ball,catchRate,battle,battler,ultraBeast|
if battler.level <= 30
catchRate *= [(41 - battler.level) / 10.0, 1].max
end
next catchRate
})
BallHandlers::ModifyCatchRate.add(:REPEATBALL,proc { |ball,catchRate,battle,battler,ultraBeast|
multiplier = (Settings::NEW_POKE_BALL_CATCH_RATES) ? 3.5 : 3
catchRate *= multiplier if battle.pbPlayer.owned?(battler.species)
next catchRate
})
BallHandlers::ModifyCatchRate.add(:TIMERBALL,proc { |ball,catchRate,battle,battler,ultraBeast|
multiplier = [1+(0.3*battle.turnCount),4].min
catchRate *= multiplier
next catchRate
})
BallHandlers::ModifyCatchRate.add(:DUSKBALL,proc { |ball,catchRate,battle,battler,ultraBeast|
multiplier = (Settings::NEW_POKE_BALL_CATCH_RATES) ? 3 : 3.5
catchRate *= multiplier if battle.time==2
next catchRate
})
BallHandlers::ModifyCatchRate.add(:QUICKBALL,proc { |ball,catchRate,battle,battler,ultraBeast|
catchRate *= 5 if battle.turnCount==0
next catchRate
})
BallHandlers::ModifyCatchRate.add(:FASTBALL,proc { |ball,catchRate,battle,battler,ultraBeast|
baseStats = battler.pokemon.baseStats
baseSpeed = baseStats[:SPEED]
catchRate *= 4 if baseSpeed >= 100
next [catchRate, 255].min
})
BallHandlers::ModifyCatchRate.add(:LEVELBALL,proc { |ball,catchRate,battle,battler,ultraBeast|
maxlevel = 0
battle.eachSameSideBattler do |b|
maxlevel = b.level if b.level>maxlevel
end
if maxlevel>=battler.level*4; catchRate *= 8
elsif maxlevel>=battler.level*2; catchRate *= 4
elsif maxlevel>battler.level; catchRate *= 2
end
next [catchRate,255].min
})
BallHandlers::ModifyCatchRate.add(:LUREBALL,proc { |ball,catchRate,battle,battler,ultraBeast|
multiplier = (Settings::NEW_POKE_BALL_CATCH_RATES) ? 5 : 3
catchRate *= multiplier if GameData::EncounterType.get($PokemonTemp.encounterType).type == :fishing
next [catchRate,255].min
})
BallHandlers::ModifyCatchRate.add(:HEAVYBALL,proc { |ball,catchRate,battle,battler,ultraBeast|
next 0 if catchRate==0
weight = battler.pbWeight
if Settings::NEW_POKE_BALL_CATCH_RATES
if weight>=3000; catchRate += 30
elsif weight>=2000; catchRate += 20
elsif weight<1000; catchRate -= 20
end
else
if weight>=4096; catchRate += 40
elsif weight>=3072; catchRate += 30
elsif weight>=2048; catchRate += 20
else; catchRate -= 20
end
end
catchRate = [catchRate,1].max
next [catchRate,255].min
})
BallHandlers::ModifyCatchRate.add(:LOVEBALL,proc { |ball,catchRate,battle,battler,ultraBeast|
battle.eachSameSideBattler do |b|
next if b.species!=battler.species
next if b.gender==battler.gender || b.gender==2 || battler.gender==2
catchRate *= 8
break
end
next [catchRate,255].min
})
BallHandlers::ModifyCatchRate.add(:MOONBALL,proc { |ball,catchRate,battle,battler,ultraBeast|
# NOTE: Moon Ball cares about whether any species in the target's evolutionary
# family can evolve with the Moon Stone, not whether the target itself
# can immediately evolve with the Moon Stone.
moon_stone = GameData::Item.try_get(:MOONSTONE)
if moon_stone && battler.pokemon.species_data.family_item_evolutions_use_item?(moon_stone.id)
catchRate *= 4
end
next [catchRate, 255].min
})
BallHandlers::ModifyCatchRate.add(:SPORTBALL,proc { |ball,catchRate,battle,battler,ultraBeast|
next catchRate*1.5
})
BallHandlers::ModifyCatchRate.add(:DREAMBALL,proc { |ball,catchRate,battle,battler,ultraBeast|
catchRate *= 4 if battler.status == :SLEEP
next catchRate
})
BallHandlers::ModifyCatchRate.add(:BEASTBALL,proc { |ball,catchRate,battle,battler,ultraBeast|
if ultraBeast
catchRate *= 5
else
catchRate /= 10
end
next catchRate
})
#===============================================================================
# OnCatch
#===============================================================================
BallHandlers::OnCatch.add(:HEALBALL,proc { |ball,battle,pkmn|
pkmn.heal
})
BallHandlers::OnCatch.add(:FRIENDBALL,proc { |ball,battle,pkmn|
pkmn.happiness = 200
})

View File

@@ -0,0 +1,276 @@
class PokeBattle_Animation
def initialize(sprites,viewport)
@sprites = sprites
@viewport = viewport
@pictureEx = [] # For all the PictureEx
@pictureSprites = [] # For all the sprites
@tempSprites = [] # For sprites that exist only for this animation
@animDone = false
createProcesses
end
def dispose
@tempSprites.each { |s| s.dispose if s }
end
def createProcesses; end
def empty?; return @pictureEx.length==0; end
def animDone?; return @animDone; end
def addSprite(s,origin=PictureOrigin::TopLeft)
num = @pictureEx.length
picture = PictureEx.new(s.z)
picture.x = s.x
picture.y = s.y
picture.visible = s.visible
picture.tone = s.tone.clone
picture.setOrigin(0,origin)
@pictureEx[num] = picture
@pictureSprites[num] = s
return picture
end
def addNewSprite(x,y,name,origin=PictureOrigin::TopLeft)
num = @pictureEx.length
picture = PictureEx.new(num)
picture.setXY(0,x,y)
picture.setName(0,name)
picture.setOrigin(0,origin)
@pictureEx[num] = picture
s = IconSprite.new(x,y,@viewport)
s.setBitmap(name)
@pictureSprites[num] = s
@tempSprites.push(s)
return picture
end
def update
return if @animDone
@tempSprites.each { |s| s.update if s }
finished = true
@pictureEx.each_with_index do |p,i|
next if !p.running?
finished = false
p.update
setPictureIconSprite(@pictureSprites[i],p)
end
@animDone = true if finished
end
end
module PokeBattle_BallAnimationMixin
# Returns the color that the Pokémon turns when it goes into or out of its
# Poké Ball.
def getBattlerColorFromPokeBall(poke_ball)
case poke_ball
when :GREATBALL then return Color.new(132, 189, 247)
when :SAFARIBALL then return Color.new(189, 247, 165)
when :ULTRABALL then return Color.new(255, 255, 123)
when :MASTERBALL then return Color.new(189, 165, 231)
when :NETBALL then return Color.new(173, 255, 206)
when :DIVEBALL then return Color.new( 99, 206, 247)
when :NESTBALL then return Color.new(247, 222, 82)
when :REPEATBALL then return Color.new(255, 198, 132)
when :TIMERBALL then return Color.new(239, 247, 247)
when :LUXURYBALL then return Color.new(255, 140, 82)
when :PREMIERBALL then return Color.new(255, 74, 82)
when :DUSKBALL then return Color.new(115, 115, 140)
when :HEALBALL then return Color.new(255, 198, 231)
when :QUICKBALL then return Color.new(140, 214, 255)
when :CHERISHBALL then return Color.new(247, 66, 41)
end
return Color.new(255, 181, 247) # Poké Ball, Sport Ball, Apricorn Balls, others
end
def addBallSprite(ballX, ballY, poke_ball)
file_path = sprintf("Graphics/Battle animations/ball_%s", poke_ball)
if !pbResolveBitmap(file_path)
file_path = sprintf("Graphics/Battle animations/ball_%02d", pbGetBallType(poke_ball).id_number)
end
ball = addNewSprite(ballX, ballY, file_path, PictureOrigin::Center)
@ballSprite = @pictureSprites.last
if @ballSprite.bitmap.width >= @ballSprite.bitmap.height
@ballSprite.src_rect.width = @ballSprite.bitmap.height / 2
ball.setSrcSize(0, @ballSprite.bitmap.height / 2, @ballSprite.bitmap.height)
end
return ball
end
def ballTracksHand(ball,traSprite,safariThrow=false)
# Back sprite isn't animated, no hand-tracking needed
if traSprite.bitmap.width<traSprite.bitmap.height*2
ball.setVisible(7,true)
ballStartX = traSprite.x
ballStartX -= ball.totalDuration*(Graphics.width/(2*16)) if !safariThrow
ballStartY = traSprite.y-traSprite.bitmap.height/2
return ballStartX, ballStartY
end
# Back sprite is animated, make the Poké Ball track the trainer's hand
coordSets = [[traSprite.x-44,traSprite.y-32],[-10,-36],[118,-4]]
case @trainer.trainer_type
when :POKEMONTRAINER_Leaf
coordSets = [[traSprite.x-30,traSprite.y-30],[-18,-36],[118,-6]]
when :POKEMONTRAINER_Brendan
coordSets = [[traSprite.x-46,traSprite.y-40],[-4,-30],[118,-2]]
when :POKEMONTRAINER_May
coordSets = [[traSprite.x-44,traSprite.y-38],[-8,-30],[122,0]]
end
# Arm stretched out behind player
ball.setVisible(0,true)
ball.setXY(0,coordSets[0][0],coordSets[0][1])
ball.moveDelta(0,5,-5*(Graphics.width/(2*16)),0) if !safariThrow
ball.setDelta(0,-12,0) if safariThrow
# Arm mid throw
ball.setDelta(5,coordSets[1][0],coordSets[1][1])
ball.moveDelta(5,2,-2*(Graphics.width/(2*16)),0) if !safariThrow
ball.setDelta(5,34,0) if safariThrow
# Start of throw
ball.setDelta(7,coordSets[2][0],coordSets[2][1])
ball.setDelta(7,-14,0) if safariThrow
# Update Poké Ball trajectory's start position
ballStartX = ballStartY = 0
coordSets.each do |c|
ballStartX += c[0]
ballStartY += c[1]
end
ballStartX -= ball.totalDuration*(Graphics.width/(2*16)) if !safariThrow
ballStartX += 8 if safariThrow # -12 + 34 - 14
return ballStartX, ballStartY
end
def trainerThrowingFrames(ball,trainer,traSprite)
ball.setZ(0,traSprite.z-1)
# Change trainer's frames
size = traSprite.src_rect.width # Width per frame
trainer.setSrc(0,size,0)
trainer.setSrc(5,size*2,0)
trainer.setSrc(7,size*3,0)
trainer.setSrc(9,size*4,0)
trainer.setSrc(18,0,0)
# Alter trainer's positioning
trainer.setDelta(0,-12,0)
trainer.setDelta(5,34,0)
trainer.setDelta(7,-14,0)
trainer.setDelta(9,28,0)
trainer.moveDelta(10,3,-6,6)
trainer.setDelta(18,-4,0)
trainer.setDelta(19,-26,-6)
# Make ball track the trainer's hand
ballStartX, ballStartY = ballTracksHand(ball,traSprite,true)
return ballStartX, ballStartY
end
def createBallTrajectory(ball,delay,duration,startX,startY,midX,midY,endX,endY)
# NOTE: This trajectory is the same regardless of whether the player's
# sprite is being shown on-screen (and sliding off while animating a
# throw). Instead, that throw animation and initialDelay are designed
# to make sure the Ball's trajectory starts at the same position.
ball.setVisible(delay,true)
a = 2*startY - 4*midY + 2*endY
b = 4*midY - 3*startY - endY
c = startY
for i in 1..duration
t = i.to_f/duration # t ranges from 0 to 1
x = startX + (endX-startX)*t # Linear in t
y = a*t**2 + b*t + c # Quadratic in t
ball.moveXY(delay+i-1,1,x,y)
end
createBallTumbling(ball,delay,duration)
end
def createBallTumbling(ball,delay,duration)
# Animate ball frames
numTumbles = 1
numFrames = 1
if @ballSprite && @ballSprite.bitmap.width>=@ballSprite.bitmap.height
# 2* because each frame is twice as tall as it is wide
numFrames = 2*@ballSprite.bitmap.width/@ballSprite.bitmap.height
end
if numFrames>1
curFrame = 0
for i in 1..duration
thisFrame = numFrames*numTumbles*i/duration
if thisFrame>curFrame
curFrame = thisFrame
ball.setSrc(delay+i-1,(curFrame%numFrames)*@ballSprite.bitmap.height/2,0)
end
end
ball.setSrc(delay+duration,0,0)
end
# Rotate ball
ball.moveAngle(delay,duration,360*3)
ball.setAngle(delay+duration,0)
end
def ballSetOpen(ball, delay, poke_ball)
file_path = sprintf("Graphics/Battle animations/ball_%s_open", poke_ball)
if !pbResolveBitmap(file_path)
file_path = sprintf("Graphics/Battle animations/ball_%02d_open", pbGetBallType(poke_ball).id_number)
end
ball.setName(delay, file_path)
if @ballSprite && @ballSprite.bitmap.width >= @ballSprite.bitmap.height
ball.setSrcSize(delay, @ballSprite.bitmap.height / 2, @ballSprite.bitmap.height)
end
end
def ballSetClosed(ball, delay, poke_ball)
file_path = sprintf("Graphics/Battle animations/ball_%s", poke_ball)
if !pbResolveBitmap(file_path)
file_path = sprintf("Graphics/Battle animations/ball_%02d", pbGetBallType(poke_ball).id_number)
end
ball.setName(delay, file_path)
if @ballSprite && @ballSprite.bitmap.width >= @ballSprite.bitmap.height
ball.setSrcSize(delay, @ballSprite.bitmap.height / 2, @ballSprite.bitmap.height)
end
end
def ballOpenUp(ball, delay, poke_ball, showSquish = true, playSE = true)
if showSquish
ball.moveZoomXY(delay, 1, 120, 80) # Squish
ball.moveZoom(delay + 5, 1, 100) # Unsquish
delay += 6
end
ball.setSE(delay, "Battle recall") if playSE
ballSetOpen(ball, delay, poke_ball)
end
def battlerAppear(battler,delay,battlerX,battlerY,batSprite,color)
battler.setVisible(delay,true)
battler.setOpacity(delay,255)
battler.moveXY(delay,5,battlerX,battlerY)
battler.moveZoom(delay,5,100,[batSprite,:pbPlayIntroAnimation])
# NOTE: As soon as the battler sprite finishes zooming, and just as it
# starts changing its tone to normal, it plays its intro animation.
color.alpha = 0
battler.moveColor(delay+5,10,color)
end
def battlerAbsorb(battler,delay,battlerX,battlerY,color)
color.alpha = 255
battler.moveColor(delay,10,color)
delay = battler.totalDuration
battler.moveXY(delay,5,battlerX,battlerY)
battler.moveZoom(delay,5,0)
battler.setVisible(delay+5,false)
end
# The regular Poké Ball burst animation.
def ballBurst(delay, ballX, ballY, poke_ball)
end
# The Poké Ball burst animation used when absorbing a wild Pokémon during a
# capture attempt.
def ballBurstCapture(delay, ballX, ballY, poke_ball)
end
def ballCaptureSuccess(ball, delay, ballX, ballY)
ball.setSE(delay, "Battle catch click")
ball.moveTone(delay, 4, Tone.new(-64, -64, -64, 128))
end
# The Poké Ball burst animation used when recalling a Pokémon.
def ballBurstRecall(delay, ballX, ballY, poke_ball)
end
end

View File

@@ -0,0 +1,869 @@
#===============================================================================
# Shows the battle scene fading in while elements slide around into place
#===============================================================================
class BattleIntroAnimation < PokeBattle_Animation
def initialize(sprites,viewport,battle)
@battle = battle
super(sprites,viewport)
end
def createProcesses
appearTime = 20 # This is in 1/20 seconds
# Background
if @sprites["battle_bg2"]
makeSlideSprite("battle_bg",0.5,appearTime)
makeSlideSprite("battle_bg2",0.5,appearTime)
end
# Bases
makeSlideSprite("base_0",1,appearTime,PictureOrigin::Bottom)
makeSlideSprite("base_1",-1,appearTime,PictureOrigin::Center)
# Player sprite, partner trainer sprite
@battle.player.each_with_index do |_p,i|
makeSlideSprite("player_#{i+1}",1,appearTime,PictureOrigin::Bottom)
end
# Opposing trainer sprite(s) or wild Pokémon sprite(s)
if @battle.trainerBattle?
@battle.opponent.each_with_index do |_p,i|
makeSlideSprite("trainer_#{i+1}",-1,appearTime,PictureOrigin::Bottom)
end
else # Wild battle
@battle.pbParty(1).each_with_index do |_pkmn,i|
idxBattler = 2*i+1
makeSlideSprite("pokemon_#{idxBattler}",-1,appearTime,PictureOrigin::Bottom)
end
end
# Shadows
for i in 0...@battle.battlers.length
makeSlideSprite("shadow_#{i}",((i%2)==0) ? 1 : -1,appearTime,PictureOrigin::Center)
end
# Fading blackness over whole screen
blackScreen = addNewSprite(0,0,"Graphics/Battle animations/black_screen")
blackScreen.setZ(0,999)
blackScreen.moveOpacity(0,8,0)
# Fading blackness over command bar
blackBar = addNewSprite(@sprites["cmdBar_bg"].x,@sprites["cmdBar_bg"].y,
"Graphics/Battle animations/black_bar")
blackBar.setZ(0,998)
blackBar.moveOpacity(appearTime*3/4,appearTime/4,0)
end
def makeSlideSprite(spriteName,deltaMult,appearTime,origin=nil)
# If deltaMult is positive, the sprite starts off to the right and moves
# left (for sprites on the player's side and the background).
return if !@sprites[spriteName]
s = addSprite(@sprites[spriteName],origin)
s.setDelta(0,(Graphics.width*deltaMult).floor,0)
s.moveDelta(0,appearTime,(-Graphics.width*deltaMult).floor,0)
end
end
#===============================================================================
# Shows wild Pokémon fading back to their normal color, and triggers their intro
# animations
#===============================================================================
class BattleIntroAnimation2 < PokeBattle_Animation
def initialize(sprites,viewport,sideSize)
@sideSize = sideSize
super(sprites,viewport)
end
def createProcesses
for i in 0...@sideSize
idxBattler = 2*i+1
next if !@sprites["pokemon_#{idxBattler}"]
battler = addSprite(@sprites["pokemon_#{idxBattler}"],PictureOrigin::Bottom)
battler.moveTone(0,4,Tone.new(0,0,0,0))
battler.setCallback(10*i,[@sprites["pokemon_#{idxBattler}"],:pbPlayIntroAnimation])
end
end
end
#===============================================================================
# Makes a side's party bar and balls appear
#===============================================================================
class LineupAppearAnimation < PokeBattle_Animation
BAR_DISPLAY_WIDTH = 248
def initialize(sprites,viewport,side,party,partyStarts,fullAnim)
@side = side
@party = party
@partyStarts = partyStarts
@fullAnim = fullAnim # True at start of battle, false when switching
resetGraphics(sprites)
super(sprites,viewport)
end
def resetGraphics(sprites)
bar = sprites["partyBar_#{@side}"]
case @side
when 0 # Player's lineup
barX = Graphics.width - BAR_DISPLAY_WIDTH
barY = Graphics.height - 142
ballX = barX + 44
ballY = barY - 30
when 1 # Opposing lineup
barX = BAR_DISPLAY_WIDTH
barY = 114
ballX = barX - 44 - 30 # 30 is width of ball icon
ballY = barY - 30
barX -= bar.bitmap.width
end
ballXdiff = 32*(1-2*@side)
bar.x = barX
bar.y = barY
bar.opacity = 255
bar.visible = false
for i in 0...PokeBattle_SceneConstants::NUM_BALLS
ball = sprites["partyBall_#{@side}_#{i}"]
ball.x = ballX
ball.y = ballY
ball.opacity = 255
ball.visible = false
ballX += ballXdiff
end
end
def getPartyIndexFromBallIndex(idxBall)
# Player's lineup (just show balls for player's party)
if @side==0
return idxBall if @partyStarts.length<2
return idxBall if idxBall<@partyStarts[1]
return -1
end
# Opposing lineup
# NOTE: This doesn't work well for 4+ opposing trainers.
ballsPerTrainer = PokeBattle_SceneConstants::NUM_BALLS/@partyStarts.length # 6/3/2
startsIndex = idxBall/ballsPerTrainer
teamIndex = idxBall%ballsPerTrainer
ret = @partyStarts[startsIndex]+teamIndex
if startsIndex<@partyStarts.length-1
# There is a later trainer, don't spill over into its team
return -1 if ret>=@partyStarts[startsIndex+1]
end
return ret
end
def createProcesses
bar = addSprite(@sprites["partyBar_#{@side}"])
bar.setVisible(0,true)
dir = (@side==0) ? 1 : -1
bar.setDelta(0,dir*Graphics.width/2,0)
bar.moveDelta(0,8,-dir*Graphics.width/2,0)
delay = bar.totalDuration
for i in 0...PokeBattle_SceneConstants::NUM_BALLS
createBall(i,(@fullAnim) ? delay+i*2 : 0,dir)
end
end
def createBall(idxBall,delay,dir)
# Choose ball's graphic
idxParty = getPartyIndexFromBallIndex(idxBall)
graphicFilename = "Graphics/Pictures/Battle/icon_ball_empty"
if idxParty>=0 && idxParty<@party.length && @party[idxParty]
if !@party[idxParty].able?
graphicFilename = "Graphics/Pictures/Battle/icon_ball_faint"
elsif @party[idxParty].status != :NONE
graphicFilename = "Graphics/Pictures/Battle/icon_ball_status"
else
graphicFilename = "Graphics/Pictures/Battle/icon_ball"
end
end
# Set up ball sprite
ball = addSprite(@sprites["partyBall_#{@side}_#{idxBall}"])
ball.setVisible(delay,true)
ball.setName(delay,graphicFilename)
ball.setDelta(delay,dir*Graphics.width/2,0)
ball.moveDelta(delay,8,-dir*Graphics.width/2,0)
end
end
#===============================================================================
# Makes a Pokémon's data box appear
#===============================================================================
class DataBoxAppearAnimation < PokeBattle_Animation
def initialize(sprites,viewport,idxBox)
@idxBox = idxBox
super(sprites,viewport)
end
def createProcesses
return if !@sprites["dataBox_#{@idxBox}"]
box = addSprite(@sprites["dataBox_#{@idxBox}"])
box.setVisible(0,true)
dir = ((@idxBox%2)==0) ? 1 : -1
box.setDelta(0,dir*Graphics.width/2,0)
box.moveDelta(0,8,-dir*Graphics.width/2,0)
end
end
#===============================================================================
# Makes a Pokémon's data box disappear
#===============================================================================
class DataBoxDisappearAnimation < PokeBattle_Animation
def initialize(sprites,viewport,idxBox)
@idxBox = idxBox
super(sprites,viewport)
end
def createProcesses
return if !@sprites["dataBox_#{@idxBox}"] || !@sprites["dataBox_#{@idxBox}"].visible
box = addSprite(@sprites["dataBox_#{@idxBox}"])
dir = ((@idxBox%2)==0) ? 1 : -1
box.moveDelta(0,8,dir*Graphics.width/2,0)
box.setVisible(8,false)
end
end
#===============================================================================
# Makes a Pokémon's ability bar appear
#===============================================================================
class AbilitySplashAppearAnimation < PokeBattle_Animation
def initialize(sprites,viewport,side)
@side = side
super(sprites,viewport)
end
def createProcesses
return if !@sprites["abilityBar_#{@side}"]
bar = addSprite(@sprites["abilityBar_#{@side}"])
bar.setVisible(0,true)
dir = (@side==0) ? 1 : -1
bar.moveDelta(0,8,dir*Graphics.width/2,0)
end
end
#===============================================================================
# Makes a Pokémon's ability bar disappear
#===============================================================================
class AbilitySplashDisappearAnimation < PokeBattle_Animation
def initialize(sprites,viewport,side)
@side = side
super(sprites,viewport)
end
def createProcesses
return if !@sprites["abilityBar_#{@side}"]
bar = addSprite(@sprites["abilityBar_#{@side}"])
dir = (@side==0) ? -1 : 1
bar.moveDelta(0,8,dir*Graphics.width/2,0)
bar.setVisible(8,false)
end
end
#===============================================================================
# Make an enemy trainer slide on-screen from the right. Makes the previous
# trainer slide off to the right first if it is on-screen.
# Used at the end of battle.
#===============================================================================
class TrainerAppearAnimation < PokeBattle_Animation
def initialize(sprites,viewport,idxTrainer)
@idxTrainer = idxTrainer
super(sprites,viewport)
end
def createProcesses
delay = 0
# Make old trainer sprite move off-screen first if necessary
if @idxTrainer>0 && @sprites["trainer_#{@idxTrainer}"].visible
oldTrainer = addSprite(@sprites["trainer_#{@idxTrainer}"],PictureOrigin::Bottom)
oldTrainer.moveDelta(delay,8,Graphics.width/4,0)
oldTrainer.setVisible(delay+8,false)
delay = oldTrainer.totalDuration
end
# Make new trainer sprite move on-screen
if @sprites["trainer_#{@idxTrainer+1}"]
trainerX, trainerY = PokeBattle_SceneConstants.pbTrainerPosition(1)
trainerX += 64+Graphics.width/4
newTrainer = addSprite(@sprites["trainer_#{@idxTrainer+1}"],PictureOrigin::Bottom)
newTrainer.setVisible(delay,true)
newTrainer.setXY(delay,trainerX,trainerY)
newTrainer.moveDelta(delay,8,-Graphics.width/4,0)
end
end
end
#===============================================================================
# Shows the player (and partner) and the player party lineup sliding off screen.
# Shows the player's/partner's throwing animation (if they have one).
# Doesn't show the ball thrown or the Pokémon.
#===============================================================================
class PlayerFadeAnimation < PokeBattle_Animation
def initialize(sprites,viewport,fullAnim=false)
@fullAnim = fullAnim # True at start of battle, false when switching
super(sprites,viewport)
end
def createProcesses
# NOTE: The movement speeds of trainers/bar/balls are all different.
# Move trainer sprite(s) off-screen
spriteNameBase = "player"
i = 1
while @sprites[spriteNameBase+"_#{i}"]
pl = @sprites[spriteNameBase+"_#{i}"]
i += 1
next if !pl.visible || pl.x<0
trainer = addSprite(pl,PictureOrigin::Bottom)
trainer.moveDelta(0,16,-Graphics.width/2,0)
# Animate trainer sprite(s) if they have multiple frames
if pl.bitmap && !pl.bitmap.disposed? && pl.bitmap.width>=pl.bitmap.height*2
size = pl.src_rect.width # Width per frame
trainer.setSrc(0,size,0)
trainer.setSrc(5,size*2,0)
trainer.setSrc(7,size*3,0)
trainer.setSrc(9,size*4,0)
end
trainer.setVisible(16,false)
end
# Move and fade party bar/balls
delay = 3
if @sprites["partyBar_0"] && @sprites["partyBar_0"].visible
partyBar = addSprite(@sprites["partyBar_0"])
partyBar.moveDelta(delay,16,-Graphics.width/4,0) if @fullAnim
partyBar.moveOpacity(delay,12,0)
partyBar.setVisible(delay+12,false)
partyBar.setOpacity(delay+12,255)
end
for i in 0...PokeBattle_SceneConstants::NUM_BALLS
next if !@sprites["partyBall_0_#{i}"] || !@sprites["partyBall_0_#{i}"].visible
partyBall = addSprite(@sprites["partyBall_0_#{i}"])
partyBall.moveDelta(delay+2*i,16,-Graphics.width,0) if @fullAnim
partyBall.moveOpacity(delay,12,0)
partyBall.setVisible(delay+12,false)
partyBall.setOpacity(delay+12,255)
end
end
end
#===============================================================================
# Shows the enemy trainer(s) and the enemy party lineup sliding off screen.
# Doesn't show the ball thrown or the Pokémon.
#===============================================================================
class TrainerFadeAnimation < PokeBattle_Animation
def initialize(sprites,viewport,fullAnim=false)
@fullAnim = fullAnim # True at start of battle, false when switching
super(sprites,viewport)
end
def createProcesses
# NOTE: The movement speeds of trainers/bar/balls are all different.
# Move trainer sprite(s) off-screen
spriteNameBase = "trainer"
i = 1
while @sprites[spriteNameBase+"_#{i}"]
trSprite = @sprites[spriteNameBase+"_#{i}"]
i += 1
next if !trSprite.visible || trSprite.x>Graphics.width
trainer = addSprite(trSprite,PictureOrigin::Bottom)
trainer.moveDelta(0,16,Graphics.width/2,0)
trainer.setVisible(16,false)
end
# Move and fade party bar/balls
delay = 3
if @sprites["partyBar_1"] && @sprites["partyBar_1"].visible
partyBar = addSprite(@sprites["partyBar_1"])
partyBar.moveDelta(delay,16,Graphics.width/4,0) if @fullAnim
partyBar.moveOpacity(delay,12,0)
partyBar.setVisible(delay+12,false)
partyBar.setOpacity(delay+12,255)
end
for i in 0...PokeBattle_SceneConstants::NUM_BALLS
next if !@sprites["partyBall_1_#{i}"] || !@sprites["partyBall_1_#{i}"].visible
partyBall = addSprite(@sprites["partyBall_1_#{i}"])
partyBall.moveDelta(delay+2*i,16,Graphics.width,0) if @fullAnim
partyBall.moveOpacity(delay,12,0)
partyBall.setVisible(delay+12,false)
partyBall.setOpacity(delay+12,255)
end
end
end
#===============================================================================
# Shows a Pokémon being sent out on the player's side (including by a partner).
# Includes the Poké Ball being thrown.
#===============================================================================
class PokeballPlayerSendOutAnimation < PokeBattle_Animation
include PokeBattle_BallAnimationMixin
def initialize(sprites,viewport,idxTrainer,battler,startBattle,idxOrder=0)
@idxTrainer = idxTrainer
@battler = battler
@showingTrainer = startBattle
@idxOrder = idxOrder
@trainer = @battler.battle.pbGetOwnerFromBattlerIndex(@battler.index)
sprites["pokemon_#{battler.index}"].visible = false
@shadowVisible = sprites["shadow_#{battler.index}"].visible
sprites["shadow_#{battler.index}"].visible = false
super(sprites,viewport)
end
def createProcesses
batSprite = @sprites["pokemon_#{@battler.index}"]
shaSprite = @sprites["shadow_#{@battler.index}"]
traSprite = @sprites["player_#{@idxTrainer}"]
# Calculate the Poké Ball graphic to use
poke_ball = (batSprite.pkmn) ? batSprite.pkmn.poke_ball : nil
# Calculate the color to turn the battler sprite
col = getBattlerColorFromPokeBall(poke_ball)
col.alpha = 255
# Calculate start and end coordinates for battler sprite movement
ballPos = PokeBattle_SceneConstants.pbBattlerPosition(@battler.index,batSprite.sideSize)
battlerStartX = ballPos[0] # Is also where the Ball needs to end
battlerStartY = ballPos[1] # Is also where the Ball needs to end + 18
battlerEndX = batSprite.x
battlerEndY = batSprite.y
# Calculate start and end coordinates for Poké Ball sprite movement
ballStartX = -6
ballStartY = 202
ballMidX = 0 # Unused in trajectory calculation
ballMidY = battlerStartY-144
# Set up Poké Ball sprite
ball = addBallSprite(ballStartX,ballStartY,poke_ball)
ball.setZ(0,25)
ball.setVisible(0,false)
# Poké Ball tracking the player's hand animation (if trainer is visible)
if @showingTrainer && traSprite && traSprite.x>0
ball.setZ(0,traSprite.z-1)
ballStartX, ballStartY = ballTracksHand(ball,traSprite)
end
delay = ball.totalDuration # 0 or 7
# Poké Ball trajectory animation
createBallTrajectory(ball,delay,12,
ballStartX,ballStartY,ballMidX,ballMidY,battlerStartX,battlerStartY-18)
ball.setZ(9,batSprite.z-1)
delay = ball.totalDuration+4
delay += 10*@idxOrder # Stagger appearances if multiple Pokémon are sent out at once
ballOpenUp(ball,delay-2,poke_ball)
ballBurst(delay,battlerStartX,battlerStartY-18,poke_ball)
ball.moveOpacity(delay+2,2,0)
# Set up battler sprite
battler = addSprite(batSprite,PictureOrigin::Bottom)
battler.setXY(0,battlerStartX,battlerStartY)
battler.setZoom(0,0)
battler.setColor(0,col)
# Battler animation
battlerAppear(battler,delay,battlerEndX,battlerEndY,batSprite,col)
if @shadowVisible
# Set up shadow sprite
shadow = addSprite(shaSprite,PictureOrigin::Center)
shadow.setOpacity(0,0)
# Shadow animation
shadow.setVisible(delay,@shadowVisible)
shadow.moveOpacity(delay+5,10,255)
end
end
end
#===============================================================================
# Shows a Pokémon being sent out on the opposing side.
# Includes the Poké Ball being "thrown" (although here the Poké Ball just
# appears in the spot where it opens up rather than being thrown to there).
#===============================================================================
class PokeballTrainerSendOutAnimation < PokeBattle_Animation
include PokeBattle_BallAnimationMixin
def initialize(sprites,viewport,idxTrainer,battler,startBattle,idxOrder)
@idxTrainer = idxTrainer
@battler = battler
@showingTrainer = startBattle
@idxOrder = idxOrder
sprites["pokemon_#{battler.index}"].visible = false
@shadowVisible = sprites["shadow_#{battler.index}"].visible
sprites["shadow_#{battler.index}"].visible = false
super(sprites,viewport)
end
def createProcesses
batSprite = @sprites["pokemon_#{@battler.index}"]
shaSprite = @sprites["shadow_#{@battler.index}"]
# Calculate the Poké Ball graphic to use
poke_ball = (batSprite.pkmn) ? batSprite.pkmn.poke_ball : nil
# Calculate the color to turn the battler sprite
col = getBattlerColorFromPokeBall(poke_ball)
col.alpha = 255
# Calculate start and end coordinates for battler sprite movement
ballPos = PokeBattle_SceneConstants.pbBattlerPosition(@battler.index,batSprite.sideSize)
battlerStartX = ballPos[0]
battlerStartY = ballPos[1]
battlerEndX = batSprite.x
battlerEndY = batSprite.y
# Set up Poké Ball sprite
ball = addBallSprite(0,0,poke_ball)
ball.setZ(0,batSprite.z-1)
# Poké Ball animation
createBallTrajectory(ball,battlerStartX,battlerStartY)
delay = ball.totalDuration+6
delay += 10 if @showingTrainer # Give time for trainer to slide off screen
delay += 10*@idxOrder # Stagger appearances if multiple Pokémon are sent out at once
ballOpenUp(ball,delay-2,poke_ball)
ballBurst(delay,battlerStartX,battlerStartY-18,poke_ball)
ball.moveOpacity(delay+2,2,0)
# Set up battler sprite
battler = addSprite(batSprite,PictureOrigin::Bottom)
battler.setXY(0,battlerStartX,battlerStartY)
battler.setZoom(0,0)
battler.setColor(0,col)
# Battler animation
battlerAppear(battler,delay,battlerEndX,battlerEndY,batSprite,col)
if @shadowVisible
# Set up shadow sprite
shadow = addSprite(shaSprite,PictureOrigin::Center)
shadow.setOpacity(0,0)
# Shadow animation
shadow.setVisible(delay,@shadowVisible)
shadow.moveOpacity(delay+5,10,255)
end
end
def createBallTrajectory(ball,destX,destY)
# NOTE: In HGSS, there isn't a Poké Ball arc under any circumstance (neither
# when throwing out the first Pokémon nor when switching/replacing a
# fainted Pokémon). This is probably worth changing.
ball.setXY(0,destX,destY-4)
end
end
#===============================================================================
# Shows a Pokémon being recalled into its Poké Ball
#===============================================================================
class BattlerRecallAnimation < PokeBattle_Animation
include PokeBattle_BallAnimationMixin
def initialize(sprites,viewport,idxBattler)
@idxBattler = idxBattler
super(sprites,viewport)
end
def createProcesses
batSprite = @sprites["pokemon_#{@idxBattler}"]
shaSprite = @sprites["shadow_#{@idxBattler}"]
# Calculate the Poké Ball graphic to use
poke_ball = (batSprite.pkmn) ? batSprite.pkmn.poke_ball : nil
# Calculate the color to turn the battler sprite
col = getBattlerColorFromPokeBall(poke_ball)
col.alpha = 0
# Calculate end coordinates for battler sprite movement
ballPos = PokeBattle_SceneConstants.pbBattlerPosition(@idxBattler,batSprite.sideSize)
battlerEndX = ballPos[0]
battlerEndY = ballPos[1]
# Set up battler sprite
battler = addSprite(batSprite,PictureOrigin::Bottom)
battler.setVisible(0,true)
battler.setColor(0,col)
# Set up Poké Ball sprite
ball = addBallSprite(battlerEndX,battlerEndY,poke_ball)
ball.setZ(0,batSprite.z+1)
# Poké Ball animation
ballOpenUp(ball,0,poke_ball)
delay = ball.totalDuration
ballBurstRecall(delay,battlerEndX,battlerEndY,poke_ball)
ball.moveOpacity(10,2,0)
# Battler animation
battlerAbsorb(battler,delay,battlerEndX,battlerEndY,col)
if shaSprite.visible
# Set up shadow sprite
shadow = addSprite(shaSprite,PictureOrigin::Center)
# Shadow animation
shadow.moveOpacity(0,10,0)
shadow.setVisible(delay,false)
end
end
end
#===============================================================================
# Shows a Pokémon flashing after taking damage
#===============================================================================
class BattlerDamageAnimation < PokeBattle_Animation
def initialize(sprites,viewport,idxBattler,effectiveness)
@idxBattler = idxBattler
@effectiveness = effectiveness
super(sprites,viewport)
end
def createProcesses
batSprite = @sprites["pokemon_#{@idxBattler}"]
shaSprite = @sprites["shadow_#{@idxBattler}"]
# Set up battler/shadow sprite
battler = addSprite(batSprite,PictureOrigin::Bottom)
shadow = addSprite(shaSprite,PictureOrigin::Center)
# Animation
delay = 0
case @effectiveness
when 0 then battler.setSE(delay, "Battle damage normal")
when 1 then battler.setSE(delay, "Battle damage weak")
when 2 then battler.setSE(delay, "Battle damage super")
end
4.times do # 4 flashes, each lasting 0.2 (4/20) seconds
battler.setVisible(delay,false)
shadow.setVisible(delay,false)
battler.setVisible(delay+2,true) if batSprite.visible
shadow.setVisible(delay+2,true) if shaSprite.visible
delay += 4
end
# Restore original battler/shadow sprites visibilities
battler.setVisible(delay,batSprite.visible)
shadow.setVisible(delay,shaSprite.visible)
end
end
#===============================================================================
# Shows a Pokémon fainting
#===============================================================================
class BattlerFaintAnimation < PokeBattle_Animation
def initialize(sprites,viewport,idxBattler,battle)
@idxBattler = idxBattler
@battle = battle
super(sprites,viewport)
end
def createProcesses
batSprite = @sprites["pokemon_#{@idxBattler}"]
shaSprite = @sprites["shadow_#{@idxBattler}"]
# Set up battler/shadow sprite
battler = addSprite(batSprite,PictureOrigin::Bottom)
shadow = addSprite(shaSprite,PictureOrigin::Center)
# Get approx duration depending on sprite's position/size. Min 20 frames.
battlerTop = batSprite.y-batSprite.height
cropY = PokeBattle_SceneConstants.pbBattlerPosition(@idxBattler,
@battle.pbSideSize(@idxBattler))[1]
cropY += 8
duration = (cropY-battlerTop)/8
duration = 10 if duration<10 # Min 0.5 seconds
# Animation
# Play cry
delay = 10
cry = GameData::Species.cry_filename_from_pokemon(batSprite.pkmn)
if cry
battler.setSE(0, cry, nil, 75) # 75 is pitch
delay = GameData::Species.cry_length(batSprite.pkmn) * 20 / Graphics.frame_rate
end
# Sprite drops down
shadow.setVisible(delay,false)
battler.setSE(delay,"Pkmn faint")
battler.moveOpacity(delay,duration,0)
battler.moveDelta(delay,duration,0,cropY-battlerTop)
battler.setCropBottom(delay,cropY)
battler.setVisible(delay+duration,false)
battler.setOpacity(delay+duration,255)
end
end
#===============================================================================
# Shows the player's Poké Ball being thrown to capture a Pokémon
#===============================================================================
class PokeballThrowCaptureAnimation < PokeBattle_Animation
include PokeBattle_BallAnimationMixin
def initialize(sprites,viewport,
poke_ball,numShakes,critCapture,battler,showingTrainer)
@poke_ball = poke_ball
@numShakes = (critCapture) ? 1 : numShakes
@critCapture = critCapture
@battler = battler
@showingTrainer = showingTrainer # Only true if a Safari Zone battle
@shadowVisible = sprites["shadow_#{battler.index}"].visible
@trainer = battler.battle.pbPlayer
super(sprites,viewport)
end
def createProcesses
# Calculate start and end coordinates for battler sprite movement
batSprite = @sprites["pokemon_#{@battler.index}"]
shaSprite = @sprites["shadow_#{@battler.index}"]
traSprite = @sprites["player_1"]
ballPos = PokeBattle_SceneConstants.pbBattlerPosition(@battler.index,batSprite.sideSize)
battlerStartX = batSprite.x
battlerStartY = batSprite.y
ballStartX = -6
ballStartY = 246
ballMidX = 0 # Unused in arc calculation
ballMidY = 78
ballEndX = ballPos[0]
ballEndY = 112
ballGroundY = ballPos[1]-4
# Set up Poké Ball sprite
ball = addBallSprite(ballStartX,ballStartY,@poke_ball)
ball.setZ(0,batSprite.z+1)
@ballSpriteIndex = (@numShakes>=4 || @critCapture) ? @tempSprites.length-1 : -1
# Set up trainer sprite (only visible in Safari Zone battles)
if @showingTrainer && traSprite
if traSprite.bitmap.width>=traSprite.bitmap.height*2
trainer = addSprite(traSprite,PictureOrigin::Bottom)
# Trainer animation
ballStartX, ballStartY = trainerThrowingFrames(ball,trainer,traSprite)
end
end
delay = ball.totalDuration # 0 or 7
# Poké Ball arc animation
ball.setSE(delay,"Battle throw")
createBallTrajectory(ball,delay,16,
ballStartX,ballStartY,ballMidX,ballMidY,ballEndX,ballEndY)
ball.setZ(9,batSprite.z+1)
ball.setSE(delay+16,"Battle ball hit")
# Poké Ball opens up
delay = ball.totalDuration+6
ballOpenUp(ball,delay,@poke_ball,true,false)
# Set up battler sprite
battler = addSprite(batSprite,PictureOrigin::Bottom)
# Poké Ball absorbs battler
delay = ball.totalDuration
ballBurstCapture(delay,ballEndX,ballEndY,@poke_ball)
delay = ball.totalDuration+4
# NOTE: The Pokémon does not change color while being absorbed into a Poké
# Ball during a capture attempt. This may be an oversight in HGSS.
battler.setSE(delay,"Battle jump to ball")
battler.moveXY(delay,5,ballEndX,ballEndY)
battler.moveZoom(delay,5,0)
battler.setVisible(delay+5,false)
if @shadowVisible
# Set up shadow sprite
shadow = addSprite(shaSprite,PictureOrigin::Center)
# Shadow animation
shadow.moveOpacity(delay,5,0)
shadow.moveZoom(delay,5,0)
shadow.setVisible(delay+5,false)
end
# Poké Ball closes
delay = battler.totalDuration
ballSetClosed(ball,delay,@poke_ball)
ball.moveTone(delay,3,Tone.new(96,64,-160,160))
ball.moveTone(delay+5,3,Tone.new(0,0,0,0))
# Poké Ball critical capture animation
delay = ball.totalDuration+3
if @critCapture
ball.setSE(delay,"Battle ball shake")
ball.moveXY(delay,1,ballEndX+4,ballEndY)
ball.moveXY(delay+1,2,ballEndX-4,ballEndY)
ball.moveXY(delay+3,2,ballEndX+4,ballEndY)
ball.setSE(delay+4,"Battle ball shake")
ball.moveXY(delay+5,2,ballEndX-4,ballEndY)
ball.moveXY(delay+7,1,ballEndX,ballEndY)
delay = ball.totalDuration+3
end
# Poké Ball drops to the ground
for i in 0...4
t = [4,4,3,2][i] # Time taken to rise or fall for each bounce
d = [1,2,4,8][i] # Fraction of the starting height each bounce rises to
delay -= t if i==0
if i>0
ball.setZoomXY(delay,100+5*(5-i),100-5*(5-i)) # Squish
ball.moveZoom(delay,2,100) # Unsquish
ball.moveXY(delay,t,ballEndX,ballGroundY-(ballGroundY-ballEndY)/d)
end
ball.moveXY(delay+t,t,ballEndX,ballGroundY)
ball.setSE(delay+2*t,"Battle ball drop",100-i*7)
delay = ball.totalDuration
end
battler.setXY(ball.totalDuration,ballEndX,ballGroundY)
# Poké Ball shakes
delay = ball.totalDuration+12
for i in 0...[@numShakes,3].min
ball.setSE(delay,"Battle ball shake")
ball.moveXY(delay,2,ballEndX-2*(4-i),ballGroundY)
ball.moveAngle(delay,2,5*(4-i)) # positive means counterclockwise
ball.moveXY(delay+2,4,ballEndX+2*(4-i),ballGroundY)
ball.moveAngle(delay+2,4,-5*(4-i)) # negative means clockwise
ball.moveXY(delay+6,2,ballEndX,ballGroundY)
ball.moveAngle(delay+6,2,0)
delay = ball.totalDuration+8
end
if @numShakes==0 || (@numShakes<4 && !@critCapture)
# Poké Ball opens
ball.setZ(delay,batSprite.z-1)
ballOpenUp(ball,delay,@poke_ball,false)
ballBurst(delay,ballEndX,ballGroundY,@poke_ball)
ball.moveOpacity(delay+2,2,0)
# Battler emerges
col = getBattlerColorFromPokeBall(@poke_ball)
col.alpha = 255
battler.setColor(delay,col)
battlerAppear(battler,delay,battlerStartX,battlerStartY,batSprite,col)
if @shadowVisible
shadow.setVisible(delay+5,true)
shadow.setZoom(delay+5,100)
shadow.moveOpacity(delay+5,10,255)
end
else
# Pokémon was caught
ballCaptureSuccess(ball,delay,ballEndX,ballGroundY)
end
end
def dispose
if @ballSpriteIndex>=0
# Capture was successful, the Poké Ball sprite should stay around after
# this animation has finished.
@sprites["captureBall"] = @tempSprites[@ballSpriteIndex]
@tempSprites[@ballSpriteIndex] = nil
end
super
end
end
#===============================================================================
# Shows the player throwing a Poké Ball and it being deflected
#===============================================================================
class PokeballThrowDeflectAnimation < PokeBattle_Animation
include PokeBattle_BallAnimationMixin
def initialize(sprites,viewport,poke_ball,battler)
@poke_ball = poke_ball
@battler = battler
super(sprites,viewport)
end
def createProcesses
# Calculate start and end coordinates for battler sprite movement
batSprite = @sprites["pokemon_#{@battler.index}"]
ballPos = PokeBattle_SceneConstants.pbBattlerPosition(@battler.index,batSprite.sideSize)
ballStartX = -6
ballStartY = 246
ballMidX = 190 # Unused in arc calculation
ballMidY = 78
ballEndX = ballPos[0]
ballEndY = 112
# Set up Poké Ball sprite
ball = addBallSprite(ballStartX,ballStartY,@poke_ball)
ball.setZ(0,90)
# Poké Ball arc animation
ball.setSE(0,"Battle throw")
createBallTrajectory(ball,0,16,
ballStartX,ballStartY,ballMidX,ballMidY,ballEndX,ballEndY)
# Poké Ball knocked back
delay = ball.totalDuration
ball.setSE(delay,"Battle ball drop")
ball.moveXY(delay,8,-32,Graphics.height-96+32) # Back to player's corner
createBallTumbling(ball,delay,8)
end
end

View File

@@ -0,0 +1,67 @@
module PokeBattle_SceneConstants
USE_ABILITY_SPLASH = true
# Text colors
MESSAGE_BASE_COLOR = Color.new(80, 80, 88)
MESSAGE_SHADOW_COLOR = Color.new(160, 160, 168)
# The number of party balls to show in each side's lineup.
NUM_BALLS = Settings::MAX_PARTY_SIZE
# Centre bottom of the player's side base graphic
PLAYER_BASE_X = 128
PLAYER_BASE_Y = Settings::SCREEN_HEIGHT - 80
# Centre middle of the foe's side base graphic
FOE_BASE_X = Settings::SCREEN_WIDTH - 128
FOE_BASE_Y = (Settings::SCREEN_HEIGHT * 3 / 4) - 112
# Returns where the centre bottom of a battler's sprite should be, given its
# index and the number of battlers on its side, assuming the battler has
# metrics of 0 (those are added later).
def self.pbBattlerPosition(index, sideSize = 1)
# Start at the centre of the base for the appropriate side
if (index & 1) == 0
ret = [PLAYER_BASE_X, PLAYER_BASE_Y]
else
ret = [FOE_BASE_X, FOE_BASE_Y]
end
# Shift depending on index (no shifting needed for sideSize of 1)
case sideSize
when 2
ret[0] += [-48, 48, 32, -32][index]
ret[1] += [ 0, 0, 16, -16][index]
when 3
ret[0] += [-80, 80, 0, 0, 80, -80][index]
ret[1] += [ 0, 0, 8, -8, 16, -16][index]
end
return ret
end
# Returns where the centre bottom of a trainer's sprite should be, given its
# side (0/1), index and the number of trainers on its side.
def self.pbTrainerPosition(side, index = 0, sideSize = 1)
# Start at the centre of the base for the appropriate side
if side == 0
ret = [PLAYER_BASE_X, PLAYER_BASE_Y - 16]
else
ret = [FOE_BASE_X, FOE_BASE_Y + 6]
end
# Shift depending on index (no shifting needed for sideSize of 1)
case sideSize
when 2
ret[0] += [-48, 48, 32, -32][2 * index + side]
ret[1] += [ 0, 0, 0, -16][2 * index + side]
when 3
ret[0] += [-80, 80, 0, 0, 80, -80][2 * index + side]
ret[1] += [ 0, 0, 0, -8, 0, -16][2 * index + side]
end
return ret
end
# Default focal points of user and target in animations - do not change!
# Is the centre middle of each sprite
FOCUSUSER_X = 128 # 144
FOCUSUSER_Y = 224 # 188
FOCUSTARGET_X = 384 # 352
FOCUSTARGET_Y = 96 # 108, 98
end

View File

@@ -0,0 +1,659 @@
#===============================================================================
# Data box for regular battles
#===============================================================================
class PokemonDataBox < SpriteWrapper
attr_reader :battler
attr_accessor :selected
attr_reader :animatingHP
attr_reader :animatingExp
# Time in seconds to fully fill the Exp bar (from empty).
EXP_BAR_FILL_TIME = 1.75
# Maximum time in seconds to make a change to the HP bar.
HP_BAR_CHANGE_TIME = 1.0
STATUS_ICON_HEIGHT = 16
NAME_BASE_COLOR = Color.new(72,72,72)
NAME_SHADOW_COLOR = Color.new(184,184,184)
MALE_BASE_COLOR = Color.new(48,96,216)
MALE_SHADOW_COLOR = NAME_SHADOW_COLOR
FEMALE_BASE_COLOR = Color.new(248,88,40)
FEMALE_SHADOW_COLOR = NAME_SHADOW_COLOR
def initialize(battler,sideSize,viewport=nil)
super(viewport)
@battler = battler
@sprites = {}
@spriteX = 0
@spriteY = 0
@spriteBaseX = 0
@selected = 0
@frame = 0
@showHP = false # Specifically, show the HP numbers
@animatingHP = false
@showExp = false # Specifically, show the Exp bar
@animatingExp = false
@expFlash = 0
initializeDataBoxGraphic(sideSize)
initializeOtherGraphics(viewport)
refresh
end
def initializeDataBoxGraphic(sideSize)
onPlayerSide = ((@battler.index%2)==0)
# Get the data box graphic and set whether the HP numbers/Exp bar are shown
if sideSize==1 # One Pokémon on side, use the regular dara box BG
bgFilename = ["Graphics/Pictures/Battle/databox_normal",
"Graphics/Pictures/Battle/databox_normal_foe"][@battler.index%2]
if onPlayerSide
@showHP = true
@showExp = true
end
else # Multiple Pokémon on side, use the thin dara box BG
bgFilename = ["Graphics/Pictures/Battle/databox_thin",
"Graphics/Pictures/Battle/databox_thin_foe"][@battler.index%2]
end
@databoxBitmap = AnimatedBitmap.new(bgFilename)
# Determine the co-ordinates of the data box and the left edge padding width
if onPlayerSide
@spriteX = Graphics.width - 244
@spriteY = Graphics.height - 192
@spriteBaseX = 34
else
@spriteX = -16
@spriteY = 36
@spriteBaseX = 16
end
case sideSize
when 2
@spriteX += [-12, 12, 0, 0][@battler.index]
@spriteY += [-20, -34, 34, 20][@battler.index]
when 3
@spriteX += [-12, 12, -6, 6, 0, 0][@battler.index]
@spriteY += [-42, -46, 4, 0, 50, 46][@battler.index]
end
end
def initializeOtherGraphics(viewport)
# Create other bitmaps
@numbersBitmap = AnimatedBitmap.new(_INTL("Graphics/Pictures/Battle/icon_numbers"))
@hpBarBitmap = AnimatedBitmap.new(_INTL("Graphics/Pictures/Battle/overlay_hp"))
@expBarBitmap = AnimatedBitmap.new(_INTL("Graphics/Pictures/Battle/overlay_exp"))
# Create sprite to draw HP numbers on
@hpNumbers = BitmapSprite.new(124,16,viewport)
pbSetSmallFont(@hpNumbers.bitmap)
@sprites["hpNumbers"] = @hpNumbers
# Create sprite wrapper that displays HP bar
@hpBar = SpriteWrapper.new(viewport)
@hpBar.bitmap = @hpBarBitmap.bitmap
@hpBar.src_rect.height = @hpBarBitmap.height/3
@sprites["hpBar"] = @hpBar
# Create sprite wrapper that displays Exp bar
@expBar = SpriteWrapper.new(viewport)
@expBar.bitmap = @expBarBitmap.bitmap
@sprites["expBar"] = @expBar
# Create sprite wrapper that displays everything except the above
@contents = BitmapWrapper.new(@databoxBitmap.width,@databoxBitmap.height)
self.bitmap = @contents
self.visible = false
self.z = 150+((@battler.index)/2)*5
pbSetSystemFont(self.bitmap)
end
def dispose
pbDisposeSpriteHash(@sprites)
@databoxBitmap.dispose
@numbersBitmap.dispose
@hpBarBitmap.dispose
@expBarBitmap.dispose
@contents.dispose
super
end
def x=(value)
super
@hpBar.x = value+@spriteBaseX+102
@expBar.x = value+@spriteBaseX+6
@hpNumbers.x = value+@spriteBaseX+80
end
def y=(value)
super
@hpBar.y = value+40
@expBar.y = value+74
@hpNumbers.y = value+52
end
def z=(value)
super
@hpBar.z = value+1
@expBar.z = value+1
@hpNumbers.z = value+2
end
def opacity=(value)
super
for i in @sprites
i[1].opacity = value if !i[1].disposed?
end
end
def visible=(value)
super
for i in @sprites
i[1].visible = value if !i[1].disposed?
end
@expBar.visible = (value && @showExp)
end
def color=(value)
super
for i in @sprites
i[1].color = value if !i[1].disposed?
end
end
def battler=(b)
@battler = b
self.visible = (@battler && !@battler.fainted?)
end
def hp
return (@animatingHP) ? @currentHP : @battler.hp
end
def exp_fraction
return (@animatingExp) ? @currentExp.to_f/@rangeExp : @battler.pokemon.exp_fraction
end
def animateHP(oldHP,newHP,rangeHP)
@currentHP = oldHP
@endHP = newHP
@rangeHP = rangeHP
# NOTE: A change in HP takes the same amount of time to animate, no matter
# how big a change it is.
@hpIncPerFrame = (newHP-oldHP).abs/(HP_BAR_CHANGE_TIME*Graphics.frame_rate)
# minInc is the smallest amount that HP is allowed to change per frame.
# This avoids a tiny change in HP still taking HP_BAR_CHANGE_TIME seconds.
minInc = (rangeHP*4)/(@hpBarBitmap.width*HP_BAR_CHANGE_TIME*Graphics.frame_rate)
@hpIncPerFrame = minInc if @hpIncPerFrame<minInc
@animatingHP = true
end
def animateExp(oldExp,newExp,rangeExp)
@currentExp = oldExp
@endExp = newExp
@rangeExp = rangeExp
# NOTE: Filling the Exp bar from empty to full takes EXP_BAR_FILL_TIME
# seconds no matter what. Filling half of it takes half as long, etc.
@expIncPerFrame = rangeExp/(EXP_BAR_FILL_TIME*Graphics.frame_rate)
@animatingExp = true
pbSEPlay("Pkmn exp gain") if @showExp
end
def pbDrawNumber(number,btmp,startX,startY,align=0)
# -1 means draw the / character
n = (number == -1) ? [10] : number.to_i.digits.reverse
charWidth = @numbersBitmap.width/11
charHeight = @numbersBitmap.height
startX -= charWidth*n.length if align==1
n.each do |i|
btmp.blt(startX,startY,@numbersBitmap.bitmap,Rect.new(i*charWidth,0,charWidth,charHeight))
startX += charWidth
end
end
def refresh
self.bitmap.clear
return if !@battler.pokemon
textPos = []
imagePos = []
# Draw background panel
self.bitmap.blt(0,0,@databoxBitmap.bitmap,Rect.new(0,0,@databoxBitmap.width,@databoxBitmap.height))
# Draw Pokémon's name
nameWidth = self.bitmap.text_size(@battler.name).width
nameOffset = 0
nameOffset = nameWidth-116 if nameWidth>116
textPos.push([@battler.name,@spriteBaseX+8-nameOffset,0,false,NAME_BASE_COLOR,NAME_SHADOW_COLOR])
# Draw Pokémon's gender symbol
case @battler.displayGender
when 0 # Male
textPos.push([_INTL(""),@spriteBaseX+126,0,false,MALE_BASE_COLOR,MALE_SHADOW_COLOR])
when 1 # Female
textPos.push([_INTL(""),@spriteBaseX+126,0,false,FEMALE_BASE_COLOR,FEMALE_SHADOW_COLOR])
end
pbDrawTextPositions(self.bitmap,textPos)
# Draw Pokémon's level
imagePos.push(["Graphics/Pictures/Battle/overlay_lv",@spriteBaseX+140,16])
pbDrawNumber(@battler.level,self.bitmap,@spriteBaseX+162,16)
# Draw shiny icon
if @battler.shiny?
shinyX = (@battler.opposes?(0)) ? 206 : -6 # Foe's/player's
imagePos.push(["Graphics/Pictures/shiny",@spriteBaseX+shinyX,36])
end
# Draw Mega Evolution/Primal Reversion icon
if @battler.mega?
imagePos.push(["Graphics/Pictures/Battle/icon_mega",@spriteBaseX+8,34])
elsif @battler.primal?
primalX = (@battler.opposes?) ? 208 : -28 # Foe's/player's
if @battler.isSpecies?(:KYOGRE)
imagePos.push(["Graphics/Pictures/Battle/icon_primal_Kyogre",@spriteBaseX+primalX,4])
elsif @battler.isSpecies?(:GROUDON)
imagePos.push(["Graphics/Pictures/Battle/icon_primal_Groudon",@spriteBaseX+primalX,4])
end
end
# Draw owned icon (foe Pokémon only)
if @battler.owned? && @battler.opposes?(0)
imagePos.push(["Graphics/Pictures/Battle/icon_own",@spriteBaseX+8,36])
end
# Draw status icon
if @battler.status != :NONE
s = GameData::Status.get(@battler.status).id_number
if s == :POISON && @battler.statusCount > 0 # Badly poisoned
s = GameData::Status::DATA.keys.length / 2
end
imagePos.push(["Graphics/Pictures/Battle/icon_statuses",@spriteBaseX+24,36,
0,(s-1)*STATUS_ICON_HEIGHT,-1,STATUS_ICON_HEIGHT])
end
pbDrawImagePositions(self.bitmap,imagePos)
refreshHP
refreshExp
end
def refreshHP
@hpNumbers.bitmap.clear
return if !@battler.pokemon
# Show HP numbers
if @showHP
pbDrawNumber(self.hp,@hpNumbers.bitmap,54,2,1)
pbDrawNumber(-1,@hpNumbers.bitmap,54,2) # / char
pbDrawNumber(@battler.totalhp,@hpNumbers.bitmap,70,2)
end
# Resize HP bar
w = 0
if self.hp>0
w = @hpBarBitmap.width.to_f*self.hp/@battler.totalhp
w = 1 if w<1
# NOTE: The line below snaps the bar's width to the nearest 2 pixels, to
# fit in with the rest of the graphics which are doubled in size.
w = ((w/2.0).round)*2
end
@hpBar.src_rect.width = w
hpColor = 0 # Green bar
hpColor = 1 if self.hp<=@battler.totalhp/2 # Yellow bar
hpColor = 2 if self.hp<=@battler.totalhp/4 # Red bar
@hpBar.src_rect.y = hpColor*@hpBarBitmap.height/3
end
def refreshExp
return if !@showExp
w = exp_fraction * @expBarBitmap.width
# NOTE: The line below snaps the bar's width to the nearest 2 pixels, to
# fit in with the rest of the graphics which are doubled in size.
w = ((w/2).round)*2
@expBar.src_rect.width = w
end
def updateHPAnimation
return if !@animatingHP
if @currentHP<@endHP # Gaining HP
@currentHP += @hpIncPerFrame
@currentHP = @endHP if @currentHP>=@endHP
elsif @currentHP>@endHP # Losing HP
@currentHP -= @hpIncPerFrame
@currentHP = @endHP if @currentHP<=@endHP
end
# Refresh the HP bar/numbers
refreshHP
@animatingHP = false if @currentHP==@endHP
end
def updateExpAnimation
return if !@animatingExp
if !@showExp # Not showing the Exp bar, no need to waste time animating it
@currentExp = @endExp
@animatingExp = false
return
end
if @currentExp<@endExp # Gaining Exp
@currentExp += @expIncPerFrame
@currentExp = @endExp if @currentExp>=@endExp
elsif @currentExp>@endExp # Losing Exp
@currentExp -= @expIncPerFrame
@currentExp = @endExp if @currentExp<=@endExp
end
# Refresh the Exp bar
refreshExp
return if @currentExp!=@endExp # Exp bar still has more to animate
# Exp bar is completely filled, level up with a flash and sound effect
if @currentExp>=@rangeExp
if @expFlash==0
pbSEStop
@expFlash = Graphics.frame_rate/5
pbSEPlay("Pkmn exp full")
self.flash(Color.new(64,200,248,192),@expFlash)
for i in @sprites
i[1].flash(Color.new(64,200,248,192),@expFlash) if !i[1].disposed?
end
else
@expFlash -= 1
@animatingExp = false if @expFlash==0
end
else
pbSEStop
# Exp bar has finished filling, end animation
@animatingExp = false
end
end
QUARTER_ANIM_PERIOD = Graphics.frame_rate*3/20
def updatePositions(frameCounter)
self.x = @spriteX
self.y = @spriteY
# Data box bobbing while Pokémon is selected
if @selected==1 || @selected==2 # Choosing commands/targeted or damaged
case (frameCounter/QUARTER_ANIM_PERIOD).floor
when 1 then self.y = @spriteY-2
when 3 then self.y = @spriteY+2
end
end
end
def update(frameCounter=0)
super()
# Animate HP bar
updateHPAnimation
# Animate Exp bar
updateExpAnimation
# Update coordinates of the data box
updatePositions(frameCounter)
pbUpdateSpriteHash(@sprites)
end
end
#===============================================================================
# Splash bar to announce a triggered ability
#===============================================================================
class AbilitySplashBar < SpriteWrapper
attr_reader :battler
TEXT_BASE_COLOR = Color.new(0,0,0)
TEXT_SHADOW_COLOR = Color.new(248,248,248)
def initialize(side,viewport=nil)
super(viewport)
@side = side
@battler = nil
# Create sprite wrapper that displays background graphic
@bgBitmap = AnimatedBitmap.new(_INTL("Graphics/Pictures/Battle/ability_bar"))
@bgSprite = SpriteWrapper.new(viewport)
@bgSprite.bitmap = @bgBitmap.bitmap
@bgSprite.src_rect.y = (side==0) ? 0 : @bgBitmap.height/2
@bgSprite.src_rect.height = @bgBitmap.height/2
# Create bitmap that displays the text
@contents = BitmapWrapper.new(@bgBitmap.width,@bgBitmap.height/2)
self.bitmap = @contents
pbSetSystemFont(self.bitmap)
# Position the bar
self.x = (side==0) ? -Graphics.width/2 : Graphics.width
self.y = (side==0) ? 180 : 80
self.z = 120
self.visible = false
end
def dispose
@bgSprite.dispose
@bgBitmap.dispose
@contents.dispose
super
end
def x=(value)
super
@bgSprite.x = value
end
def y=(value)
super
@bgSprite.y = value
end
def z=(value)
super
@bgSprite.z = value-1
end
def opacity=(value)
super
@bgSprite.opacity = value
end
def visible=(value)
super
@bgSprite.visible = value
end
def color=(value)
super
@bgSprite.color = value
end
def battler=(value)
@battler = value
refresh
end
def refresh
self.bitmap.clear
return if !@battler
textPos = []
textX = (@side==0) ? 10 : self.bitmap.width-8
# Draw Pokémon's name
textPos.push([_INTL("{1}'s",@battler.name),textX,-4,@side==1,
TEXT_BASE_COLOR,TEXT_SHADOW_COLOR,true])
# Draw Pokémon's ability
textPos.push([@battler.abilityName,textX,26,@side==1,
TEXT_BASE_COLOR,TEXT_SHADOW_COLOR,true])
pbDrawTextPositions(self.bitmap,textPos)
end
def update
super
@bgSprite.update
end
end
#===============================================================================
# Pokémon sprite (used in battle)
#===============================================================================
class PokemonBattlerSprite < RPG::Sprite
attr_reader :pkmn
attr_accessor :index
attr_accessor :selected
attr_reader :sideSize
def initialize(viewport,sideSize,index,battleAnimations)
super(viewport)
@pkmn = nil
@sideSize = sideSize
@index = index
@battleAnimations = battleAnimations
# @selected: 0 = not selected, 1 = choosing action bobbing for this Pokémon,
# 2 = flashing when targeted
@selected = 0
@frame = 0
@updating = false
@spriteX = 0 # Actual x coordinate
@spriteY = 0 # Actual y coordinate
@spriteXExtra = 0 # Offset due to "bobbing" animation
@spriteYExtra = 0 # Offset due to "bobbing" animation
@_iconBitmap = nil
self.visible = false
end
def dispose
@_iconBitmap.dispose if @_iconBitmap
@_iconBitmap = nil
self.bitmap = nil if !self.disposed?
super
end
def x; return @spriteX; end
def y; return @spriteY; end
def x=(value)
@spriteX = value
super(value+@spriteXExtra)
end
def y=(value)
@spriteY = value
super(value+@spriteYExtra)
end
def width; return (self.bitmap) ? self.bitmap.width : 0; end
def height; return (self.bitmap) ? self.bitmap.height : 0; end
def visible=(value)
@spriteVisible = value if !@updating # For selection/targeting flashing
super
end
# Set sprite's origin to bottom middle
def pbSetOrigin
return if !@_iconBitmap
self.ox = @_iconBitmap.width/2
self.oy = @_iconBitmap.height
end
def pbSetPosition
return if !@_iconBitmap
pbSetOrigin
if (@index%2)==0
self.z = 50+5*@index/2
else
self.z = 50-5*(@index+1)/2
end
# Set original position
p = PokeBattle_SceneConstants.pbBattlerPosition(@index,@sideSize)
@spriteX = p[0]
@spriteY = p[1]
# Apply metrics
@pkmn.species_data.apply_metrics_to_sprite(self, @index)
end
def setPokemonBitmap(pkmn,back=false)
@pkmn = pkmn
@_iconBitmap.dispose if @_iconBitmap
@_iconBitmap = GameData::Species.sprite_bitmap_from_pokemon(@pkmn, back)
self.bitmap = (@_iconBitmap) ? @_iconBitmap.bitmap : nil
pbSetPosition
end
# This method plays the battle entrance animation of a Pokémon. By default
# this is just playing the Pokémon's cry, but you can expand on it. The
# recommendation is to create a PictureEx animation and push it into
# the @battleAnimations array.
def pbPlayIntroAnimation(pictureEx=nil)
return if !@pkmn
GameData::Species.play_cry_from_pokemon(@pkmn)
end
QUARTER_ANIM_PERIOD = Graphics.frame_rate*3/20
SIXTH_ANIM_PERIOD = Graphics.frame_rate*2/20
def update(frameCounter=0)
return if !@_iconBitmap
@updating = true
# Update bitmap
@_iconBitmap.update
self.bitmap = @_iconBitmap.bitmap
# Pokémon sprite bobbing while Pokémon is selected
@spriteYExtra = 0
if @selected==1 # When choosing commands for this Pokémon
case (frameCounter/QUARTER_ANIM_PERIOD).floor
when 1 then @spriteYExtra = 2
when 3 then @spriteYExtra = -2
end
end
self.x = self.x
self.y = self.y
self.visible = @spriteVisible
# Pokémon sprite blinking when targeted
if @selected==2 && @spriteVisible
case (frameCounter/SIXTH_ANIM_PERIOD).floor
when 2, 5 then self.visible = false
else self.visible = true
end
end
@updating = false
end
end
#===============================================================================
# Shadow sprite for Pokémon (used in battle)
#===============================================================================
class PokemonBattlerShadowSprite < RPG::Sprite
attr_reader :pkmn
attr_accessor :index
attr_accessor :selected
def initialize(viewport,sideSize,index)
super(viewport)
@pkmn = nil
@sideSize = sideSize
@index = index
@_iconBitmap = nil
self.visible = false
end
def dispose
@_iconBitmap.dispose if @_iconBitmap
@_iconBitmap = nil
self.bitmap = nil if !self.disposed?
super
end
def width; return (self.bitmap) ? self.bitmap.width : 0; end
def height; return (self.bitmap) ? self.bitmap.height : 0; end
# Set sprite's origin to centre
def pbSetOrigin
return if !@_iconBitmap
self.ox = @_iconBitmap.width/2
self.oy = @_iconBitmap.height/2
end
def pbSetPosition
return if !@_iconBitmap
pbSetOrigin
self.z = 3
# Set original position
p = PokeBattle_SceneConstants.pbBattlerPosition(@index,@sideSize)
self.x = p[0]
self.y = p[1]
# Apply metrics
@pkmn.species_data.apply_metrics_to_sprite(self, @index, true)
end
def setPokemonBitmap(pkmn)
@pkmn = pkmn
@_iconBitmap.dispose if @_iconBitmap
@_iconBitmap = GameData::Species.shadow_bitmap_from_pokemon(@pkmn)
self.bitmap = (@_iconBitmap) ? @_iconBitmap.bitmap : nil
pbSetPosition
end
def update(frameCounter=0)
return if !@_iconBitmap
# Update bitmap
@_iconBitmap.update
self.bitmap = @_iconBitmap.bitmap
end
end

View File

@@ -0,0 +1,552 @@
#===============================================================================
# Base class for all three menu classes below
#===============================================================================
class BattleMenuBase
attr_accessor :x
attr_accessor :y
attr_reader :z
attr_reader :visible
attr_reader :color
attr_reader :index
attr_reader :mode
# NOTE: Button width is half the width of the graphic containing them all.
BUTTON_HEIGHT = 46
TEXT_BASE_COLOR = PokeBattle_SceneConstants::MESSAGE_BASE_COLOR
TEXT_SHADOW_COLOR = PokeBattle_SceneConstants::MESSAGE_SHADOW_COLOR
def initialize(viewport=nil)
@x = 0
@y = 0
@z = 0
@visible = false
@color = Color.new(0,0,0,0)
@index = 0
@mode = 0
@disposed = false
@sprites = {}
@visibility = {}
end
def dispose
return if disposed?
pbDisposeSpriteHash(@sprites)
@disposed = true
end
def disposed?; return @disposed; end
def z=(value)
@z = value
for i in @sprites
i[1].z = value if !i[1].disposed?
end
end
def visible=(value)
@visible = value
for i in @sprites
i[1].visible = (value && @visibility[i[0]]) if !i[1].disposed?
end
end
def color=(value)
@color = value
for i in @sprites
i[1].color = value if !i[1].disposed?
end
end
def index=(value)
oldValue = @index
@index = value
@cmdWindow.index = @index if @cmdWindow
refresh if @index!=oldValue
end
def mode=(value)
oldValue = @mode
@mode = value
refresh if @mode!=oldValue
end
def addSprite(key,sprite)
@sprites[key] = sprite
@visibility[key] = true
end
def setIndexAndMode(index,mode)
oldIndex = @index
oldMode = @mode
@index = index
@mode = mode
@cmdWindow.index = @index if @cmdWindow
refresh if @index!=oldIndex || @mode!=oldMode
end
def refresh; end
def update
pbUpdateSpriteHash(@sprites)
end
end
#===============================================================================
# Command menu (Fight/Pokémon/Bag/Run)
#===============================================================================
class CommandMenuDisplay < BattleMenuBase
# If true, displays graphics from Graphics/Pictures/Battle/overlay_command.png
# and Graphics/Pictures/Battle/cursor_command.png.
# If false, just displays text and the command window over the graphic
# Graphics/Pictures/Battle/overlay_message.png. You will need to edit def
# pbShowWindow to make the graphic appear while the command menu is being
# displayed.
USE_GRAPHICS = true
# Lists of which button graphics to use in different situations/types of battle.
MODES = [
[0,2,1,3], # 0 = Regular battle
[0,2,1,9], # 1 = Regular battle with "Cancel" instead of "Run"
[0,2,1,4], # 2 = Regular battle with "Call" instead of "Run"
[5,7,6,3], # 3 = Safari Zone
[0,8,1,3] # 4 = Bug Catching Contest
]
def initialize(viewport,z)
super(viewport)
self.x = 0
self.y = Graphics.height-96
# Create message box (shows "What will X do?")
@msgBox = Window_UnformattedTextPokemon.newWithSize("",
self.x+16,self.y+2,220,Graphics.height-self.y,viewport)
@msgBox.baseColor = TEXT_BASE_COLOR
@msgBox.shadowColor = TEXT_SHADOW_COLOR
@msgBox.windowskin = nil
addSprite("msgBox",@msgBox)
if USE_GRAPHICS
# Create background graphic
background = IconSprite.new(self.x,self.y,viewport)
background.setBitmap("Graphics/Pictures/Battle/overlay_command")
addSprite("background",background)
# Create bitmaps
@buttonBitmap = AnimatedBitmap.new(_INTL("Graphics/Pictures/Battle/cursor_command"))
# Create action buttons
@buttons = Array.new(4) do |i| # 4 command options, therefore 4 buttons
button = SpriteWrapper.new(viewport)
button.bitmap = @buttonBitmap.bitmap
button.x = self.x+Graphics.width-260
button.x += (((i%2)==0) ? 0 : @buttonBitmap.width/2-4)
button.y = self.y+6
button.y += (((i/2)==0) ? 0 : BUTTON_HEIGHT-4)
button.src_rect.width = @buttonBitmap.width/2
button.src_rect.height = BUTTON_HEIGHT
addSprite("button_#{i}",button)
next button
end
else
# Create command window (shows Fight/Bag/Pokémon/Run)
@cmdWindow = Window_CommandPokemon.newWithSize([],
self.x+Graphics.width-240,self.y,240,Graphics.height-self.y,viewport)
@cmdWindow.columns = 2
@cmdWindow.columnSpacing = 4
@cmdWindow.ignore_input = true
addSprite("cmdWindow",@cmdWindow)
end
self.z = z
refresh
end
def dispose
super
@buttonBitmap.dispose if @buttonBitmap
end
def z=(value)
super
@msgBox.z += 1
@cmdWindow.z += 1 if @cmdWindow
end
def setTexts(value)
@msgBox.text = value[0]
return if USE_GRAPHICS
commands = []
for i in 1..4
commands.push(value[i]) if value[i] && value[i]!=nil
end
@cmdWindow.commands = commands
end
def refreshButtons
return if !USE_GRAPHICS
for i in 0...@buttons.length
button = @buttons[i]
button.src_rect.x = (i==@index) ? @buttonBitmap.width/2 : 0
button.src_rect.y = MODES[@mode][i]*BUTTON_HEIGHT
button.z = self.z + ((i==@index) ? 3 : 2)
end
end
def refresh
@msgBox.refresh
@cmdWindow.refresh if @cmdWindow
refreshButtons
end
end
#===============================================================================
# Fight menu (choose a move)
#===============================================================================
class FightMenuDisplay < BattleMenuBase
attr_reader :battler
attr_reader :shiftMode
# If true, displays graphics from Graphics/Pictures/Battle/overlay_fight.png
# and Graphics/Pictures/Battle/cursor_fight.png.
# If false, just displays text and the command window over the graphic
# Graphics/Pictures/Battle/overlay_message.png. You will need to edit def
# pbShowWindow to make the graphic appear while the command menu is being
# displayed.
USE_GRAPHICS = true
TYPE_ICON_HEIGHT = 28
# Text colours of PP of selected move
PP_COLORS = [
Color.new(248,72,72),Color.new(136,48,48), # Red, zero PP
Color.new(248,136,32),Color.new(144,72,24), # Orange, 1/4 of total PP or less
Color.new(248,192,0),Color.new(144,104,0), # Yellow, 1/2 of total PP or less
TEXT_BASE_COLOR,TEXT_SHADOW_COLOR # Black, more than 1/2 of total PP
]
def initialize(viewport,z)
super(viewport)
self.x = 0
self.y = Graphics.height-96
@battler = nil
@shiftMode = 0
# NOTE: @mode is for the display of the Mega Evolution button.
# 0=don't show, 1=show unpressed, 2=show pressed
if USE_GRAPHICS
# Create bitmaps
@buttonBitmap = AnimatedBitmap.new(_INTL("Graphics/Pictures/Battle/cursor_fight"))
@typeBitmap = AnimatedBitmap.new(_INTL("Graphics/Pictures/types"))
@megaEvoBitmap = AnimatedBitmap.new(_INTL("Graphics/Pictures/Battle/cursor_mega"))
@shiftBitmap = AnimatedBitmap.new(_INTL("Graphics/Pictures/Battle/cursor_shift"))
# Create background graphic
background = IconSprite.new(0,Graphics.height-96,viewport)
background.setBitmap("Graphics/Pictures/Battle/overlay_fight")
addSprite("background",background)
# Create move buttons
@buttons = Array.new(Pokemon::MAX_MOVES) do |i|
button = SpriteWrapper.new(viewport)
button.bitmap = @buttonBitmap.bitmap
button.x = self.x+4
button.x += (((i%2)==0) ? 0 : @buttonBitmap.width/2-4)
button.y = self.y+6
button.y += (((i/2)==0) ? 0 : BUTTON_HEIGHT-4)
button.src_rect.width = @buttonBitmap.width/2
button.src_rect.height = BUTTON_HEIGHT
addSprite("button_#{i}",button)
next button
end
# Create overlay for buttons (shows move names)
@overlay = BitmapSprite.new(Graphics.width,Graphics.height-self.y,viewport)
@overlay.x = self.x
@overlay.y = self.y
pbSetNarrowFont(@overlay.bitmap)
addSprite("overlay",@overlay)
# Create overlay for selected move's info (shows move's PP)
@infoOverlay = BitmapSprite.new(Graphics.width,Graphics.height-self.y,viewport)
@infoOverlay.x = self.x
@infoOverlay.y = self.y
pbSetNarrowFont(@infoOverlay.bitmap)
addSprite("infoOverlay",@infoOverlay)
# Create type icon
@typeIcon = SpriteWrapper.new(viewport)
@typeIcon.bitmap = @typeBitmap.bitmap
@typeIcon.x = self.x+416
@typeIcon.y = self.y+20
@typeIcon.src_rect.height = TYPE_ICON_HEIGHT
addSprite("typeIcon",@typeIcon)
# Create Mega Evolution button
@megaButton = SpriteWrapper.new(viewport)
@megaButton.bitmap = @megaEvoBitmap.bitmap
@megaButton.x = self.x+120
@megaButton.y = self.y-@megaEvoBitmap.height/2
@megaButton.src_rect.height = @megaEvoBitmap.height/2
addSprite("megaButton",@megaButton)
# Create Shift button
@shiftButton = SpriteWrapper.new(viewport)
@shiftButton.bitmap = @shiftBitmap.bitmap
@shiftButton.x = self.x+4
@shiftButton.y = self.y-@shiftBitmap.height
addSprite("shiftButton",@shiftButton)
else
# Create message box (shows type and PP of selected move)
@msgBox = Window_AdvancedTextPokemon.newWithSize("",
self.x+320,self.y,Graphics.width-320,Graphics.height-self.y,viewport)
@msgBox.baseColor = TEXT_BASE_COLOR
@msgBox.shadowColor = TEXT_SHADOW_COLOR
pbSetNarrowFont(@msgBox.contents)
addSprite("msgBox",@msgBox)
# Create command window (shows moves)
@cmdWindow = Window_CommandPokemon.newWithSize([],
self.x,self.y,320,Graphics.height-self.y,viewport)
@cmdWindow.columns = 2
@cmdWindow.columnSpacing = 4
@cmdWindow.ignore_input = true
pbSetNarrowFont(@cmdWindow.contents)
addSprite("cmdWindow",@cmdWindow)
end
self.z = z
end
def dispose
super
@buttonBitmap.dispose if @buttonBitmap
@typeBitmap.dispose if @typeBitmap
@megaEvoBitmap.dispose if @megaEvoBitmap
@shiftBitmap.dispose if @shiftBitmap
end
def z=(value)
super
@msgBox.z += 1 if @msgBox
@cmdWindow.z += 2 if @cmdWindow
@overlay.z += 5 if @overlay
@infoOverlay.z += 6 if @infoOverlay
@typeIcon.z += 1 if @typeIcon
end
def battler=(value)
@battler = value
refresh
refreshButtonNames
end
def shiftMode=(value)
oldValue = @shiftMode
@shiftMode = value
refreshShiftButton if @shiftMode!=oldValue
end
def refreshButtonNames
moves = (@battler) ? @battler.moves : []
if !USE_GRAPHICS
# Fill in command window
commands = []
for i in 0...[4, moves.length].max
commands.push((moves[i]) ? moves[i].name : "-")
end
@cmdWindow.commands = commands
return
end
# Draw move names onto overlay
@overlay.bitmap.clear
textPos = []
@buttons.each_with_index do |button,i|
next if !@visibility["button_#{i}"]
x = button.x-self.x+button.src_rect.width/2
y = button.y-self.y+2
moveNameBase = TEXT_BASE_COLOR
if moves[i].type
# NOTE: This takes a colour from a particular pixel in the button
# graphic and makes the move name's base colour that same colour.
# The pixel is at coordinates 10,34 in the button box. If you
# change the graphic, you may want to change/remove the below line
# of code to ensure the font is an appropriate colour.
moveNameBase = button.bitmap.get_pixel(10,button.src_rect.y+34)
end
textPos.push([moves[i].name,x,y,2,moveNameBase,TEXT_SHADOW_COLOR])
end
pbDrawTextPositions(@overlay.bitmap,textPos)
end
def refreshSelection
moves = (@battler) ? @battler.moves : []
if USE_GRAPHICS
# Choose appropriate button graphics and z positions
@buttons.each_with_index do |button,i|
if !moves[i]
@visibility["button_#{i}"] = false
next
end
@visibility["button_#{i}"] = true
button.src_rect.x = (i==@index) ? @buttonBitmap.width/2 : 0
button.src_rect.y = GameData::Type.get(moves[i].type).id_number * BUTTON_HEIGHT
button.z = self.z + ((i==@index) ? 4 : 3)
end
end
refreshMoveData(moves[@index])
end
def refreshMoveData(move)
# Write PP and type of the selected move
if !USE_GRAPHICS
moveType = GameData::Type.get(move.type).name
if move.total_pp<=0
@msgBox.text = _INTL("PP: ---<br>TYPE/{1}",moveType)
else
@msgBox.text = _ISPRINTF("PP: {1: 2d}/{2: 2d}<br>TYPE/{3:s}",
move.pp,move.total_pp,moveType)
end
return
end
@infoOverlay.bitmap.clear
if !move
@visibility["typeIcon"] = false
return
end
@visibility["typeIcon"] = true
# Type icon
type_number = GameData::Type.get(move.type).id_number
@typeIcon.src_rect.y = type_number * TYPE_ICON_HEIGHT
# PP text
if move.total_pp>0
ppFraction = [(4.0*move.pp/move.total_pp).ceil,3].min
textPos = []
textPos.push([_INTL("PP: {1}/{2}",move.pp,move.total_pp),
448,44,2,PP_COLORS[ppFraction*2],PP_COLORS[ppFraction*2+1]])
pbDrawTextPositions(@infoOverlay.bitmap,textPos)
end
end
def refreshMegaEvolutionButton
return if !USE_GRAPHICS
@megaButton.src_rect.y = (@mode - 1) * @megaEvoBitmap.height / 2
@megaButton.x = self.x + ((@shiftMode > 0) ? 204 : 120)
@megaButton.z = self.z - 1
@visibility["megaButton"] = (@mode > 0)
end
def refreshShiftButton
return if !USE_GRAPHICS
@shiftButton.src_rect.y = (@shiftMode - 1) * @shiftBitmap.height
@shiftButton.z = self.z - 1
@visibility["shiftButton"] = (@shiftMode > 0)
end
def refresh
return if !@battler
refreshSelection
refreshMegaEvolutionButton
refreshShiftButton
end
end
#===============================================================================
# Target menu (choose a move's target)
# NOTE: Unlike the command and fight menus, this one doesn't have a textbox-only
# version.
#===============================================================================
class TargetMenuDisplay < BattleMenuBase
attr_accessor :mode
# Lists of which button graphics to use in different situations/types of battle.
MODES = [
[0,2,1,3], # 0 = Regular battle
[0,2,1,9], # 1 = Regular battle with "Cancel" instead of "Run"
[0,2,1,4], # 2 = Regular battle with "Call" instead of "Run"
[5,7,6,3], # 3 = Safari Zone
[0,8,1,3] # 4 = Bug Catching Contest
]
CMD_BUTTON_WIDTH_SMALL = 170
TEXT_BASE_COLOR = Color.new(240,248,224)
TEXT_SHADOW_COLOR = Color.new(64,64,64)
def initialize(viewport,z,sideSizes)
super(viewport)
@sideSizes = sideSizes
maxIndex = (@sideSizes[0]>@sideSizes[1]) ? (@sideSizes[0]-1)*2 : @sideSizes[1]*2-1
@smallButtons = (@sideSizes.max>2)
self.x = 0
self.y = Graphics.height-96
@texts = []
# NOTE: @mode is for which buttons are shown as selected.
# 0=select 1 button (@index), 1=select all buttons with text
# Create bitmaps
@buttonBitmap = AnimatedBitmap.new(_INTL("Graphics/Pictures/Battle/cursor_target"))
# Create target buttons
@buttons = Array.new(maxIndex+1) do |i|
numButtons = @sideSizes[i%2]
next if numButtons<=i/2
# NOTE: Battler indexes go from left to right from the perspective of
# that side's trainer, so inc is different for each side for the
# same value of i/2.
inc = ((i%2)==0) ? i/2 : numButtons-1-i/2
button = SpriteWrapper.new(viewport)
button.bitmap = @buttonBitmap.bitmap
button.src_rect.width = (@smallButtons) ? CMD_BUTTON_WIDTH_SMALL : @buttonBitmap.width/2
button.src_rect.height = BUTTON_HEIGHT
if @smallButtons
button.x = self.x+170-[0,82,166][numButtons-1]
else
button.x = self.x+138-[0,116][numButtons-1]
end
button.x += (button.src_rect.width-4)*inc
button.y = self.y+6
button.y += (BUTTON_HEIGHT-4)*((i+1)%2)
addSprite("button_#{i}",button)
next button
end
# Create overlay (shows target names)
@overlay = BitmapSprite.new(Graphics.width,Graphics.height-self.y,viewport)
@overlay.x = self.x
@overlay.y = self.y
pbSetNarrowFont(@overlay.bitmap)
addSprite("overlay",@overlay)
self.z = z
refresh
end
def dispose
super
@buttonBitmap.dispose if @buttonBitmap
end
def z=(value)
super
@overlay.z += 5 if @overlay
end
def setDetails(texts,mode)
@texts = texts
@mode = mode
refresh
end
def refreshButtons
# Choose appropriate button graphics and z positions
@buttons.each_with_index do |button,i|
next if !button
sel = false
buttonType = 0
if @texts[i]
sel ||= (@mode==0 && i==@index)
sel ||= (@mode==1)
buttonType = ((i%2)==0) ? 1 : 2
end
buttonType = 2*buttonType + ((@smallButtons) ? 1 : 0)
button.src_rect.x = (sel) ? @buttonBitmap.width/2 : 0
button.src_rect.y = buttonType*BUTTON_HEIGHT
button.z = self.z + ((sel) ? 3 : 2)
end
# Draw target names onto overlay
@overlay.bitmap.clear
textpos = []
@buttons.each_with_index do |button,i|
next if !button || @texts[i].nil? || @texts[i]==""
x = button.x-self.x+button.src_rect.width/2
y = button.y-self.y+2
textpos.push([@texts[i],x,y,2,TEXT_BASE_COLOR,TEXT_SHADOW_COLOR])
end
pbDrawTextPositions(@overlay.bitmap,textpos)
end
def refresh
refreshButtons
end
end

View File

@@ -0,0 +1,349 @@
# Battle scene (the visuals of the battle)
class PokeBattle_Scene
attr_accessor :abortable # For non-interactive battles, can quit immediately
attr_reader :viewport
attr_reader :sprites
BLANK = 0
MESSAGE_BOX = 1
COMMAND_BOX = 2
FIGHT_BOX = 3
TARGET_BOX = 4
MESSAGE_PAUSE_TIME = (Graphics.frame_rate*1.0).floor # 1 second
#=============================================================================
# Updating and refreshing
#=============================================================================
def pbUpdate(cw=nil)
pbGraphicsUpdate
pbInputUpdate
pbFrameUpdate(cw)
end
def pbGraphicsUpdate
# Update lineup animations
if @animations.length>0
shouldCompact = false
@animations.each_with_index do |a,i|
a.update
if a.animDone?
a.dispose
@animations[i] = nil
shouldCompact = true
end
end
@animations.compact! if shouldCompact
end
# Update other graphics
@sprites["battle_bg"].update if @sprites["battle_bg"].respond_to?("update")
Graphics.update
@frameCounter += 1
@frameCounter = @frameCounter%(Graphics.frame_rate*12/20)
end
def pbInputUpdate
Input.update
if Input.trigger?(Input::BACK) && @abortable && !@aborted
@aborted = true
@battle.pbAbort
end
end
def pbFrameUpdate(cw=nil)
cw.update if cw
@battle.battlers.each_with_index do |b,i|
next if !b
@sprites["dataBox_#{i}"].update(@frameCounter) if @sprites["dataBox_#{i}"]
@sprites["pokemon_#{i}"].update(@frameCounter) if @sprites["pokemon_#{i}"]
@sprites["shadow_#{i}"].update(@frameCounter) if @sprites["shadow_#{i}"]
end
end
def pbRefresh
@battle.battlers.each_with_index do |b,i|
next if !b
@sprites["dataBox_#{i}"].refresh if @sprites["dataBox_#{i}"]
end
end
def pbRefreshOne(idxBattler)
@sprites["dataBox_#{idxBattler}"].refresh if @sprites["dataBox_#{idxBattler}"]
end
#=============================================================================
# Party lineup
#=============================================================================
# Returns whether the party line-ups are currently coming on-screen
def inPartyAnimation?
return @animations.length>0
end
#=============================================================================
# Window displays
#=============================================================================
def pbShowWindow(windowType)
# NOTE: If you are not using fancy graphics for the command/fight menus, you
# will need to make "messageBox" also visible if the windowtype if
# COMMAND_BOX/FIGHT_BOX respectively.
@sprites["messageBox"].visible = (windowType==MESSAGE_BOX)
@sprites["messageWindow"].visible = (windowType==MESSAGE_BOX)
@sprites["commandWindow"].visible = (windowType==COMMAND_BOX)
@sprites["fightWindow"].visible = (windowType==FIGHT_BOX)
@sprites["targetWindow"].visible = (windowType==TARGET_BOX)
end
# This is for the end of brief messages, which have been lingering on-screen
# while other things happened. This is only called when another message wants
# to be shown, and makes the brief message linger for one more second first.
# Some animations skip this extra second by setting @briefMessage to false
# despite not having any other messages to show.
def pbWaitMessage
return if !@briefMessage
pbShowWindow(MESSAGE_BOX)
cw = @sprites["messageWindow"]
MESSAGE_PAUSE_TIME.times do
pbUpdate(cw)
end
cw.text = ""
cw.visible = false
@briefMessage = false
end
# NOTE: A regular message is displayed for 1 second after it fully appears (or
# less if Back/Use is pressed). Disappears automatically after that time.
def pbDisplayMessage(msg,brief=false)
pbWaitMessage
pbShowWindow(MESSAGE_BOX)
cw = @sprites["messageWindow"]
cw.setText(msg)
PBDebug.log(msg)
yielded = false
i = 0
loop do
pbUpdate(cw)
if !cw.busy?
if !yielded
yield if block_given? # For playing SE as soon as the message is all shown
yielded = true
end
if brief
# NOTE: A brief message lingers on-screen while other things happen. A
# regular message has to end before the game can continue.
@briefMessage = true
break
end
if i>=MESSAGE_PAUSE_TIME # Autoclose after 1 second
cw.text = ""
cw.visible = false
break
end
i += 1
end
if Input.trigger?(Input::BACK) || Input.trigger?(Input::USE) || @abortable
if cw.busy?
pbPlayDecisionSE if cw.pausing? && !@abortable
cw.skipAhead
elsif !@abortable
cw.text = ""
cw.visible = false
break
end
end
end
end
alias pbDisplay pbDisplayMessage
# NOTE: A paused message has the arrow in the bottom corner indicating there
# is another message immediately afterward. It is displayed for 3
# seconds after it fully appears (or less if B/C is pressed) and
# disappears automatically after that time, except at the end of battle.
def pbDisplayPausedMessage(msg)
pbWaitMessage
pbShowWindow(MESSAGE_BOX)
cw = @sprites["messageWindow"]
cw.text = _INTL("{1}\1",msg)
PBDebug.log(msg)
yielded = false
i = 0
loop do
pbUpdate(cw)
if !cw.busy?
if !yielded
yield if block_given? # For playing SE as soon as the message is all shown
yielded = true
end
if !@battleEnd
if i>=MESSAGE_PAUSE_TIME*3 # Autoclose after 3 seconds
cw.text = ""
cw.visible = false
break
end
i += 1
end
end
if Input.trigger?(Input::BACK) || Input.trigger?(Input::USE) || @abortable
if cw.busy?
pbPlayDecisionSE if cw.pausing? && !@abortable
cw.skipAhead
elsif !@abortable
cw.text = ""
pbPlayDecisionSE
break
end
end
end
end
def pbDisplayConfirmMessage(msg)
return pbShowCommands(msg,[_INTL("Yes"),_INTL("No")],1)==0
end
def pbShowCommands(msg,commands,defaultValue)
pbWaitMessage
pbShowWindow(MESSAGE_BOX)
dw = @sprites["messageWindow"]
dw.text = msg
cw = Window_CommandPokemon.new(commands)
cw.x = Graphics.width-cw.width
cw.y = Graphics.height-cw.height-dw.height
cw.z = dw.z+1
cw.index = 0
cw.viewport = @viewport
PBDebug.log(msg)
loop do
cw.visible = (!dw.busy?)
pbUpdate(cw)
dw.update
if Input.trigger?(Input::BACK) && defaultValue>=0
if dw.busy?
pbPlayDecisionSE if dw.pausing?
dw.resume
else
cw.dispose
dw.text = ""
return defaultValue
end
elsif Input.trigger?(Input::USE)
if dw.busy?
pbPlayDecisionSE if dw.pausing?
dw.resume
else
cw.dispose
dw.text = ""
return cw.index
end
end
end
end
#=============================================================================
# Sprites
#=============================================================================
def pbAddSprite(id,x,y,filename,viewport)
sprite = IconSprite.new(x,y,viewport)
if filename
sprite.setBitmap(filename) rescue nil
end
@sprites[id] = sprite
return sprite
end
def pbAddPlane(id,filename,viewport)
sprite = AnimatedPlane.new(viewport)
if filename
sprite.setBitmap(filename)
end
@sprites[id] = sprite
return sprite
end
def pbDisposeSprites
pbDisposeSpriteHash(@sprites)
end
# Used by Ally Switch.
def pbSwapBattlerSprites(idxA,idxB)
@sprites["pokemon_#{idxA}"], @sprites["pokemon_#{idxB}"] = @sprites["pokemon_#{idxB}"], @sprites["pokemon_#{idxA}"]
@sprites["shadow_#{idxA}"], @sprites["shadow_#{idxB}"] = @sprites["shadow_#{idxB}"], @sprites["shadow_#{idxA}"]
@lastCmd[idxA], @lastCmd[idxB] = @lastCmd[idxB], @lastCmd[idxA]
@lastMove[idxA], @lastMove[idxB] = @lastMove[idxB], @lastMove[idxA]
[idxA,idxB].each do |i|
@sprites["pokemon_#{i}"].index = i
@sprites["pokemon_#{i}"].pbSetPosition
@sprites["shadow_#{i}"].index = i
@sprites["shadow_#{i}"].pbSetPosition
@sprites["dataBox_#{i}"].battler = @battle.battlers[i]
end
pbRefresh
end
#=============================================================================
# Phases
#=============================================================================
def pbBeginCommandPhase
@sprites["messageWindow"].text = ""
end
def pbBeginAttackPhase
pbSelectBattler(-1)
pbShowWindow(MESSAGE_BOX)
end
def pbBeginEndOfRoundPhase
end
def pbEndBattle(_result)
@abortable = false
pbShowWindow(BLANK)
# Fade out all sprites
pbBGMFade(1.0)
pbFadeOutAndHide(@sprites)
pbDisposeSprites
end
#=============================================================================
#
#=============================================================================
def pbSelectBattler(idxBattler,selectMode=1)
numWindows = @battle.sideSizes.max*2
for i in 0...numWindows
sel = (idxBattler.is_a?(Array)) ? !idxBattler[i].nil? : i==idxBattler
selVal = (sel) ? selectMode : 0
@sprites["dataBox_#{i}"].selected = selVal if @sprites["dataBox_#{i}"]
@sprites["pokemon_#{i}"].selected = selVal if @sprites["pokemon_#{i}"]
end
end
def pbChangePokemon(idxBattler,pkmn)
idxBattler = idxBattler.index if idxBattler.respond_to?("index")
pkmnSprite = @sprites["pokemon_#{idxBattler}"]
shadowSprite = @sprites["shadow_#{idxBattler}"]
back = !@battle.opposes?(idxBattler)
pkmnSprite.setPokemonBitmap(pkmn,back)
shadowSprite.setPokemonBitmap(pkmn)
# Set visibility of battler's shadow
shadowSprite.visible = pkmn.species_data.shows_shadow? if shadowSprite && !back
end
def pbResetMoveIndex(idxBattler)
@lastMove[idxBattler] = 0
end
#=============================================================================
#
#=============================================================================
# This method is called when the player wins a wild Pokémon battle.
# This method can change the battle's music for example.
def pbWildBattleSuccess
@battleEnd = true
pbBGMPlay(pbGetWildVictoryME)
end
# This method is called when the player wins a trainer battle.
# This method can change the battle's music for example.
def pbTrainerBattleSuccess
@battleEnd = true
pbBGMPlay(pbGetTrainerVictoryME(@battle.opponent))
end
end

View File

@@ -0,0 +1,191 @@
class PokeBattle_Scene
#=============================================================================
# Create the battle scene and its elements
#=============================================================================
def initialize
@battle = nil
@abortable = false
@aborted = false
@battleEnd = false
@animations = []
@frameCounter = 0
end
# Called whenever the battle begins.
def pbStartBattle(battle)
@battle = battle
@viewport = Viewport.new(0,0,Graphics.width,Graphics.height)
@viewport.z = 99999
@lastCmd = Array.new(@battle.battlers.length,0)
@lastMove = Array.new(@battle.battlers.length,0)
pbInitSprites
pbBattleIntroAnimation
end
def pbInitSprites
@sprites = {}
# The background image and each side's base graphic
pbCreateBackdropSprites
# Create message box graphic
messageBox = pbAddSprite("messageBox",0,Graphics.height-96,
"Graphics/Pictures/Battle/overlay_message",@viewport)
messageBox.z = 195
# Create message window (displays the message)
msgWindow = Window_AdvancedTextPokemon.newWithSize("",
16,Graphics.height-96+2,Graphics.width-32,96,@viewport)
msgWindow.z = 200
msgWindow.opacity = 0
msgWindow.baseColor = PokeBattle_SceneConstants::MESSAGE_BASE_COLOR
msgWindow.shadowColor = PokeBattle_SceneConstants::MESSAGE_SHADOW_COLOR
msgWindow.letterbyletter = true
@sprites["messageWindow"] = msgWindow
# Create command window
@sprites["commandWindow"] = CommandMenuDisplay.new(@viewport,200)
# Create fight window
@sprites["fightWindow"] = FightMenuDisplay.new(@viewport,200)
# Create targeting window
@sprites["targetWindow"] = TargetMenuDisplay.new(@viewport,200,@battle.sideSizes)
pbShowWindow(MESSAGE_BOX)
# The party lineup graphics (bar and balls) for both sides
for side in 0...2
partyBar = pbAddSprite("partyBar_#{side}",0,0,
"Graphics/Pictures/Battle/overlay_lineup",@viewport)
partyBar.z = 120
partyBar.mirror = true if side==0 # Player's lineup bar only
partyBar.visible = false
for i in 0...PokeBattle_SceneConstants::NUM_BALLS
ball = pbAddSprite("partyBall_#{side}_#{i}",0,0,nil,@viewport)
ball.z = 121
ball.visible = false
end
# Ability splash bars
if PokeBattle_SceneConstants::USE_ABILITY_SPLASH
@sprites["abilityBar_#{side}"] = AbilitySplashBar.new(side,@viewport)
end
end
# Player's and partner trainer's back sprite
@battle.player.each_with_index do |p,i|
pbCreateTrainerBackSprite(i,p.trainer_type,@battle.player.length)
end
# Opposing trainer(s) sprites
if @battle.trainerBattle?
@battle.opponent.each_with_index do |p,i|
pbCreateTrainerFrontSprite(i,p.trainer_type,@battle.opponent.length)
end
end
# Data boxes and Pokémon sprites
@battle.battlers.each_with_index do |b,i|
next if !b
@sprites["dataBox_#{i}"] = PokemonDataBox.new(b,@battle.pbSideSize(i),@viewport)
pbCreatePokemonSprite(i)
end
# Wild battle, so set up the Pokémon sprite(s) accordingly
if @battle.wildBattle?
@battle.pbParty(1).each_with_index do |pkmn,i|
index = i*2+1
pbChangePokemon(index,pkmn)
pkmnSprite = @sprites["pokemon_#{index}"]
pkmnSprite.tone = Tone.new(-80,-80,-80)
pkmnSprite.visible = true
end
end
end
def pbCreateBackdropSprites
case @battle.time
when 1 then time = "eve"
when 2 then time = "night"
end
# Put everything together into backdrop, bases and message bar filenames
backdropFilename = @battle.backdrop
baseFilename = @battle.backdrop
baseFilename = sprintf("%s_%s",baseFilename,@battle.backdropBase) if @battle.backdropBase
messageFilename = @battle.backdrop
if time
trialName = sprintf("%s_%s",backdropFilename,time)
if pbResolveBitmap(sprintf("Graphics/Battlebacks/"+trialName+"_bg"))
backdropFilename = trialName
end
trialName = sprintf("%s_%s",baseFilename,time)
if pbResolveBitmap(sprintf("Graphics/Battlebacks/"+trialName+"_base0"))
baseFilename = trialName
end
trialName = sprintf("%s_%s",messageFilename,time)
if pbResolveBitmap(sprintf("Graphics/Battlebacks/"+trialName+"_message"))
messageFilename = trialName
end
end
if !pbResolveBitmap(sprintf("Graphics/Battlebacks/"+baseFilename+"_base0")) &&
@battle.backdropBase
baseFilename = @battle.backdropBase
if time
trialName = sprintf("%s_%s",baseFilename,time)
if pbResolveBitmap(sprintf("Graphics/Battlebacks/"+trialName+"_base0"))
baseFilename = trialName
end
end
end
# Finalise filenames
battleBG = "Graphics/Battlebacks/"+backdropFilename+"_bg"
playerBase = "Graphics/Battlebacks/"+baseFilename+"_base0"
enemyBase = "Graphics/Battlebacks/"+baseFilename+"_base1"
messageBG = "Graphics/Battlebacks/"+messageFilename+"_message"
# Apply graphics
bg = pbAddSprite("battle_bg",0,0,battleBG,@viewport)
bg.z = 0
bg = pbAddSprite("battle_bg2",-Graphics.width,0,battleBG,@viewport)
bg.z = 0
bg.mirror = true
for side in 0...2
baseX, baseY = PokeBattle_SceneConstants.pbBattlerPosition(side)
base = pbAddSprite("base_#{side}",baseX,baseY,
(side==0) ? playerBase : enemyBase,@viewport)
base.z = 1
if base.bitmap
base.ox = base.bitmap.width/2
base.oy = (side==0) ? base.bitmap.height : base.bitmap.height/2
end
end
cmdBarBG = pbAddSprite("cmdBar_bg",0,Graphics.height-96,messageBG,@viewport)
cmdBarBG.z = 180
end
def pbCreateTrainerBackSprite(idxTrainer,trainerType,numTrainers=1)
if idxTrainer==0 # Player's sprite
trainerFile = GameData::TrainerType.player_back_sprite_filename(trainerType)
else # Partner trainer's sprite
trainerFile = GameData::TrainerType.back_sprite_filename(trainerType)
end
spriteX, spriteY = PokeBattle_SceneConstants.pbTrainerPosition(0,idxTrainer,numTrainers)
trainer = pbAddSprite("player_#{idxTrainer+1}",spriteX,spriteY,trainerFile,@viewport)
return if !trainer.bitmap
# Alter position of sprite
trainer.z = 30+idxTrainer
if trainer.bitmap.width>trainer.bitmap.height*2
trainer.src_rect.x = 0
trainer.src_rect.width = trainer.bitmap.width/5
end
trainer.ox = trainer.src_rect.width/2
trainer.oy = trainer.bitmap.height
end
def pbCreateTrainerFrontSprite(idxTrainer,trainerType,numTrainers=1)
trainerFile = GameData::TrainerType.front_sprite_filename(trainerType)
spriteX, spriteY = PokeBattle_SceneConstants.pbTrainerPosition(1,idxTrainer,numTrainers)
trainer = pbAddSprite("trainer_#{idxTrainer+1}",spriteX,spriteY,trainerFile,@viewport)
return if !trainer.bitmap
# Alter position of sprite
trainer.z = 7+idxTrainer
trainer.ox = trainer.src_rect.width/2
trainer.oy = trainer.bitmap.height
end
def pbCreatePokemonSprite(idxBattler)
sideSize = @battle.pbSideSize(idxBattler)
batSprite = PokemonBattlerSprite.new(@viewport,sideSize,idxBattler,@animations)
@sprites["pokemon_#{idxBattler}"] = batSprite
shaSprite = PokemonBattlerShadowSprite.new(@viewport,sideSize,idxBattler)
shaSprite.visible = false
@sprites["shadow_#{idxBattler}"] = shaSprite
end
end

View File

@@ -0,0 +1,470 @@
class PokeBattle_Scene
#=============================================================================
# The player chooses a main command for a Pokémon
# Return values: -1=Cancel, 0=Fight, 1=Bag, 2=Pokémon, 3=Run, 4=Call
#=============================================================================
def pbCommandMenu(idxBattler,firstAction)
shadowTrainer = (GameData::Type.exists?(:SHADOW) && @battle.trainerBattle?)
cmds = [
_INTL("What will\n{1} do?",@battle.battlers[idxBattler].name),
_INTL("Fight"),
_INTL("Bag"),
_INTL("Pokémon"),
(shadowTrainer) ? _INTL("Call") : (firstAction) ? _INTL("Run") : _INTL("Cancel")
]
ret = pbCommandMenuEx(idxBattler,cmds,(shadowTrainer) ? 2 : (firstAction) ? 0 : 1)
ret = 4 if ret==3 && shadowTrainer # Convert "Run" to "Call"
ret = -1 if ret==3 && !firstAction # Convert "Run" to "Cancel"
return ret
end
# Mode: 0 = regular battle with "Run" (first choosable action in the round only)
# 1 = regular battle with "Cancel"
# 2 = regular battle with "Call" (for Shadow Pokémon battles)
# 3 = Safari Zone
# 4 = Bug Catching Contest
def pbCommandMenuEx(idxBattler,texts,mode=0)
pbShowWindow(COMMAND_BOX)
cw = @sprites["commandWindow"]
cw.setTexts(texts)
cw.setIndexAndMode(@lastCmd[idxBattler],mode)
pbSelectBattler(idxBattler)
ret = -1
loop do
oldIndex = cw.index
pbUpdate(cw)
# Update selected command
if Input.trigger?(Input::LEFT)
cw.index -= 1 if (cw.index&1)==1
elsif Input.trigger?(Input::RIGHT)
cw.index += 1 if (cw.index&1)==0
elsif Input.trigger?(Input::UP)
cw.index -= 2 if (cw.index&2)==2
elsif Input.trigger?(Input::DOWN)
cw.index += 2 if (cw.index&2)==0
end
pbPlayCursorSE if cw.index!=oldIndex
# Actions
if Input.trigger?(Input::USE) # Confirm choice
pbPlayDecisionSE
ret = cw.index
@lastCmd[idxBattler] = ret
break
elsif Input.trigger?(Input::BACK) && mode==1 # Cancel
pbPlayCancelSE
break
elsif Input.trigger?(Input::F9) && $DEBUG # Debug menu
pbPlayDecisionSE
ret = -2
break
end
end
return ret
end
#=============================================================================
# The player chooses a move for a Pokémon to use
#=============================================================================
def pbFightMenu(idxBattler,megaEvoPossible=false)
battler = @battle.battlers[idxBattler]
cw = @sprites["fightWindow"]
cw.battler = battler
moveIndex = 0
if battler.moves[@lastMove[idxBattler]] && battler.moves[@lastMove[idxBattler]].id
moveIndex = @lastMove[idxBattler]
end
cw.shiftMode = (@battle.pbCanShift?(idxBattler)) ? 1 : 0
cw.setIndexAndMode(moveIndex,(megaEvoPossible) ? 1 : 0)
needFullRefresh = true
needRefresh = false
loop do
# Refresh view if necessary
if needFullRefresh
pbShowWindow(FIGHT_BOX)
pbSelectBattler(idxBattler)
needFullRefresh = false
end
if needRefresh
if megaEvoPossible
newMode = (@battle.pbRegisteredMegaEvolution?(idxBattler)) ? 2 : 1
cw.mode = newMode if newMode!=cw.mode
end
needRefresh = false
end
oldIndex = cw.index
# General update
pbUpdate(cw)
# Update selected command
if Input.trigger?(Input::LEFT)
cw.index -= 1 if (cw.index&1)==1
elsif Input.trigger?(Input::RIGHT)
if battler.moves[cw.index+1] && battler.moves[cw.index+1].id
cw.index += 1 if (cw.index&1)==0
end
elsif Input.trigger?(Input::UP)
cw.index -= 2 if (cw.index&2)==2
elsif Input.trigger?(Input::DOWN)
if battler.moves[cw.index+2] && battler.moves[cw.index+2].id
cw.index += 2 if (cw.index&2)==0
end
end
pbPlayCursorSE if cw.index!=oldIndex
# Actions
if Input.trigger?(Input::USE) # Confirm choice
pbPlayDecisionSE
break if yield cw.index
needFullRefresh = true
needRefresh = true
elsif Input.trigger?(Input::BACK) # Cancel fight menu
pbPlayCancelSE
break if yield -1
needRefresh = true
elsif Input.trigger?(Input::ACTION) # Toggle Mega Evolution
if megaEvoPossible
pbPlayDecisionSE
break if yield -2
needRefresh = true
end
elsif Input.trigger?(Input::SPECIAL) # Shift
if cw.shiftMode>0
pbPlayDecisionSE
break if yield -3
needRefresh = true
end
end
end
@lastMove[idxBattler] = cw.index
end
#=============================================================================
# Opens the party screen to choose a Pokémon to switch in (or just view its
# summary screens)
#=============================================================================
def pbPartyScreen(idxBattler,canCancel=false)
# Fade out and hide all sprites
visibleSprites = pbFadeOutAndHide(@sprites)
# Get player's party
partyPos = @battle.pbPartyOrder(idxBattler)
partyStart, _partyEnd = @battle.pbTeamIndexRangeFromBattlerIndex(idxBattler)
modParty = @battle.pbPlayerDisplayParty(idxBattler)
# Start party screen
scene = PokemonParty_Scene.new
switchScreen = PokemonPartyScreen.new(scene,modParty)
switchScreen.pbStartScene(_INTL("Choose a Pokémon."),@battle.pbNumPositions(0,0))
# Loop while in party screen
loop do
# Select a Pokémon
scene.pbSetHelpText(_INTL("Choose a Pokémon."))
idxParty = switchScreen.pbChoosePokemon
if idxParty<0
next if !canCancel
break
end
# Choose a command for the selected Pokémon
cmdSwitch = -1
cmdSummary = -1
commands = []
commands[cmdSwitch = commands.length] = _INTL("Switch In") if modParty[idxParty].able?
commands[cmdSummary = commands.length] = _INTL("Summary")
commands[commands.length] = _INTL("Cancel")
command = scene.pbShowCommands(_INTL("Do what with {1}?",modParty[idxParty].name),commands)
if cmdSwitch>=0 && command==cmdSwitch # Switch In
idxPartyRet = -1
partyPos.each_with_index do |pos,i|
next if pos!=idxParty+partyStart
idxPartyRet = i
break
end
break if yield idxPartyRet, switchScreen
elsif cmdSummary>=0 && command==cmdSummary # Summary
scene.pbSummary(idxParty,true)
end
end
# Close party screen
switchScreen.pbEndScene
# Fade back into battle screen
pbFadeInAndShow(@sprites,visibleSprites)
end
#=============================================================================
# Opens the Bag screen and chooses an item to use
#=============================================================================
def pbItemMenu(idxBattler,_firstAction)
# Fade out and hide all sprites
visibleSprites = pbFadeOutAndHide(@sprites)
# Set Bag starting positions
oldLastPocket = $PokemonBag.lastpocket
oldChoices = $PokemonBag.getAllChoices
$PokemonBag.lastpocket = @bagLastPocket if @bagLastPocket!=nil
$PokemonBag.setAllChoices(@bagChoices) if @bagChoices!=nil
# Start Bag screen
itemScene = PokemonBag_Scene.new
itemScene.pbStartScene($PokemonBag,true,Proc.new { |item|
useType = GameData::Item.get(item).battle_use
next useType && useType>0
},false)
# Loop while in Bag screen
wasTargeting = false
loop do
# Select an item
item = itemScene.pbChooseItem
break if !item
# Choose a command for the selected item
item = GameData::Item.get(item)
itemName = item.name
useType = item.battle_use
cmdUse = -1
commands = []
commands[cmdUse = commands.length] = _INTL("Use") if useType && useType!=0
commands[commands.length] = _INTL("Cancel")
command = itemScene.pbShowCommands(_INTL("{1} is selected.",itemName),commands)
next unless cmdUse>=0 && command==cmdUse # Use
# Use types:
# 0 = not usable in battle
# 1 = use on Pokémon (lots of items), consumed
# 2 = use on Pokémon's move (Ethers), consumed
# 3 = use on battler (X items, Persim Berry), consumed
# 4 = use on opposing battler (Poké Balls), consumed
# 5 = use no target (Poké Doll, Guard Spec., Launcher items), consumed
# 6 = use on Pokémon (Blue Flute), not consumed
# 7 = use on Pokémon's move, not consumed
# 8 = use on battler (Red/Yellow Flutes), not consumed
# 9 = use on opposing battler, not consumed
# 10 = use no target (Poké Flute), not consumed
case useType
when 1, 2, 3, 6, 7, 8 # Use on Pokémon/Pokémon's move/battler
# Auto-choose the Pokémon/battler whose action is being decided if they
# are the only available Pokémon/battler to use the item on
case useType
when 1, 6 # Use on Pokémon
if @battle.pbTeamLengthFromBattlerIndex(idxBattler)==1
break if yield item.id, useType, @battle.battlers[idxBattler].pokemonIndex, -1, itemScene
end
when 3, 8 # Use on battler
if @battle.pbPlayerBattlerCount==1
break if yield item.id, useType, @battle.battlers[idxBattler].pokemonIndex, -1, itemScene
end
end
# Fade out and hide Bag screen
itemScene.pbFadeOutScene
# Get player's party
party = @battle.pbParty(idxBattler)
partyPos = @battle.pbPartyOrder(idxBattler)
partyStart, _partyEnd = @battle.pbTeamIndexRangeFromBattlerIndex(idxBattler)
modParty = @battle.pbPlayerDisplayParty(idxBattler)
# Start party screen
pkmnScene = PokemonParty_Scene.new
pkmnScreen = PokemonPartyScreen.new(pkmnScene,modParty)
pkmnScreen.pbStartScene(_INTL("Use on which Pokémon?"),@battle.pbNumPositions(0,0))
idxParty = -1
# Loop while in party screen
loop do
# Select a Pokémon
pkmnScene.pbSetHelpText(_INTL("Use on which Pokémon?"))
idxParty = pkmnScreen.pbChoosePokemon
break if idxParty<0
idxPartyRet = -1
partyPos.each_with_index do |pos,i|
next if pos!=idxParty+partyStart
idxPartyRet = i
break
end
next if idxPartyRet<0
pkmn = party[idxPartyRet]
next if !pkmn || pkmn.egg?
idxMove = -1
if useType==2 || useType==7 # Use on Pokémon's move
idxMove = pkmnScreen.pbChooseMove(pkmn,_INTL("Restore which move?"))
next if idxMove<0
end
break if yield item.id, useType, idxPartyRet, idxMove, pkmnScene
end
pkmnScene.pbEndScene
break if idxParty>=0
# Cancelled choosing a Pokémon; show the Bag screen again
itemScene.pbFadeInScene
when 4, 9 # Use on opposing battler (Poké Balls)
idxTarget = -1
if @battle.pbOpposingBattlerCount(idxBattler)==1
@battle.eachOtherSideBattler(idxBattler) { |b| idxTarget = b.index }
break if yield item.id, useType, idxTarget, -1, itemScene
else
wasTargeting = true
# Fade out and hide Bag screen
itemScene.pbFadeOutScene
# Fade in and show the battle screen, choosing a target
tempVisibleSprites = visibleSprites.clone
tempVisibleSprites["commandWindow"] = false
tempVisibleSprites["targetWindow"] = true
idxTarget = pbChooseTarget(idxBattler,GameData::Target.get(:Foe),tempVisibleSprites)
if idxTarget>=0
break if yield item.id, useType, idxTarget, -1, self
end
# Target invalid/cancelled choosing a target; show the Bag screen again
wasTargeting = false
pbFadeOutAndHide(@sprites)
itemScene.pbFadeInScene
end
when 5, 10 # Use with no target
break if yield item.id, useType, idxBattler, -1, itemScene
end
end
@bagLastPocket = $PokemonBag.lastpocket
@bagChoices = $PokemonBag.getAllChoices
$PokemonBag.lastpocket = oldLastPocket
$PokemonBag.setAllChoices(oldChoices)
# Close Bag screen
itemScene.pbEndScene
# Fade back into battle screen (if not already showing it)
pbFadeInAndShow(@sprites,visibleSprites) if !wasTargeting
end
#=============================================================================
# The player chooses a target battler for a move/item (non-single battles only)
#=============================================================================
# Returns an array containing battler names to display when choosing a move's
# target.
# nil means can't select that position, "" means can select that position but
# there is no battler there, otherwise is a battler's name.
def pbCreateTargetTexts(idxBattler,target_data)
texts = Array.new(@battle.battlers.length) do |i|
next nil if !@battle.battlers[i]
showName = false
# NOTE: Targets listed here are ones with num_targets of 0, plus
# RandomNearFoe which should look like it targets the user. All
# other targets are handled by the "else" part.
case target_data.id
when :None, :User, :RandomNearFoe
showName = (i==idxBattler)
when :UserSide
showName = !@battle.opposes?(i,idxBattler)
when :FoeSide
showName = @battle.opposes?(i,idxBattler)
when :BothSides
showName = true
else
showName = @battle.pbMoveCanTarget?(i,idxBattler,target_data)
end
next nil if !showName
next (@battle.battlers[i].fainted?) ? "" : @battle.battlers[i].name
end
return texts
end
# Returns the initial position of the cursor when choosing a target for a move
# in a non-single battle.
def pbFirstTarget(idxBattler,target_data)
case target_data.id
when :NearAlly
@battle.eachSameSideBattler(idxBattler) do |b|
next if b.index==idxBattler || !@battle.nearBattlers?(b,idxBattler)
next if b.fainted?
return b.index
end
@battle.eachSameSideBattler(idxBattler) do |b|
next if b.index==idxBattler || !@battle.nearBattlers?(b,idxBattler)
return b.index
end
when :NearFoe, :NearOther
indices = @battle.pbGetOpposingIndicesInOrder(idxBattler)
indices.each { |i| return i if @battle.nearBattlers?(i,idxBattler) && !@battle.battlers[i].fainted? }
indices.each { |i| return i if @battle.nearBattlers?(i,idxBattler) }
when :Foe, :Other
indices = @battle.pbGetOpposingIndicesInOrder(idxBattler)
indices.each { |i| return i if !@battle.battlers[i].fainted? }
indices.each { |i| return i }
end
return idxBattler # Target the user initially
end
def pbChooseTarget(idxBattler,target_data,visibleSprites=nil)
pbShowWindow(TARGET_BOX)
cw = @sprites["targetWindow"]
# Create an array of battler names (only valid targets are named)
texts = pbCreateTargetTexts(idxBattler,target_data)
# Determine mode based on target_data
mode = (target_data.num_targets == 1) ? 0 : 1
cw.setDetails(texts,mode)
cw.index = pbFirstTarget(idxBattler,target_data)
pbSelectBattler((mode==0) ? cw.index : texts,2) # Select initial battler/data box
pbFadeInAndShow(@sprites,visibleSprites) if visibleSprites
ret = -1
loop do
oldIndex = cw.index
pbUpdate(cw)
# Update selected command
if mode==0 # Choosing just one target, can change index
if Input.trigger?(Input::LEFT) || Input.trigger?(Input::RIGHT)
inc = ((cw.index%2)==0) ? -2 : 2
inc *= -1 if Input.trigger?(Input::RIGHT)
indexLength = @battle.sideSizes[cw.index%2]*2
newIndex = cw.index
loop do
newIndex += inc
break if newIndex<0 || newIndex>=indexLength
next if texts[newIndex].nil?
cw.index = newIndex
break
end
elsif (Input.trigger?(Input::UP) && (cw.index%2)==0) ||
(Input.trigger?(Input::DOWN) && (cw.index%2)==1)
tryIndex = @battle.pbGetOpposingIndicesInOrder(cw.index)
tryIndex.each do |idxBattlerTry|
next if texts[idxBattlerTry].nil?
cw.index = idxBattlerTry
break
end
end
if cw.index!=oldIndex
pbPlayCursorSE
pbSelectBattler(cw.index,2) # Select the new battler/data box
end
end
if Input.trigger?(Input::USE) # Confirm
ret = cw.index
pbPlayDecisionSE
break
elsif Input.trigger?(Input::BACK) # Cancel
ret = -1
pbPlayCancelSE
break
end
end
pbSelectBattler(-1) # Deselect all battlers/data boxes
return ret
end
#=============================================================================
# Opens a Pokémon's summary screen to try to learn a new move
#=============================================================================
# Called whenever a Pokémon should forget a move. It should return -1 if the
# selection is canceled, or 0 to 3 to indicate the move to forget. It should
# not allow HM moves to be forgotten.
def pbForgetMove(pkmn,moveToLearn)
ret = -1
pbFadeOutIn {
scene = PokemonSummary_Scene.new
screen = PokemonSummaryScreen.new(scene)
ret = screen.pbStartForgetScreen([pkmn],0,moveToLearn)
}
return ret
end
#=============================================================================
# Opens the nicknaming screen for a newly caught Pokémon
#=============================================================================
def pbNameEntry(helpText,pkmn)
return pbEnterPokemonName(helpText, 0, Pokemon::MAX_NAME_SIZE, "", pkmn)
end
#=============================================================================
# Shows the Pokédex entry screen for a newly caught Pokémon
#=============================================================================
def pbShowPokedex(species)
pbFadeOutIn {
scene = PokemonPokedexInfo_Scene.new
screen = PokemonPokedexInfoScreen.new(scene)
screen.pbDexEntry(species)
}
end
end

View File

@@ -0,0 +1,539 @@
class PokeBattle_Scene
#=============================================================================
# Animates the battle intro
#=============================================================================
def pbBattleIntroAnimation
# Make everything appear
introAnim = BattleIntroAnimation.new(@sprites,@viewport,@battle)
loop do
introAnim.update
pbUpdate
break if introAnim.animDone?
end
introAnim.dispose
# Post-appearance activities
# Trainer battle: get ready to show the party lineups (they are brought
# on-screen by a separate animation)
if @battle.trainerBattle?
# NOTE: Here is where you'd make trainer sprites animate if they had an
# entrance animation. Be sure to set it up like a Pokémon entrance
# animation, i.e. add them to @animations so that they can play out
# while party lineups appear and messages show.
pbShowPartyLineup(0,true)
pbShowPartyLineup(1,true)
return
end
# Wild battle: play wild Pokémon's intro animations (including cry), show
# data box(es), return the wild Pokémon's sprite(s) to normal colour, show
# shiny animation(s)
# Set up data box animation
for i in 0...@battle.sideSizes[1]
idxBattler = 2*i+1
next if !@battle.battlers[idxBattler]
dataBoxAnim = DataBoxAppearAnimation.new(@sprites,@viewport,idxBattler)
@animations.push(dataBoxAnim)
end
# Set up wild Pokémon returning to normal colour and playing intro
# animations (including cry)
@animations.push(BattleIntroAnimation2.new(@sprites,@viewport,@battle.sideSizes[1]))
# Play all the animations
while inPartyAnimation?; pbUpdate; end
# Show shiny animation for wild Pokémon
if @battle.showAnims
for i in 0...@battle.sideSizes[1]
idxBattler = 2*i+1
next if !@battle.battlers[idxBattler] || !@battle.battlers[idxBattler].shiny?
pbCommonAnimation("Shiny",@battle.battlers[idxBattler])
end
end
end
#=============================================================================
# Animates a party lineup appearing for the given side
#=============================================================================
def pbShowPartyLineup(side,fullAnim=false)
@animations.push(LineupAppearAnimation.new(@sprites,@viewport,
side,@battle.pbParty(side),@battle.pbPartyStarts(side),fullAnim))
if !fullAnim
while inPartyAnimation?; pbUpdate; end
end
end
#=============================================================================
# Animates an opposing trainer sliding in from off-screen. Will animate a
# previous trainer that is already on-screen slide off first. Used at the end
# of battle.
#=============================================================================
def pbShowOpponent(idxTrainer)
# Set up trainer appearing animation
appearAnim = TrainerAppearAnimation.new(@sprites,@viewport,idxTrainer)
@animations.push(appearAnim)
# Play the animation
while inPartyAnimation?; pbUpdate; end
end
#=============================================================================
# Animates a trainer's sprite and party lineup hiding (if they are visible).
# Animates a Pokémon being sent out into battle, then plays the shiny
# animation for it if relevant.
# sendOuts is an array; each element is itself an array: [idxBattler,pkmn]
#=============================================================================
def pbSendOutBattlers(sendOuts,startBattle=false)
return if sendOuts.length==0
# If party balls are still appearing, wait for them to finish showing up, as
# the FadeAnimation will make them disappear.
while inPartyAnimation?; pbUpdate; end
@briefMessage = false
# Make all trainers and party lineups disappear (player-side trainers may
# animate throwing a Poké Ball)
if @battle.opposes?(sendOuts[0][0])
fadeAnim = TrainerFadeAnimation.new(@sprites,@viewport,startBattle)
else
fadeAnim = PlayerFadeAnimation.new(@sprites,@viewport,startBattle)
end
# For each battler being sent out, set the battler's sprite and create two
# animations (the Poké Ball moving and battler appearing from it, and its
# data box appearing)
sendOutAnims = []
sendOuts.each_with_index do |b,i|
pkmn = @battle.battlers[b[0]].effects[PBEffects::Illusion] || b[1]
pbChangePokemon(b[0],pkmn)
pbRefresh
if @battle.opposes?(b[0])
sendOutAnim = PokeballTrainerSendOutAnimation.new(@sprites,@viewport,
@battle.pbGetOwnerIndexFromBattlerIndex(b[0])+1,
@battle.battlers[b[0]],startBattle,i)
else
sendOutAnim = PokeballPlayerSendOutAnimation.new(@sprites,@viewport,
@battle.pbGetOwnerIndexFromBattlerIndex(b[0])+1,
@battle.battlers[b[0]],startBattle,i)
end
dataBoxAnim = DataBoxAppearAnimation.new(@sprites,@viewport,b[0])
sendOutAnims.push([sendOutAnim,dataBoxAnim,false])
end
# Play all animations
loop do
fadeAnim.update
sendOutAnims.each do |a|
next if a[2]
a[0].update
a[1].update if a[0].animDone?
a[2] = true if a[1].animDone?
end
pbUpdate
if !inPartyAnimation?
break if !sendOutAnims.any? { |a| !a[2] }
end
end
fadeAnim.dispose
sendOutAnims.each { |a| a[0].dispose; a[1].dispose }
# Play shininess animations for shiny Pokémon
sendOuts.each do |b|
next if !@battle.showAnims || !@battle.battlers[b[0]].shiny?
pbCommonAnimation("Shiny",@battle.battlers[b[0]])
end
end
#=============================================================================
# Animates a Pokémon being recalled into its Poké Ball and its data box hiding
#=============================================================================
def pbRecall(idxBattler)
@briefMessage = false
# Recall animation
recallAnim = BattlerRecallAnimation.new(@sprites,@viewport,idxBattler)
loop do
recallAnim.update if recallAnim
pbUpdate
break if recallAnim.animDone?
end
recallAnim.dispose
# Data box disappear animation
dataBoxAnim = DataBoxDisappearAnimation.new(@sprites,@viewport,idxBattler)
loop do
dataBoxAnim.update
pbUpdate
break if dataBoxAnim.animDone?
end
dataBoxAnim.dispose
end
#=============================================================================
# Ability splash bar animations
#=============================================================================
def pbShowAbilitySplash(battler)
return if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH
side = battler.index%2
pbHideAbilitySplash(battler) if @sprites["abilityBar_#{side}"].visible
@sprites["abilityBar_#{side}"].battler = battler
abilitySplashAnim = AbilitySplashAppearAnimation.new(@sprites,@viewport,side)
loop do
abilitySplashAnim.update
pbUpdate
break if abilitySplashAnim.animDone?
end
abilitySplashAnim.dispose
end
def pbHideAbilitySplash(battler)
return if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH
side = battler.index%2
return if !@sprites["abilityBar_#{side}"].visible
abilitySplashAnim = AbilitySplashDisappearAnimation.new(@sprites,@viewport,side)
loop do
abilitySplashAnim.update
pbUpdate
break if abilitySplashAnim.animDone?
end
abilitySplashAnim.dispose
end
def pbReplaceAbilitySplash(battler)
return if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH
pbShowAbilitySplash(battler)
end
#=============================================================================
# HP change animations
#=============================================================================
# Shows a HP-changing common animation and animates a data box's HP bar.
# Called by def pbReduceHP, def pbRecoverHP.
def pbHPChanged(battler,oldHP,showAnim=false)
@briefMessage = false
if battler.hp>oldHP
pbCommonAnimation("HealthUp",battler) if showAnim && @battle.showAnims
elsif battler.hp<oldHP
pbCommonAnimation("HealthDown",battler) if showAnim && @battle.showAnims
end
@sprites["dataBox_#{battler.index}"].animateHP(oldHP,battler.hp,battler.totalhp)
while @sprites["dataBox_#{battler.index}"].animatingHP
pbUpdate
end
end
def pbDamageAnimation(battler,effectiveness=0)
@briefMessage = false
# Damage animation
damageAnim = BattlerDamageAnimation.new(@sprites,@viewport,battler.index,effectiveness)
loop do
damageAnim.update
pbUpdate
break if damageAnim.animDone?
end
damageAnim.dispose
end
# Animates battlers flashing and data boxes' HP bars because of damage taken
# by an attack. targets is an array, which are all animated simultaneously.
# Each element in targets is also an array: [battler, old HP, effectiveness]
def pbHitAndHPLossAnimation(targets)
@briefMessage = false
# Set up animations
damageAnims = []
targets.each do |t|
anim = BattlerDamageAnimation.new(@sprites,@viewport,t[0].index,t[2])
damageAnims.push(anim)
@sprites["dataBox_#{t[0].index}"].animateHP(t[1],t[0].hp,t[0].totalhp)
end
# Update loop
loop do
damageAnims.each { |a| a.update }
pbUpdate
allDone = true
targets.each do |t|
next if !@sprites["dataBox_#{t[0].index}"].animatingHP
allDone = false
break
end
next if !allDone
damageAnims.each do |a|
next if a.animDone?
allDone = false
break
end
next if !allDone
break
end
damageAnims.each { |a| a.dispose }
end
#=============================================================================
# Animates a data box's Exp bar
#=============================================================================
def pbEXPBar(battler,startExp,endExp,tempExp1,tempExp2)
return if !battler
startExpLevel = tempExp1-startExp
endExpLevel = tempExp2-startExp
expRange = endExp-startExp
dataBox = @sprites["dataBox_#{battler.index}"]
dataBox.animateExp(startExpLevel,endExpLevel,expRange)
while dataBox.animatingExp; pbUpdate; end
end
#=============================================================================
# Shows stats windows upon a Pokémon levelling up
#=============================================================================
def pbLevelUp(pkmn,_battler,oldTotalHP,oldAttack,oldDefense,oldSpAtk,oldSpDef,oldSpeed)
pbTopRightWindow(
_INTL("Max. HP<r>+{1}\r\nAttack<r>+{2}\r\nDefense<r>+{3}\r\nSp. Atk<r>+{4}\r\nSp. Def<r>+{5}\r\nSpeed<r>+{6}",
pkmn.totalhp-oldTotalHP,pkmn.attack-oldAttack,pkmn.defense-oldDefense,
pkmn.spatk-oldSpAtk,pkmn.spdef-oldSpDef,pkmn.speed-oldSpeed))
pbTopRightWindow(
_INTL("Max. HP<r>{1}\r\nAttack<r>{2}\r\nDefense<r>{3}\r\nSp. Atk<r>{4}\r\nSp. Def<r>{5}\r\nSpeed<r>{6}",
pkmn.totalhp,pkmn.attack,pkmn.defense,pkmn.spatk,pkmn.spdef,pkmn.speed))
end
#=============================================================================
# Animates a Pokémon fainting
#=============================================================================
def pbFaintBattler(battler)
@briefMessage = false
# Pokémon plays cry and drops down, data box disappears
faintAnim = BattlerFaintAnimation.new(@sprites,@viewport,battler.index,@battle)
dataBoxAnim = DataBoxDisappearAnimation.new(@sprites,@viewport,battler.index)
loop do
faintAnim.update
dataBoxAnim.update
pbUpdate
break if faintAnim.animDone? && dataBoxAnim.animDone?
end
faintAnim.dispose
dataBoxAnim.dispose
end
#=============================================================================
# Animates throwing a Poké Ball at a Pokémon in an attempt to catch it
#=============================================================================
def pbThrow(ball,shakes,critical,targetBattler,showPlayer=false)
@briefMessage = false
captureAnim = PokeballThrowCaptureAnimation.new(@sprites,@viewport,
ball,shakes,critical,@battle.battlers[targetBattler],showPlayer)
loop do
captureAnim.update
pbUpdate
break if captureAnim.animDone? && !inPartyAnimation?
end
captureAnim.dispose
end
def pbThrowSuccess
return if @battle.opponent
@briefMessage = false
pbMEPlay(pbGetWildCaptureME)
i = 0
loop do
pbUpdate
break if i>=Graphics.frame_rate*3.5 # 3.5 seconds
i += 1
end
pbMEStop
end
def pbHideCaptureBall(idxBattler)
# NOTE: It's not really worth writing a whole PokeBattle_Animation class for
# making the capture ball fade out.
ball = @sprites["captureBall"]
return if !ball
# Data box disappear animation
dataBoxAnim = DataBoxDisappearAnimation.new(@sprites,@viewport,idxBattler)
loop do
dataBoxAnim.update
ball.opacity -= 12*20/Graphics.frame_rate if ball.opacity>0
pbUpdate
break if dataBoxAnim.animDone? && ball.opacity<=0
end
dataBoxAnim.dispose
end
def pbThrowAndDeflect(ball,idxBattler)
@briefMessage = false
throwAnim = PokeballThrowDeflectAnimation.new(@sprites,@viewport,
ball,@battle.battlers[idxBattler])
loop do
throwAnim.update
pbUpdate
break if throwAnim.animDone?
end
throwAnim.dispose
end
#=============================================================================
# Hides all battler shadows before yielding to a move animation, and then
# restores the shadows afterwards
#=============================================================================
def pbSaveShadows
# Remember which shadows were visible
shadows = Array.new(@battle.battlers.length) do |i|
shadow = @sprites["shadow_#{i}"]
ret = (shadow) ? shadow.visible : false
shadow.visible = false if shadow
next ret
end
# Yield to other code, i.e. playing an animation
yield
# Restore shadow visibility
for i in 0...@battle.battlers.length
shadow = @sprites["shadow_#{i}"]
shadow.visible = shadows[i] if shadow
end
end
#=============================================================================
# Loads a move/common animation
#=============================================================================
# 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)
id_number = GameData::Move.get(moveID).id_number
noFlip = false
if (idxUser&1)==0 # On player's side
anim = move2anim[0][id_number]
else # On opposing side
anim = move2anim[1][id_number]
noFlip = true if anim
anim = move2anim[0][id_number] if !anim
end
return [anim+hitNum,noFlip] if anim
return nil
end
# Returns the animation ID to use for a given move. If the move has no
# animations, tries to use a default move animation depending on the move's
# type. If that default move animation doesn't exist, trues to use Tackle's
# move animation. Returns nil if it can't find any of these animations to use.
def pbFindMoveAnimation(moveID, idxUser, hitNum)
begin
move2anim = pbLoadMoveToAnim
# Find actual animation requested (an opponent using the animation first
# looks for an OppMove version then a Move version)
anim = pbFindMoveAnimDetails(move2anim, moveID, idxUser, hitNum)
return anim if anim
# Actual animation not found, get the default animation for the move's type
moveData = GameData::Move.get(moveID)
target_data = GameData::Target.get(moveData.target)
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
# [one target physical, one target special, user status,
# multiple targets physical, multiple targets special, non-user status]
typeDefaultAnim = {
:NORMAL => [:TACKLE, :SONICBOOM, :DEFENSECURL, :EXPLOSION, :SWIFT, :TAILWHIP],
:FIGHTING => [:MACHPUNCH, :AURASPHERE, :DETECT, nil, nil, nil],
:FLYING => [:WINGATTACK, :GUST, :ROOST, nil, :AIRCUTTER, :FEATHERDANCE],
:POISON => [:POISONSTING, :SLUDGE, :ACIDARMOR, nil, :ACID, :POISONPOWDER],
:GROUND => [:SANDTOMB, :MUDSLAP, nil, :EARTHQUAKE, :EARTHPOWER, :MUDSPORT],
:ROCK => [:ROCKTHROW, :POWERGEM, :ROCKPOLISH, :ROCKSLIDE, nil, :SANDSTORM],
:BUG => [:TWINEEDLE, :BUGBUZZ, :QUIVERDANCE, nil, :STRUGGLEBUG, :STRINGSHOT],
:GHOST => [:LICK, :SHADOWBALL, :GRUDGE, nil, nil, :CONFUSERAY],
:STEEL => [:IRONHEAD, :MIRRORSHOT, :IRONDEFENSE, nil, nil, :METALSOUND],
:FIRE => [:FIREPUNCH, :EMBER, :SUNNYDAY, nil, :INCINERATE, :WILLOWISP],
:WATER => [:CRABHAMMER, :WATERGUN, :AQUARING, nil, :SURF, :WATERSPORT],
:GRASS => [:VINEWHIP, :MEGADRAIN, :COTTONGUARD, :RAZORLEAF, nil, :SPORE],
:ELECTRIC => [:THUNDERPUNCH, :THUNDERSHOCK, :CHARGE, nil, :DISCHARGE, :THUNDERWAVE],
:PSYCHIC => [:ZENHEADBUTT, :CONFUSION, :CALMMIND, nil, :SYNCHRONOISE, :MIRACLEEYE],
:ICE => [:ICEPUNCH, :ICEBEAM, :MIST, nil, :POWDERSNOW, :HAIL],
:DRAGON => [:DRAGONCLAW, :DRAGONRAGE, :DRAGONDANCE, nil, :TWISTER, nil],
:DARK => [:PURSUIT, :DARKPULSE, :HONECLAWS, nil, :SNARL, :EMBARGO],
:FAIRY => [:TACKLE, :FAIRYWIND, :MOONLIGHT, nil, :SWIFT, :SWEETKISS]
}
if typeDefaultAnim[moveType]
anims = typeDefaultAnim[moveType]
if GameData::Move.exists?(anims[moveKind])
anim = pbFindMoveAnimDetails(move2anim, anims[moveKind], idxUser)
end
if !anim && moveKind >= 3 && GameData::Move.exists?(anims[moveKind - 3])
anim = pbFindMoveAnimDetails(move2anim, anims[moveKind - 3], idxUser)
end
if !anim && GameData::Move.exists?(anims[2])
anim = pbFindMoveAnimDetails(move2anim, anims[2], idxUser)
end
end
return anim if anim
# Default animation for the move's type not found, use Tackle's animation
if GameData::Move.exists?(:TACKLE)
return pbFindMoveAnimDetails(move2anim, :TACKLE, idxUser)
end
rescue
end
return nil
end
#=============================================================================
# Plays a move/common animation
#=============================================================================
# Plays a move animation.
def pbAnimation(moveID,user,targets,hitNum=0)
animID = pbFindMoveAnimation(moveID,user.index,hitNum)
return if !animID
anim = animID[0]
target = (targets && targets.is_a?(Array)) ? targets[0] : targets
animations = pbLoadBattleAnimations
return if !animations
pbSaveShadows {
if animID[1] # On opposing side and using OppMove animation
pbAnimationCore(animations[anim],target,user,true)
else # On player's side, and/or using Move animation
pbAnimationCore(animations[anim],user,target)
end
}
end
# Plays a common animation.
def pbCommonAnimation(animName,user=nil,target=nil)
return if !animName || animName==""
target = target[0] if target && target.is_a?(Array)
animations = pbLoadBattleAnimations
return if !animations
animations.each do |a|
next if !a || a.name!="Common:"+animName
pbAnimationCore(a,user,(target!=nil) ? target : user)
return
end
end
def pbAnimationCore(animation,user,target,oppMove=false)
return if !animation
@briefMessage = false
userSprite = (user) ? @sprites["pokemon_#{user.index}"] : nil
targetSprite = (target) ? @sprites["pokemon_#{target.index}"] : nil
# Remember the original positions of Pokémon sprites
oldUserX = (userSprite) ? userSprite.x : 0
oldUserY = (userSprite) ? userSprite.y : 0
oldTargetX = (targetSprite) ? targetSprite.x : oldUserX
oldTargetY = (targetSprite) ? targetSprite.y : oldUserY
# Create the animation player
animPlayer = PBAnimationPlayerX.new(animation,user,target,self,oppMove)
# Apply a transformation to the animation based on where the user and target
# actually are. Get the centres of each sprite.
userHeight = (userSprite && userSprite.bitmap && !userSprite.bitmap.disposed?) ? userSprite.bitmap.height : 128
if targetSprite
targetHeight = (targetSprite.bitmap && !targetSprite.bitmap.disposed?) ? targetSprite.bitmap.height : 128
else
targetHeight = userHeight
end
animPlayer.setLineTransform(
PokeBattle_SceneConstants::FOCUSUSER_X,PokeBattle_SceneConstants::FOCUSUSER_Y,
PokeBattle_SceneConstants::FOCUSTARGET_X,PokeBattle_SceneConstants::FOCUSTARGET_Y,
oldUserX,oldUserY-userHeight/2,
oldTargetX,oldTargetY-targetHeight/2)
# Play the animation
animPlayer.start
loop do
animPlayer.update
pbUpdate
break if animPlayer.animDone?
end
animPlayer.dispose
# Return Pokémon sprites to their original positions
if userSprite
userSprite.x = oldUserX
userSprite.y = oldUserY
userSprite.pbSetOrigin
end
if targetSprite
targetSprite.x = oldTargetX
targetSprite.y = oldTargetY
targetSprite.pbSetOrigin
end
end
end

View File

@@ -0,0 +1,876 @@
#===============================================================================
#
#===============================================================================
class AnimFrame
X = 0
Y = 1
ZOOMX = 2
ANGLE = 3
MIRROR = 4
BLENDTYPE = 5
VISIBLE = 6
PATTERN = 7
OPACITY = 8
ZOOMY = 11
COLORRED = 12
COLORGREEN = 13
COLORBLUE = 14
COLORALPHA = 15
TONERED = 16
TONEGREEN = 17
TONEBLUE = 18
TONEGRAY = 19
LOCKED = 20
FLASHRED = 21
FLASHGREEN = 22
FLASHBLUE = 23
FLASHALPHA = 24
PRIORITY = 25
FOCUS = 26
end
#===============================================================================
#
#===============================================================================
def yaxisIntersect(x1,y1,x2,y2,px,py)
dx = x2-x1
dy = y2-y1
x = (dx==0) ? 0.0 : (px-x1).to_f/dx
y = (dy==0) ? 0.0 : (py-y1).to_f/dy
return [x,y]
end
def repositionY(x1,y1,x2,y2,tx,ty)
dx = x2-x1
dy = y2-y1
x = x1+tx*dx.to_f
y = y1+ty*dy.to_f
return [x,y]
end
def transformPoint(x1,y1,x2,y2, # Source line
x3,y3,x4,y4, # Destination line
px,py) # Source point
ret = yaxisIntersect(x1,y1,x2,y2,px,py)
ret2 = repositionY(x3,y3,x4,y4,ret[0],ret[1])
return ret2
end
def getSpriteCenter(sprite)
return [0,0] if !sprite || sprite.disposed?
return [sprite.x,sprite.y] if !sprite.bitmap || sprite.bitmap.disposed?
centerX = sprite.src_rect.width/2
centerY = sprite.src_rect.height/2
offsetX = (centerX-sprite.ox)*sprite.zoom_x
offsetY = (centerY-sprite.oy)*sprite.zoom_y
return [sprite.x+offsetX,sprite.y+offsetY]
end
def isReversed(src0,src1,dst0,dst1)
return false if src0==src1
return (dst0>dst1) if src0<src1
return (dst0<dst1)
end
def pbCreateCel(x,y,pattern,focus=4)
frame = []
frame[AnimFrame::X] = x
frame[AnimFrame::Y] = y
frame[AnimFrame::PATTERN] = pattern
frame[AnimFrame::FOCUS] = focus # 1=target, 2=user, 3=user and target, 4=screen
frame[AnimFrame::LOCKED] = 0
pbResetCel(frame)
return frame
end
def pbResetCel(frame)
return if !frame
frame[AnimFrame::ZOOMX] = 100
frame[AnimFrame::ZOOMY] = 100
frame[AnimFrame::BLENDTYPE] = 0
frame[AnimFrame::VISIBLE] = 1
frame[AnimFrame::ANGLE] = 0
frame[AnimFrame::MIRROR] = 0
frame[AnimFrame::OPACITY] = 255
frame[AnimFrame::COLORRED] = 0
frame[AnimFrame::COLORGREEN] = 0
frame[AnimFrame::COLORBLUE] = 0
frame[AnimFrame::COLORALPHA] = 0
frame[AnimFrame::TONERED] = 0
frame[AnimFrame::TONEGREEN] = 0
frame[AnimFrame::TONEBLUE] = 0
frame[AnimFrame::TONEGRAY] = 0
frame[AnimFrame::FLASHRED] = 0
frame[AnimFrame::FLASHGREEN] = 0
frame[AnimFrame::FLASHBLUE] = 0
frame[AnimFrame::FLASHALPHA] = 0
frame[AnimFrame::PRIORITY] = 1 # 0=back, 1=front, 2=behind focus, 3=before focus
end
#===============================================================================
#
#===============================================================================
def pbConvertRPGAnimation(animation)
pbAnim = PBAnimation.new
pbAnim.id = animation.id
pbAnim.name = animation.name.clone
pbAnim.graphic = animation.animation_name
pbAnim.hue = animation.animation_hue
pbAnim.array.clear
yOffset = 0
pbAnim.position = animation.position
yOffset = -64 if animation.position==0
yOffset = 64 if animation.position==2
for i in 0...animation.frames.length
frame = pbAnim.addFrame
animFrame = animation.frames[i]
for j in 0...animFrame.cell_max
data = animFrame.cell_data
if data[j,0]==-1
frame.push(nil)
next
end
if animation.position==3 # Screen
point = transformPoint(
-160,80,160,-80,
PokeBattle_SceneConstants::FOCUSUSER_X,PokeBattle_SceneConstants::FOCUSUSER_Y,
PokeBattle_SceneConstants::FOCUSTARGET_X,PokeBattle_SceneConstants::FOCUSTARGET_Y,
data[j,1],data[j,2]
)
cel = pbCreateCel(point[0],point[1],data[j,0])
else
cel = pbCreateCel(data[j,1],data[j,2]+yOffset,data[j,0])
end
cel[AnimFrame::ZOOMX] = data[j,3]
cel[AnimFrame::ZOOMY] = data[j,3]
cel[AnimFrame::ANGLE] = data[j,4]
cel[AnimFrame::MIRROR] = data[j,5]
cel[AnimFrame::OPACITY] = data[j,6]
cel[AnimFrame::BLENDTYPE] = 0
frame.push(cel)
end
end
for i in 0...animation.timings.length
timing = animation.timings[i]
newTiming = PBAnimTiming.new
newTiming.frame = timing.frame
newTiming.name = timing.se.name
newTiming.volume = timing.se.volume
newTiming.pitch = timing.se.pitch
newTiming.flashScope = timing.flash_scope
newTiming.flashColor = timing.flash_color.clone
newTiming.flashDuration = timing.flash_duration
pbAnim.timing.push(newTiming)
end
return pbAnim
end
#===============================================================================
#
#===============================================================================
class RPG::Animation
def self.fromOther(otherAnim,id)
ret = RPG::Animation.new
ret.id = id
ret.name = otherAnim.name.clone
ret.animation_name = otherAnim.animation_name.clone
ret.animation_hue = otherAnim.animation_hue
ret.position = otherAnim.position
return ret
end
def addSound(frame,se)
timing = RPG::Animation::Timing.new
timing.frame = frame
timing.se = RPG::AudioFile.new(se,100)
self.timings.push(timing)
end
def addAnimation(otherAnim,frame,x,y) # frame is zero-based
if frame+otherAnim.frames.length>=self.frames.length
totalframes = frame+otherAnim.frames.length+1
for i in self.frames.length...totalframes
self.frames.push(RPG::Animation::Frame.new)
end
end
self.frame_max = self.frames.length
for i in 0...otherAnim.frame_max
thisframe = self.frames[frame+i]
otherframe = otherAnim.frames[i]
cellStart = thisframe.cell_max
thisframe.cell_max += otherframe.cell_max
thisframe.cell_data.resize(thisframe.cell_max,8)
for j in 0...otherframe.cell_max
thisframe.cell_data[cellStart+j,0] = otherframe.cell_data[j,0]
thisframe.cell_data[cellStart+j,1] = otherframe.cell_data[j,1]+x
thisframe.cell_data[cellStart+j,2] = otherframe.cell_data[j,2]+y
thisframe.cell_data[cellStart+j,3] = otherframe.cell_data[j,3]
thisframe.cell_data[cellStart+j,4] = otherframe.cell_data[j,4]
thisframe.cell_data[cellStart+j,5] = otherframe.cell_data[j,5]
thisframe.cell_data[cellStart+j,6] = otherframe.cell_data[j,6]
thisframe.cell_data[cellStart+j,7] = otherframe.cell_data[j,7]
end
end
for i in 0...otherAnim.timings.length
timing = RPG::Animation::Timing.new
othertiming = otherAnim.timings[i]
timing.frame = frame+othertiming.frame
timing.se = RPG::AudioFile.new(
othertiming.se.name.clone,
othertiming.se.volume,
othertiming.se.pitch)
timing.flash_scope = othertiming.flash_scope
timing.flash_color = othertiming.flash_color.clone
timing.flash_duration = othertiming.flash_duration
timing.condition = othertiming.condition
self.timings.push(timing)
end
self.timings.sort! { |a,b| a.frame<=>b.frame }
end
end
#===============================================================================
#
#===============================================================================
class PBAnimTiming
attr_accessor :frame
attr_writer :timingType # 0=play SE, 1=set bg, 2=bg mod
attr_accessor :name # Name of SE file or BG file
attr_accessor :volume
attr_accessor :pitch
attr_accessor :bgX # x coordinate of bg (or to move bg to)
attr_accessor :bgY # y coordinate of bg (or to move bg to)
attr_accessor :opacity # Opacity of bg (or to change bg to)
attr_accessor :colorRed # Color of bg (or to change bg to)
attr_accessor :colorGreen # Color of bg (or to change bg to)
attr_accessor :colorBlue # Color of bg (or to change bg to)
attr_accessor :colorAlpha # Color of bg (or to change bg to)
attr_writer :duration # How long to spend changing to the new bg coords/color
attr_accessor :flashScope
attr_accessor :flashColor
attr_accessor :flashDuration
def initialize(type=0)
@frame = 0
@timingType = type
@name = ""
@volume = 80
@pitch = 100
@bgX = nil
@bgY = nil
@opacity = nil
@colorRed = nil
@colorGreen = nil
@colorBlue = nil
@colorAlpha = nil
@duration = 5
@flashScope = 0
@flashColor = Color.new(255,255,255,255)
@flashDuration = 5
end
def timingType
return @timingType || 0
end
def duration
return @duration || 5
end
def to_s
case self.timingType
when 0
return "[#{@frame+1}] Play SE: #{name} (volume #{@volume}, pitch #{@pitch})"
when 1
text = sprintf("[%d] Set BG: \"%s\"",@frame+1,name)
text += sprintf(" (color=%s,%s,%s,%s)",
(@colorRed!=nil) ? @colorRed.to_i : "-",
(@colorGreen!=nil) ? @colorGreen.to_i : "-",
(@colorBlue!=nil) ? @colorBlue.to_i : "-",
(@colorAlpha!=nil) ? @colorAlpha.to_i : "-")
text += sprintf(" (opacity=%s)",@opacity.to_i)
text += sprintf(" (coords=%s,%s)",
(@bgX!=nil) ? @bgX : "-",
(@bgY!=nil) ? @bgY : "-")
return text
when 2
text = sprintf("[%d] Change BG: @%d",@frame+1,duration)
if @colorRed!=nil || @colorGreen!=nil || @colorBlue!=nil || @colorAlpha!=nil
text += sprintf(" (color=%s,%s,%s,%s)",
(@colorRed!=nil) ? @colorRed.to_i : "-",
(@colorGreen!=nil) ? @colorGreen.to_i : "-",
(@colorBlue!=nil) ? @colorBlue.to_i : "-",
(@colorAlpha!=nil) ? @colorAlpha.to_i : "-")
end
text += sprintf(" (opacity=%s)",@opacity.to_i) if @opacity!=nil
if @bgX!=nil || @bgY!=nil
text += sprintf(" (coords=%s,%s)",
(@bgX!=nil) ? @bgX : "-",
(@bgY!=nil) ? @bgY : "-")
end
return text
when 3
text = sprintf("[%d] Set FG: \"%s\"",@frame+1,name)
text += sprintf(" (color=%s,%s,%s,%s)",
(@colorRed!=nil) ? @colorRed.to_i : "-",
(@colorGreen!=nil) ? @colorGreen.to_i : "-",
(@colorBlue!=nil) ? @colorBlue.to_i : "-",
(@colorAlpha!=nil) ? @colorAlpha.to_i : "-")
text += sprintf(" (opacity=%s)",@opacity.to_i)
text += sprintf(" (coords=%s,%s)",
(@bgX!=nil) ? @bgX : "-",
(@bgY!=nil) ? @bgY : "-")
return text
when 4
text = sprintf("[%d] Change FG: @%d",@frame+1,duration)
if @colorRed!=nil || @colorGreen!=nil || @colorBlue!=nil || @colorAlpha!=nil
text += sprintf(" (color=%s,%s,%s,%s)",
(@colorRed!=nil) ? @colorRed.to_i : "-",
(@colorGreen!=nil) ? @colorGreen.to_i : "-",
(@colorBlue!=nil) ? @colorBlue.to_i : "-",
(@colorAlpha!=nil) ? @colorAlpha.to_i : "-")
end
text += sprintf(" (opacity=%s)",@opacity.to_i) if @opacity!=nil
if @bgX!=nil || @bgY!=nil
text += sprintf(" (coords=%s,%s)",
(@bgX!=nil) ? @bgX : "-",
(@bgY!=nil) ? @bgY : "-")
end
return text
end
return ""
end
end
#===============================================================================
#
#===============================================================================
class PBAnimations < Array
include Enumerable
attr_reader :array
attr_accessor :selected
def initialize(size=1)
@array = []
@selected = 0
size = 1 if size<1 # Always create at least one animation
size.times do
@array.push(PBAnimation.new)
end
end
def length
return @array.length
end
def each
@array.each { |i| yield i }
end
def [](i)
return @array[i]
end
def []=(i,value)
@array[i] = value
end
def compact
@array.compact!
end
def insert(index,val)
@array.insert(index,val)
end
def delete_at(index)
@array.delete_at(index)
end
def resize(len)
idxStart = @array.length
idxEnd = len
if idxStart>idxEnd
for i in idxEnd...idxStart
@array.pop
end
else
for i in idxStart...idxEnd
@array.push(PBAnimation.new)
end
end
self.selected = len if self.selected>=len
end
end
#===============================================================================
#
#===============================================================================
class PBAnimation < Array
include Enumerable
attr_accessor :id
attr_accessor :name
attr_accessor :graphic
attr_accessor :hue
attr_accessor :position
attr_writer :speed
attr_reader :array
attr_reader :timing
MAX_SPRITES = 60
def speed
return @speed || 20
end
def initialize(size=1)
@id = -1
@name = ""
@graphic = ""
@hue = 0
@position = 4 # 1=target, 2=user, 3=user and target, 4=screen
@array = []
size = 1 if size<1 # Always create at least one frame
size.times do; addFrame; end
@timing = []
@scope = 0
end
def length
return @array.length
end
def each
@array.each { |i| yield i }
end
def [](i)
return @array[i]
end
def []=(i,value)
@array[i] = value
end
def insert(*arg)
return @array.insert(*arg)
end
def delete_at(*arg)
return @array.delete_at(*arg)
end
def resize(len)
if len<@array.length
@array[len,@array.length-len] = []
elsif len>@array.length
(len-@array.length).times do
addFrame
end
end
end
def addFrame
pos = @array.length
@array[pos] = []
# Move's user
@array[pos][0] = pbCreateCel(
PokeBattle_SceneConstants::FOCUSUSER_X,
PokeBattle_SceneConstants::FOCUSUSER_Y,-1)
@array[pos][0][AnimFrame::FOCUS] = 2
@array[pos][0][AnimFrame::LOCKED] = 1
# Move's target
@array[pos][1] = pbCreateCel(
PokeBattle_SceneConstants::FOCUSTARGET_X,
PokeBattle_SceneConstants::FOCUSTARGET_Y,-2)
@array[pos][1][AnimFrame::FOCUS] = 1
@array[pos][1][AnimFrame::LOCKED] = 1
return @array[pos]
end
def playTiming(frame,bgGraphic,bgColor,foGraphic,foColor,oldbg=[],oldfo=[],user=nil)
for i in @timing
next if i.frame!=frame
case i.timingType
when 0 # Play SE
if i.name && i.name!=""
pbSEPlay("Anim/"+i.name,i.volume,i.pitch)
elsif user && user.pokemon
name = GameData::Species.cry_filename_from_pokemon(user.pokemon)
pbSEPlay(name,i.volume,i.pitch) if name
end
# if sprite
# sprite.flash(i.flashColor,i.flashDuration*2) if i.flashScope==1
# sprite.flash(nil,i.flashDuration*2) if i.flashScope==3
# end
when 1 # Set background graphic (immediate)
if i.name && i.name!=""
bgGraphic.setBitmap("Graphics/Animations/"+i.name)
bgGraphic.ox = -i.bgX || 0
bgGraphic.oy = -i.bgY || 0
bgGraphic.color = Color.new(i.colorRed || 0,i.colorGreen || 0,i.colorBlue || 0,i.colorAlpha || 0)
bgGraphic.opacity = i.opacity || 0
bgColor.opacity = 0
else
bgGraphic.setBitmap(nil)
bgGraphic.opacity = 0
bgColor.color = Color.new(i.colorRed || 0,i.colorGreen || 0,i.colorBlue || 0,i.colorAlpha || 0)
bgColor.opacity = i.opacity || 0
end
when 2 # Move/recolour background graphic
if bgGraphic.bitmap!=nil
oldbg[0] = bgGraphic.ox || 0
oldbg[1] = bgGraphic.oy || 0
oldbg[2] = bgGraphic.opacity || 0
oldbg[3] = bgGraphic.color.clone || Color.new(0,0,0,0)
else
oldbg[0] = 0
oldbg[1] = 0
oldbg[2] = bgColor.opacity || 0
oldbg[3] = bgColor.color.clone || Color.new(0,0,0,0)
end
when 3 # Set foreground graphic (immediate)
if i.name && i.name!=""
foGraphic.setBitmap("Graphics/Animations/"+i.name)
foGraphic.ox = -i.bgX || 0
foGraphic.oy = -i.bgY || 0
foGraphic.color = Color.new(i.colorRed || 0,i.colorGreen || 0,i.colorBlue || 0,i.colorAlpha || 0)
foGraphic.opacity = i.opacity || 0
foColor.opacity = 0
else
foGraphic.setBitmap(nil)
foGraphic.opacity = 0
foColor.color = Color.new(i.colorRed || 0,i.colorGreen || 0,i.colorBlue || 0,i.colorAlpha || 0)
foColor.opacity = i.opacity || 0
end
when 4 # Move/recolour foreground graphic
if foGraphic.bitmap!=nil
oldfo[0] = foGraphic.ox || 0
oldfo[1] = foGraphic.oy || 0
oldfo[2] = foGraphic.opacity || 0
oldfo[3] = foGraphic.color.clone || Color.new(0,0,0,0)
else
oldfo[0] = 0
oldfo[1] = 0
oldfo[2] = foColor.opacity || 0
oldfo[3] = foColor.color.clone || Color.new(0,0,0,0)
end
end
end
for i in @timing
case i.timingType
when 2
next if !i.duration || i.duration<=0
next if frame<i.frame || frame>i.frame+i.duration
fraction = (frame-i.frame).to_f/i.duration
if bgGraphic.bitmap!=nil
bgGraphic.ox = oldbg[0]-(i.bgX-oldbg[0])*fraction if i.bgX!=nil
bgGraphic.oy = oldbg[1]-(i.bgY-oldbg[1])*fraction if i.bgY!=nil
bgGraphic.opacity = oldbg[2]+(i.opacity-oldbg[2])*fraction if i.opacity!=nil
cr = (i.colorRed!=nil) ? oldbg[3].red+(i.colorRed-oldbg[3].red)*fraction : oldbg[3].red
cg = (i.colorGreen!=nil) ? oldbg[3].green+(i.colorGreen-oldbg[3].green)*fraction : oldbg[3].green
cb = (i.colorBlue!=nil) ? oldbg[3].blue+(i.colorBlue-oldbg[3].blue)*fraction : oldbg[3].blue
ca = (i.colorAlpha!=nil) ? oldbg[3].alpha+(i.colorAlpha-oldbg[3].alpha)*fraction : oldbg[3].alpha
bgGraphic.color = Color.new(cr,cg,cb,ca)
else
bgColor.opacity = oldbg[2]+(i.opacity-oldbg[2])*fraction if i.opacity!=nil
cr = (i.colorRed!=nil) ? oldbg[3].red+(i.colorRed-oldbg[3].red)*fraction : oldbg[3].red
cg = (i.colorGreen!=nil) ? oldbg[3].green+(i.colorGreen-oldbg[3].green)*fraction : oldbg[3].green
cb = (i.colorBlue!=nil) ? oldbg[3].blue+(i.colorBlue-oldbg[3].blue)*fraction : oldbg[3].blue
ca = (i.colorAlpha!=nil) ? oldbg[3].alpha+(i.colorAlpha-oldbg[3].alpha)*fraction : oldbg[3].alpha
bgColor.color = Color.new(cr,cg,cb,ca)
end
when 4
next if !i.duration || i.duration<=0
next if frame<i.frame || frame>i.frame+i.duration
fraction = (frame-i.frame).to_f/i.duration
if foGraphic.bitmap!=nil
foGraphic.ox = oldfo[0]-(i.bgX-oldfo[0])*fraction if i.bgX!=nil
foGraphic.oy = oldfo[1]-(i.bgY-oldfo[1])*fraction if i.bgY!=nil
foGraphic.opacity = oldfo[2]+(i.opacity-oldfo[2])*fraction if i.opacity!=nil
cr = (i.colorRed!=nil) ? oldfo[3].red+(i.colorRed-oldfo[3].red)*fraction : oldfo[3].red
cg = (i.colorGreen!=nil) ? oldfo[3].green+(i.colorGreen-oldfo[3].green)*fraction : oldfo[3].green
cb = (i.colorBlue!=nil) ? oldfo[3].blue+(i.colorBlue-oldfo[3].blue)*fraction : oldfo[3].blue
ca = (i.colorAlpha!=nil) ? oldfo[3].alpha+(i.colorAlpha-oldfo[3].alpha)*fraction : oldfo[3].alpha
foGraphic.color = Color.new(cr,cg,cb,ca)
else
foColor.opacity = oldfo[2]+(i.opacity-oldfo[2])*fraction if i.opacity!=nil
cr = (i.colorRed!=nil) ? oldfo[3].red+(i.colorRed-oldfo[3].red)*fraction : oldfo[3].red
cg = (i.colorGreen!=nil) ? oldfo[3].green+(i.colorGreen-oldfo[3].green)*fraction : oldfo[3].green
cb = (i.colorBlue!=nil) ? oldfo[3].blue+(i.colorBlue-oldfo[3].blue)*fraction : oldfo[3].blue
ca = (i.colorAlpha!=nil) ? oldfo[3].alpha+(i.colorAlpha-oldfo[3].alpha)*fraction : oldfo[3].alpha
foColor.color = Color.new(cr,cg,cb,ca)
end
end
end
end
end
#===============================================================================
#
#===============================================================================
def pbSpriteSetAnimFrame(sprite,frame,user=nil,target=nil,inEditor=false)
return if !sprite
if !frame
sprite.visible = false
sprite.src_rect = Rect.new(0,0,1,1)
return
end
sprite.blend_type = frame[AnimFrame::BLENDTYPE]
sprite.angle = frame[AnimFrame::ANGLE]
sprite.mirror = (frame[AnimFrame::MIRROR]>0)
sprite.opacity = frame[AnimFrame::OPACITY]
sprite.visible = true
if !frame[AnimFrame::VISIBLE]==1 && inEditor
sprite.opacity /= 2
else
sprite.visible = (frame[AnimFrame::VISIBLE]==1)
end
pattern = frame[AnimFrame::PATTERN]
if pattern>=0
animwidth = 192
sprite.src_rect.set((pattern%5)*animwidth,(pattern/5)*animwidth,
animwidth,animwidth)
else
sprite.src_rect.set(0,0,
(sprite.bitmap) ? sprite.bitmap.width : 128,
(sprite.bitmap) ? sprite.bitmap.height : 128)
end
sprite.zoom_x = frame[AnimFrame::ZOOMX]/100.0
sprite.zoom_y = frame[AnimFrame::ZOOMY]/100.0
sprite.color.set(
frame[AnimFrame::COLORRED],
frame[AnimFrame::COLORGREEN],
frame[AnimFrame::COLORBLUE],
frame[AnimFrame::COLORALPHA]
)
sprite.tone.set(
frame[AnimFrame::TONERED],
frame[AnimFrame::TONEGREEN],
frame[AnimFrame::TONEBLUE],
frame[AnimFrame::TONEGRAY]
)
sprite.ox = sprite.src_rect.width/2
sprite.oy = sprite.src_rect.height/2
sprite.x = frame[AnimFrame::X]
sprite.y = frame[AnimFrame::Y]
if sprite!=user && sprite!=target
case frame[AnimFrame::PRIORITY]
when 0 # Behind everything
sprite.z = 10
when 1 # In front of everything
sprite.z = 80
when 2 # Just behind focus
case frame[AnimFrame::FOCUS]
when 1 # Focused on target
sprite.z = (target) ? target.z-1 : 20
when 2 # Focused on user
sprite.z = (user) ? user.z-1 : 20
else # Focused on user and target, or screen
sprite.z = 20
end
when 3 # Just in front of focus
case frame[AnimFrame::FOCUS]
when 1 # Focused on target
sprite.z = (target) ? target.z+1 : 80
when 2 # Focused on user
sprite.z = (user) ? user.z+1 : 80
else # Focused on user and target, or screen
sprite.z = 80
end
else
sprite.z = 80
end
end
end
#===============================================================================
# Animation player
#===============================================================================
class PBAnimationPlayerX
attr_accessor :looping
MAX_SPRITES = 60
def initialize(animation,user,target,scene=nil,oppMove=false,inEditor=false)
@animation = animation
@user = (oppMove) ? target : user # Just used for playing user's cry
@usersprite = (user) ? scene.sprites["pokemon_#{user.index}"] : nil
@targetsprite = (target) ? scene.sprites["pokemon_#{target.index}"] : nil
@userbitmap = (@usersprite && @usersprite.bitmap) ? @usersprite.bitmap : nil # not to be disposed
@targetbitmap = (@targetsprite && @targetsprite.bitmap) ? @targetsprite.bitmap : nil # not to be disposed
@scene = scene
@viewport = (scene) ? scene.viewport : nil
@inEditor = inEditor
@looping = false
@animbitmap = nil # Animation sheet graphic
@frame = -1
@framesPerTick = [Graphics.frame_rate/20,1].max # 20 ticks per second
@srcLine = nil
@dstLine = nil
@userOrig = getSpriteCenter(@usersprite)
@targetOrig = getSpriteCenter(@targetsprite)
@oldbg = []
@oldfo = []
initializeSprites
end
def initializeSprites
# Create animation sprites (0=user's sprite, 1=target's sprite)
@animsprites = []
@animsprites[0] = @usersprite
@animsprites[1] = @targetsprite
for i in 2...MAX_SPRITES
@animsprites[i] = Sprite.new(@viewport)
@animsprites[i].bitmap = nil
@animsprites[i].visible = false
end
# Create background colour sprite
@bgColor = ColoredPlane.new(Color.new(0,0,0),@viewport)
@bgColor.borderX = 64 if @inEditor
@bgColor.borderY = 64 if @inEditor
@bgColor.z = 5
@bgColor.opacity = 0
@bgColor.refresh
# Create background graphic sprite
@bgGraphic = AnimatedPlane.new(@viewport)
@bgGraphic.setBitmap(nil)
@bgGraphic.borderX = 64 if @inEditor
@bgGraphic.borderY = 64 if @inEditor
@bgGraphic.z = 5
@bgGraphic.opacity = 0
@bgGraphic.refresh
# Create foreground colour sprite
@foColor = ColoredPlane.new(Color.new(0,0,0),@viewport)
@foColor.borderX = 64 if @inEditor
@foColor.borderY = 64 if @inEditor
@foColor.z = 85
@foColor.opacity = 0
@foColor.refresh
# Create foreground graphic sprite
@foGraphic = AnimatedPlane.new(@viewport)
@foGraphic.setBitmap(nil)
@foGraphic.borderX = 64 if @inEditor
@foGraphic.borderY = 64 if @inEditor
@foGraphic.z = 85
@foGraphic.opacity = 0
@foGraphic.refresh
end
def dispose
@animbitmap.dispose if @animbitmap
for i in 2...MAX_SPRITES
@animsprites[i].dispose if @animsprites[i]
end
@bgGraphic.dispose
@bgColor.dispose
@foGraphic.dispose
@foColor.dispose
end
def start
@frame = 0
end
def animDone?
return @frame<0
end
def setLineTransform(x1,y1,x2,y2,x3,y3,x4,y4)
@srcLine = [x1,y1,x2,y2]
@dstLine = [x3,y3,x4,y4]
end
def update
return if @frame<0
animFrame = @frame/@framesPerTick
# Loop or end the animation if the animation has reached the end
if animFrame >= @animation.length
@frame = (@looping) ? 0 : -1
if @frame<0
@animbitmap.dispose if @animbitmap
@animbitmap = nil
return
end
end
# Load the animation's spritesheet and assign it to all the sprites.
if !@animbitmap || @animbitmap.disposed?
@animbitmap = AnimatedBitmap.new("Graphics/Animations/"+@animation.graphic,
@animation.hue).deanimate
for i in 0...MAX_SPRITES
@animsprites[i].bitmap = @animbitmap if @animsprites[i]
end
end
# Update background and foreground graphics
@bgGraphic.update
@bgColor.update
@foGraphic.update
@foColor.update
# Update all the sprites to depict the animation's next frame
if @framesPerTick==1 || (@frame%@framesPerTick)==0
thisframe = @animation[animFrame]
# Make all cel sprites invisible
for i in 0...MAX_SPRITES
@animsprites[i].visible = false if @animsprites[i]
end
# Set each cel sprite acoordingly
for i in 0...thisframe.length
cel = thisframe[i]
next if !cel
sprite = @animsprites[i]
next if !sprite
# Set cel sprite's graphic
case cel[AnimFrame::PATTERN]
when -1
sprite.bitmap = @userbitmap
when -2
sprite.bitmap = @targetbitmap
else
sprite.bitmap = @animbitmap
end
# Apply settings to the cel sprite
pbSpriteSetAnimFrame(sprite,cel,@usersprite,@targetsprite)
case cel[AnimFrame::FOCUS]
when 1 # Focused on target
sprite.x = cel[AnimFrame::X]+@targetOrig[0]-PokeBattle_SceneConstants::FOCUSTARGET_X
sprite.y = cel[AnimFrame::Y]+@targetOrig[1]-PokeBattle_SceneConstants::FOCUSTARGET_Y
when 2 # Focused on user
sprite.x = cel[AnimFrame::X]+@userOrig[0]-PokeBattle_SceneConstants::FOCUSUSER_X
sprite.y = cel[AnimFrame::Y]+@userOrig[1]-PokeBattle_SceneConstants::FOCUSUSER_Y
when 3 # Focused on user and target
next if !@srcLine || !@dstLine
point = transformPoint(
@srcLine[0],@srcLine[1],@srcLine[2],@srcLine[3],
@dstLine[0],@dstLine[1],@dstLine[2],@dstLine[3],
sprite.x,sprite.y)
sprite.x = point[0]
sprite.y = point[1]
if isReversed(@srcLine[0],@srcLine[2],@dstLine[0],@dstLine[2]) &&
cel[AnimFrame::PATTERN]>=0
# Reverse direction
sprite.mirror = !sprite.mirror
end
end
sprite.x += 64 if @inEditor
sprite.y += 64 if @inEditor
end
# Play timings
@animation.playTiming(animFrame,@bgGraphic,@bgColor,@foGraphic,@foColor,@oldbg,@oldfo,@user)
end
@frame += 1
end
end

View File

@@ -0,0 +1,500 @@
#===============================================================================
# Simple battler class for the wild Pokémon in a Safari Zone battle
#===============================================================================
class PokeBattle_FakeBattler
attr_reader :battle
attr_reader :index
attr_reader :pokemon
attr_reader :owned
def initialize(battle,index)
@battle = battle
@pokemon = battle.party2[0]
@index = index
end
def pokemonIndex; return 0; end
def species; return @pokemon.species; end
def gender; return @pokemon.gender; end
def status; return @pokemon.status; end
def hp; return @pokemon.hp; end
def level; return @pokemon.level; end
def name; return @pokemon.name; end
def totalhp; return @pokemon.totalhp; end
def displayGender; return @pokemon.gender; end
def shiny?; return @pokemon.shiny?; end
alias isShiny? shiny?
def isSpecies?(check_species)
return @pokemon && @pokemon.isSpecies?(check_species)
end
def fainted?; return false; end
alias isFainted? fainted?
def shadowPokemon?; return false; end
alias isShadow? shadowPokemon?
def hasMega?; return false; end
def mega?; return false; end
alias isMega? mega?
def hasPrimal?; return false; end
def primal?; return false; end
alias isPrimal? primal?
def captured; return false; end
def captured=(value); end
def owned?
return $Trainer.owned?(pokemon.species)
end
def pbThis(lowerCase=false)
return (lowerCase) ? _INTL("the wild {1}",name) : _INTL("The wild {1}",name)
end
def opposes?(i)
i = i.index if i.is_a?(PokeBattle_FakeBattler)
return (@index&1)!=(i&1)
end
def pbReset; end
end
#===============================================================================
# Data box for safari battles
#===============================================================================
class SafariDataBox < SpriteWrapper
attr_accessor :selected
def initialize(battle,viewport=nil)
super(viewport)
@selected = 0
@battle = battle
@databox = AnimatedBitmap.new("Graphics/Pictures/Battle/databox_safari")
self.x = Graphics.width - 232
self.y = Graphics.height - 184
@contents = BitmapWrapper.new(@databox.width,@databox.height)
self.bitmap = @contents
self.visible = false
self.z = 50
pbSetSystemFont(self.bitmap)
refresh
end
def refresh
self.bitmap.clear
self.bitmap.blt(0,0,@databox.bitmap,Rect.new(0,0,@databox.width,@databox.height))
base = Color.new(72,72,72)
shadow = Color.new(184,184,184)
textpos = []
textpos.push([_INTL("Safari Balls"),30,2,false,base,shadow])
textpos.push([_INTL("Left: {1}",@battle.ballCount),30,32,false,base,shadow])
pbDrawTextPositions(self.bitmap,textpos)
end
def update(frameCounter=0)
super()
end
end
#===============================================================================
# Shows the player throwing bait at a wild Pokémon in a Safari battle.
#===============================================================================
class ThrowBaitAnimation < PokeBattle_Animation
include PokeBattle_BallAnimationMixin
def initialize(sprites,viewport,battler)
@battler = battler
@trainer = battler.battle.pbGetOwnerFromBattlerIndex(battler.index)
super(sprites,viewport)
end
def createProcesses
# Calculate start and end coordinates for battler sprite movement
batSprite = @sprites["pokemon_#{@battler.index}"]
traSprite = @sprites["player_1"]
ballPos = PokeBattle_SceneConstants.pbBattlerPosition(@battler.index,batSprite.sideSize)
ballStartX = traSprite.x
ballStartY = traSprite.y-traSprite.bitmap.height/2
ballMidX = 0 # Unused in arc calculation
ballMidY = 122
ballEndX = ballPos[0]-40
ballEndY = ballPos[1]-4
# Set up trainer sprite
trainer = addSprite(traSprite,PictureOrigin::Bottom)
# Set up bait sprite
ball = addNewSprite(ballStartX,ballStartY,
"Graphics/Battle animations/safari_bait",PictureOrigin::Center)
ball.setZ(0,batSprite.z+1)
# Trainer animation
if traSprite.bitmap.width>=traSprite.bitmap.height*2
ballStartX, ballStartY = trainerThrowingFrames(ball,trainer,traSprite)
end
delay = ball.totalDuration # 0 or 7
# Bait arc animation
ball.setSE(delay,"Battle throw")
createBallTrajectory(ball,delay,12,
ballStartX,ballStartY,ballMidX,ballMidY,ballEndX,ballEndY)
ball.setZ(9,batSprite.z+1)
delay = ball.totalDuration
ball.moveOpacity(delay+8,2,0)
ball.setVisible(delay+10,false)
# Set up battler sprite
battler = addSprite(batSprite,PictureOrigin::Bottom)
# Show Pokémon jumping before eating the bait
delay = ball.totalDuration+3
2.times do
battler.setSE(delay,"player jump")
battler.moveDelta(delay,3,0,-16)
battler.moveDelta(delay+4,3,0,16)
delay = battler.totalDuration+1
end
# Show Pokémon eating the bait
delay = battler.totalDuration+3
2.times do
battler.moveAngle(delay,7,5)
battler.moveDelta(delay,7,0,6)
battler.moveAngle(delay+7,7,0)
battler.moveDelta(delay+7,7,0,-6)
delay = battler.totalDuration
end
end
end
#===============================================================================
# Shows the player throwing a rock at a wild Pokémon in a Safari battle.
#===============================================================================
class ThrowRockAnimation < PokeBattle_Animation
include PokeBattle_BallAnimationMixin
def initialize(sprites,viewport,battler)
@battler = battler
@trainer = battler.battle.pbGetOwnerFromBattlerIndex(battler.index)
super(sprites,viewport)
end
def createProcesses
# Calculate start and end coordinates for battler sprite movement
batSprite = @sprites["pokemon_#{@battler.index}"]
traSprite = @sprites["player_1"]
ballStartX = traSprite.x
ballStartY = traSprite.y-traSprite.bitmap.height/2
ballMidX = 0 # Unused in arc calculation
ballMidY = 122
ballEndX = batSprite.x
ballEndY = batSprite.y-batSprite.bitmap.height/2
# Set up trainer sprite
trainer = addSprite(traSprite,PictureOrigin::Bottom)
# Set up bait sprite
ball = addNewSprite(ballStartX,ballStartY,
"Graphics/Battle animations/safari_rock",PictureOrigin::Center)
ball.setZ(0,batSprite.z+1)
# Trainer animation
if traSprite.bitmap.width>=traSprite.bitmap.height*2
ballStartX, ballStartY = trainerThrowingFrames(ball,trainer,traSprite)
end
delay = ball.totalDuration # 0 or 7
# Bait arc animation
ball.setSE(delay,"Battle throw")
createBallTrajectory(ball,delay,12,
ballStartX,ballStartY,ballMidX,ballMidY,ballEndX,ballEndY)
ball.setZ(9,batSprite.z+1)
delay = ball.totalDuration
ball.setSE(delay,"Battle damage weak")
ball.moveOpacity(delay+2,2,0)
ball.setVisible(delay+4,false)
# Set up anger sprite
anger = addNewSprite(ballEndX-42,ballEndY-36,
"Graphics/Battle animations/safari_anger",PictureOrigin::Center)
anger.setVisible(0,false)
anger.setZ(0,batSprite.z+1)
# Show anger appearing
delay = ball.totalDuration+5
2.times do
anger.setSE(delay,"Player jump")
anger.setVisible(delay,true)
anger.moveZoom(delay,3,130)
anger.moveZoom(delay+3,3,100)
anger.setVisible(delay+6,false)
anger.setDelta(delay+6,96,-16)
delay = anger.totalDuration+3
end
end
end
#===============================================================================
# Safari Zone battle scene (the visuals of the battle)
#===============================================================================
class PokeBattle_Scene
def pbSafariStart
@briefMessage = false
@sprites["dataBox_0"] = SafariDataBox.new(@battle,@viewport)
dataBoxAnim = DataBoxAppearAnimation.new(@sprites,@viewport,0)
loop do
dataBoxAnim.update
pbUpdate
break if dataBoxAnim.animDone?
end
dataBoxAnim.dispose
pbRefresh
end
def pbSafariCommandMenu(index)
pbCommandMenuEx(index,[
_INTL("What will\n{1} throw?",@battle.pbPlayer.name),
_INTL("Ball"),
_INTL("Bait"),
_INTL("Rock"),
_INTL("Run")
],3)
end
def pbThrowBait
@briefMessage = false
baitAnim = ThrowBaitAnimation.new(@sprites,@viewport,@battle.battlers[1])
loop do
baitAnim.update
pbUpdate
break if baitAnim.animDone?
end
baitAnim.dispose
end
def pbThrowRock
@briefMessage = false
rockAnim = ThrowRockAnimation.new(@sprites,@viewport,@battle.battlers[1])
loop do
rockAnim.update
pbUpdate
break if rockAnim.animDone?
end
rockAnim.dispose
end
alias __safari__pbThrowSuccess pbThrowSuccess
def pbThrowSuccess
__safari__pbThrowSuccess
pbWildBattleSuccess if @battle.is_a?(PokeBattle_SafariZone)
end
end
#===============================================================================
# Safari Zone battle class
#===============================================================================
class PokeBattle_SafariZone
attr_reader :battlers # Array of fake battler objects
attr_accessor :sideSizes # Array of number of battlers per side
attr_accessor :backdrop # Filename fragment used for background graphics
attr_accessor :backdropBase # Filename fragment used for base graphics
attr_accessor :time # Time of day (0=day, 1=eve, 2=night)
attr_accessor :environment # Battle surroundings (for mechanics purposes)
attr_reader :weather
attr_reader :player
attr_accessor :party2
attr_accessor :canRun # True if player can run from battle
attr_accessor :canLose # True if player won't black out if they lose
attr_accessor :switchStyle # Switch/Set "battle style" option
attr_accessor :showAnims # "Battle scene" option (show anims)
attr_accessor :expGain # Whether Pokémon can gain Exp/EVs
attr_accessor :moneyGain # Whether the player can gain/lose money
attr_accessor :rules
attr_accessor :ballCount
include PokeBattle_BattleCommon
def pbRandom(x); return rand(x); end
#=============================================================================
# Initialize the battle class
#=============================================================================
def initialize(scene,player,party2)
@scene = scene
@peer = PokeBattle_BattlePeer.create()
@backdrop = ""
@backdropBase = nil
@time = 0
@environment = :None # e.g. Tall grass, cave, still water
@weather = :None
@decision = 0
@caughtPokemon = []
@player = [player]
@party2 = party2
@sideSizes = [1,1]
@battlers = [
PokeBattle_FakeBattler.new(self,0),
PokeBattle_FakeBattler.new(self,1)
]
@rules = {}
@ballCount = 0
end
def defaultWeather=(value); @weather = value; end
def defaultTerrain=(value); end
#=============================================================================
# Information about the type and size of the battle
#=============================================================================
def wildBattle?; return true; end
def trainerBattle?; return false; end
def setBattleMode(mode); end
def pbSideSize(index)
return @sideSizes[index%2]
end
#=============================================================================
# Trainers and owner-related
#=============================================================================
def pbPlayer; return @player[0]; end
def opponent; return nil; end
def pbGetOwnerFromBattlerIndex(idxBattler); return pbPlayer; end
#=============================================================================
# Get party info (counts all teams on the same side)
#=============================================================================
def pbParty(idxBattler)
return (opposes?(idxBattler)) ? @party2 : nil
end
def pbAllFainted?(idxBattler=0); return false; end
#=============================================================================
# Battler-related
#=============================================================================
def opposes?(idxBattler1,idxBattler2=0)
idxBattler1 = idxBattler1.index if idxBattler1.respond_to?("index")
idxBattler2 = idxBattler2.index if idxBattler2.respond_to?("index")
return (idxBattler1&1)!=(idxBattler2&1)
end
def pbRemoveFromParty(idxBattler,idxParty); end
def pbGainExp; end
#=============================================================================
# Messages and animations
#=============================================================================
def pbDisplay(msg,&block)
@scene.pbDisplayMessage(msg,&block)
end
def pbDisplayPaused(msg,&block)
@scene.pbDisplayPausedMessage(msg,&block)
end
def pbDisplayBrief(msg)
@scene.pbDisplayMessage(msg,true)
end
def pbDisplayConfirm(msg)
return @scene.pbDisplayConfirmMessage(msg)
end
class BattleAbortedException < Exception; end
def pbAbort
raise BattleAbortedException.new("Battle aborted")
end
#=============================================================================
# Safari battle-specific methods
#=============================================================================
def pbEscapeRate(catch_rate)
return 125 if catch_rate <= 45 # Escape factor 9 (45%)
return 100 if catch_rate <= 60 # Escape factor 7 (35%)
return 75 if catch_rate <= 120 # Escape factor 5 (25%)
return 50 if catch_rate <= 250 # Escape factor 3 (15%)
return 25 # Escape factor 2 (10%)
end
def pbStartBattle
begin
pkmn = @party2[0]
self.pbPlayer.pokedex.register(pkmn)
@scene.pbStartBattle(self)
pbDisplayPaused(_INTL("Wild {1} appeared!",pkmn.name))
@scene.pbSafariStart
weather_data = GameData::BattleWeather.try_get(@weather)
@scene.pbCommonAnimation(weather_data.animation) if weather_data
safariBall = GameData::Item.get(:SAFARIBALL).id
catch_rate = pkmn.species_data.catch_rate
catchFactor = (catch_rate*100)/1275
catchFactor = [[catchFactor,3].max,20].min
escapeFactor = (pbEscapeRate(catch_rate)*100)/1275
escapeFactor = [[escapeFactor,2].max,20].min
loop do
cmd = @scene.pbSafariCommandMenu(0)
case cmd
when 0 # Ball
if pbBoxesFull?
pbDisplay(_INTL("The boxes are full! You can't catch any more Pokémon!"))
next
end
@ballCount -= 1
@scene.pbRefresh
rare = (catchFactor*1275)/100
if safariBall
pbThrowPokeBall(1,safariBall,rare,true)
if @caughtPokemon.length>0
pbRecordAndStoreCaughtPokemon
@decision = 4
end
end
when 1 # Bait
pbDisplayBrief(_INTL("{1} threw some bait at the {2}!",self.pbPlayer.name,pkmn.name))
@scene.pbThrowBait
catchFactor /= 2 if pbRandom(100)<90 # Harder to catch
escapeFactor /= 2 # Less likely to escape
when 2 # Rock
pbDisplayBrief(_INTL("{1} threw a rock at the {2}!",self.pbPlayer.name,pkmn.name))
@scene.pbThrowRock
catchFactor *= 2 # Easier to catch
escapeFactor *= 2 if pbRandom(100)<90 # More likely to escape
when 3 # Run
pbSEPlay("Battle flee")
pbDisplayPaused(_INTL("You got away safely!"))
@decision = 3
end
catchFactor = [[catchFactor,3].max,20].min
escapeFactor = [[escapeFactor,2].max,20].min
# End of round
if @decision==0
if @ballCount<=0
pbDisplay(_INTL("PA: You have no Safari Balls left! Game over!"))
@decision = 2
elsif pbRandom(100)<5*escapeFactor
pbSEPlay("Battle flee")
pbDisplay(_INTL("{1} fled!",pkmn.name))
@decision = 3
elsif cmd==1 # Bait
pbDisplay(_INTL("{1} is eating!",pkmn.name))
elsif cmd==2 # Rock
pbDisplay(_INTL("{1} is angry!",pkmn.name))
else
pbDisplay(_INTL("{1} is watching carefully!",pkmn.name))
end
# Weather continues
weather_data = GameData::BattleWeather.try_get(@weather)
@scene.pbCommonAnimation(weather_data.animation) if weather_data
end
break if @decision > 0
end
@scene.pbEndBattle(@decision)
rescue BattleAbortedException
@decision = 0
@scene.pbEndBattle(@decision)
end
return @decision
end
end

View File

@@ -0,0 +1,87 @@
#===============================================================================
# Bug Catching Contest battle scene (the visuals of the battle)
#===============================================================================
class PokeBattle_Scene
alias _bugContest_pbInitSprites pbInitSprites
def pbInitSprites
_bugContest_pbInitSprites
# "helpwindow" shows the currently caught Pokémon's details when asking if
# you want to replace it with a newly caught Pokémon.
@sprites["helpwindow"] = Window_UnformattedTextPokemon.newWithSize("",0,0,32,32,@viewport)
@sprites["helpwindow"].z = 90
@sprites["helpwindow"].visible = false
end
def pbShowHelp(text)
@sprites["helpwindow"].resizeToFit(text,Graphics.width)
@sprites["helpwindow"].y = 0
@sprites["helpwindow"].x = 0
@sprites["helpwindow"].text = text
@sprites["helpwindow"].visible = true
end
def pbHideHelp
@sprites["helpwindow"].visible = false
end
end
#===============================================================================
# Bug Catching Contest battle class
#===============================================================================
class PokeBattle_BugContestBattle < PokeBattle_Battle
attr_accessor :ballCount
def initialize(*arg)
@ballCount = 0
@ballConst = GameData::Item.get(:SPORTBALL).id
super(*arg)
end
def pbItemMenu(idxBattler,_firstAction)
return pbRegisterItem(idxBattler,@ballConst,1)
end
def pbCommandMenu(idxBattler,_firstAction)
return @scene.pbCommandMenuEx(idxBattler,[
_INTL("Sport Balls: {1}",@ballCount),
_INTL("Fight"),
_INTL("Ball"),
_INTL("Pokémon"),
_INTL("Run")
],4)
end
def pbConsumeItemInBag(_item,_idxBattler)
@ballCount -= 1 if @ballCount>0
end
def pbStorePokemon(pkmn)
if pbBugContestState.lastPokemon
lastPokemon = pbBugContestState.lastPokemon
pbDisplayPaused(_INTL("You already caught a {1}.",lastPokemon.name))
helptext = _INTL("STOCK POKéMON:\n {1} Lv.{2} MaxHP: {3}\nTHIS POKéMON:\n {4} Lv.{5} MaxHP: {6}",
lastPokemon.name,lastPokemon.level,lastPokemon.totalhp,
pkmn.name,pkmn.level,pkmn.totalhp
)
@scene.pbShowHelp(helptext)
if pbDisplayConfirm(_INTL("Switch Pokémon?"))
pbBugContestState.lastPokemon = pkmn
@scene.pbHideHelp
else
@scene.pbHideHelp
return
end
else
pbBugContestState.lastPokemon = pkmn
end
pbDisplay(_INTL("Caught {1}!",pkmn.name))
end
def pbEndOfRoundPhase
super
@decision = 3 if @ballCount<=0 && @decision==0
end
end

View File

@@ -0,0 +1,252 @@
#===============================================================================
#
#===============================================================================
class PokeBattle_BattlePalace < PokeBattle_Battle
@@BattlePalaceUsualTable = [
61, 7, 32,
20, 25, 55,
70, 15, 15,
38, 31, 31,
20, 70, 10,
30, 20, 50,
56, 22, 22,
25, 15, 60,
69, 6, 25,
35, 10, 55,
62, 10, 28,
58, 37, 5,
34, 11, 55,
35, 5, 60,
56, 22, 22,
35, 45, 20,
44, 50, 6,
56, 22, 22,
30, 58, 12,
30, 13, 57,
40, 50, 10,
18, 70, 12,
88, 6, 6,
42, 50, 8,
56, 22, 22
]
@@BattlePalacePinchTable = [
61, 7, 32,
84, 8, 8,
32, 60, 8,
70, 15, 15,
70, 22, 8,
32, 58, 10,
56, 22, 22,
75, 15, 10,
28, 55, 17,
29, 6, 65,
30, 20, 50,
88, 6, 6,
29, 11, 60,
35, 60, 5,
56, 22, 22,
34, 60, 6,
34, 6, 60,
56, 22, 22,
30, 58, 12,
27, 6, 67,
25, 62, 13,
90, 5, 5,
22, 20, 58,
42, 5, 53,
56, 22, 22
]
def initialize(*arg)
super
@justswitched = [false,false,false,false]
@battleAI.battlePalace = true
end
def pbMoveCategory(move)
if move.target == :User || move.function == "0D4" # Bide
return 1
elsif move.statusMove? ||
move.function == "071" || move.function == "072" # Counter, Mirror Coat
return 2
else
return 0
end
end
# Different implementation of pbCanChooseMove, ignores Imprison/Torment/Taunt/Disable/Encore
def pbCanChooseMovePartial?(idxPokemon,idxMove)
thispkmn = @battlers[idxPokemon]
thismove = thispkmn.moves[idxMove]
return false if !thismove
return false if thismove.pp<=0
if thispkmn.effects[PBEffects::ChoiceBand] &&
thismove.id!=thispkmn.effects[PBEffects::ChoiceBand] &&
thispkmn.hasActiveItem?(:CHOICEBAND)
return false
end
# though incorrect, just for convenience (actually checks Torment later)
if thispkmn.effects[PBEffects::Torment] && thispkmn.lastMoveUsed
return false if thismove.id==thispkmn.lastMoveUsed
end
return true
end
def pbPinchChange(idxPokemon)
thispkmn = @battlers[idxPokemon]
if !thispkmn.effects[PBEffects::Pinch] && thispkmn.status != :SLEEP &&
thispkmn.hp<=thispkmn.totalhp/2
nature = thispkmn.nature
thispkmn.effects[PBEffects::Pinch] = true
case nature
when :QUIET, :BASHFUL, :NAIVE, :QUIRKY, :HARDY, :DOCILE, :SERIOUS
pbDisplay(_INTL("{1} is eager for more!",thispkmn.pbThis))
when :CAREFUL, :RASH, :LAX, :SASSY, :MILD, :TIMID
pbDisplay(_INTL("{1} began growling deeply!",thispkmn.pbThis))
when :GENTLE, :ADAMANT, :HASTY, :LONELY, :RELAXED, :NAUGHTY
pbDisplay(_INTL("A glint appears in {1}'s eyes!",thispkmn.pbThis(true)))
when :JOLLY, :BOLD, :BRAVE, :CALM, :IMPISH, :MODEST
pbDisplay(_INTL("{1} is getting into position!",thispkmn.pbThis))
end
end
end
def pbRegisterMove(idxBattler,idxMove,_showMessages=true)
this_battler = @battlers[idxBattler]
if idxMove==-2
@choices[idxBattler][0] = :UseMove # Move
@choices[idxBattler][1] = -2 # "Incapable of using its power..."
@choices[idxBattler][2] = @struggle
@choices[idxBattler][3] = -1
else
@choices[idxBattler][0] = :UseMove # Move
@choices[idxBattler][1] = idxMove # Index of move
@choices[idxBattler][2] = this_battler.moves[idxMove] # Move object
@choices[idxBattler][3] = -1 # No target chosen
end
end
def pbAutoFightMenu(idxBattler)
this_battler = @battlers[idxBattler]
nature = this_battler.nature
randnum = @battleAI.pbAIRandom(100)
category = 0
atkpercent = 0
defpercent = 0
if this_battler.effects[PBEffects::Pinch]
atkpercent = @@BattlePalacePinchTable[nature*3]
defpercent = atkpercent+@@BattlePalacePinchTable[nature*3+1]
else
atkpercent = @@BattlePalaceUsualTable[nature*3]
defpercent = atkpercent+@@BattlePalaceUsualTable[nature*3+1]
end
if randnum<atkpercent
category = 0
elsif randnum<defpercent
category = 1
else
category = 2
end
moves = []
for i in 0...this_battler.moves.length
next if !pbCanChooseMovePartial?(idxBattler,i)
next if pbMoveCategory(this_battler.moves[i])!=category
moves[moves.length] = i
end
if moves.length==0
# No moves of selected category
pbRegisterMove(idxBattler,-2)
else
chosenmove = moves[@battleAI.pbAIRandom(moves.length)]
pbRegisterMove(idxBattler,chosenmove)
end
return true
end
def pbEndOfRoundPhase
super
return if @decision!=0
for i in 0...4
pbPinchChange(i) if !@battlers[i].fainted?
end
end
end
#===============================================================================
#
#===============================================================================
class PokeBattle_AI
attr_accessor :battlePalace
alias _battlePalace_initialize initialize
def initialize(*arg)
_battlePalace_initialize(*arg)
@justswitched = [false,false,false,false]
end
alias _battlePalace_pbEnemyShouldWithdraw? pbEnemyShouldWithdraw?
def pbEnemyShouldWithdraw?(idxBattler)
return _battlePalace_pbEnemyShouldWithdraw?(idxBattler) if !@battlePalace
thispkmn = @battle.battlers[idxBattler]
shouldswitch = false
if thispkmn.effects[PBEffects::PerishSong]==1
shouldswitch = true
elsif !@battle.pbCanChooseAnyMove?(idxBattler) &&
thispkmn.turnCount && thispkmn.turnCount>5
shouldswitch = true
else
hppercent = thispkmn.hp*100/thispkmn.totalhp
percents = []
maxindex = -1
maxpercent = 0
factor = 0
@battle.pbParty(idxBattler).each_with_index do |pkmn,i|
if @battle.pbCanSwitch?(idxBattler,i)
percents[i] = 100*pkmn.hp/pkmn.totalhp
if percents[i]>maxpercent
maxindex = i
maxpercent = percents[i]
end
else
percents[i] = 0
end
end
if hppercent<50
factor = (maxpercent<hppercent) ? 20 : 40
end
if hppercent<25
factor = (maxpercent<hppercent) ? 30 : 50
end
case thispkmn.status
when :SLEEP, :FROZEN
factor += 20
when :POISON, :BURN
factor += 10
when :PARALYSIS
factor += 15
end
if @justswitched[idxBattler]
factor -= 60
factor = 0 if factor<0
end
shouldswitch = (pbAIRandom(100)<factor)
if shouldswitch && maxindex>=0
@battle.pbRegisterSwitch(idxBattler,maxindex)
return true
end
end
@justswitched[idxBattler] = shouldswitch
if shouldswitch
@battle.pbParty(idxBattler).each_with_index do |_pkmn,i|
next if !@battle.pbCanSwitch?(idxBattler,i)
@battle.pbRegisterSwitch(idxBattler,i)
return true
end
end
return false
end
end

View File

@@ -0,0 +1,312 @@
#===============================================================================
#
#===============================================================================
class PokeBattle_BattleArena < PokeBattle_Battle
def initialize(*arg)
super
@battlersChanged = true
@mind = [0,0]
@skill = [0,0]
@starthp = [0,0]
@count = 0
@partyindexes = [0,0]
@battleAI.battleArena = true
end
def pbCanSwitchLax?(idxBattler,_idxParty,partyScene=nil)
if partyScene
partyScene.pbDisplay(_INTL("{1} can't be switched out!",@battlers[idxBattler].pbThis))
end
return false
end
def pbEORSwitch(favorDraws=false)
return if favorDraws && @decision==5
return if !favorDraws && @decision>0
pbJudge
return if @decision>0
for side in 0...2
next if !@battlers[side].fainted?
next if @partyindexes[side]+1>=self.pbParty(side).length
@partyindexes[side] += 1
newpoke = @partyindexes[side]
pbMessagesOnReplace(side,newpoke)
pbReplace(side,newpoke)
pbOnActiveOne(@battlers[side])
@battlers[side].pbEffectsOnSwitchIn(true)
end
end
def pbOnActiveAll
@battlersChanged = true
for side in 0...2
@mind[side] = 0
@skill[side] = 0
@starthp[side] = battlers[side].hp
end
@count = 0
return super
end
def pbOnActiveOne(*arg)
@battlersChanged = true
for side in 0...2
@mind[side] = 0
@skill[side] = 0
@starthp[side] = battlers[side].hp
end
@count = 0
return super
end
def pbMindScore(move)
if move.function=="0AA" || # Detect/Protect
move.function=="0E8" || # Endure
move.function=="012" # Fake Out
return -1
end
if move.function=="071" || # Counter
move.function=="072" || # Mirror Coat
move.function=="0D4" # Bide
return 0
end
return 0 if move.statusMove?
return 1
end
def pbCommandPhase
if @battlersChanged
@scene.pbBattleArenaBattlers(@battlers[0],@battlers[1])
@battlersChanged = false
@count = 0
end
super
return if @decision!=0
# Update mind rating (asserting that a move was chosen)
# TODO: Actually done at Pokémon's turn
for side in 0...2
if @choices[side][2] && @choices[side][0]==:UseMove
@mind[side] += pbMindScore(@choices[side][2])
end
end
end
def pbEndOfRoundPhase
super
return if @decision!=0
@count += 1
# Update skill rating
for side in 0...2
@skill[side] += self.successStates[side].skill_level
end
# PBDebug.log("[Mind: #{@mind.inspect}, Skill: #{@skill.inspect}]")
if @count==3
@battlers[0].pbCancelMoves
@battlers[1].pbCancelMoves
ratings1 = [0,0,0]
ratings2 = [0,0,0]
if @mind[0]==@mind[1]
ratings1[0] = 1
ratings2[0] = 1
elsif @mind[0]>@mind[1]
ratings1[0] = 2
else
ratings2[0] = 2
end
if @skill[0]==@skill[1]
ratings1[1] = 1
ratings2[1] = 1
elsif @skill[0]>@skill[1]
ratings1[1] = 2
else
ratings2[1] = 2
end
body = [0,0]
body[0] = ((@battlers[0].hp*100)/[@starthp[0],1].max).floor
body[1] = ((@battlers[1].hp*100)/[@starthp[1],1].max).floor
if body[0]==body[1]
ratings1[2] = 1
ratings2[2] = 1
elsif body[0]>body[1]
ratings1[2] = 2
else
ratings2[2] = 2
end
@scene.pbBattleArenaJudgment(@battlers[0],@battlers[1],ratings1.clone,ratings2.clone)
points = [0,0]
for i in 0...ratings1.length
points[0] += ratings1[i]
points[1] += ratings2[i]
end
if points[0]==points[1]
pbDisplay(_INTL("{1} tied the opponent\n{2} in a referee's decision!",
@battlers[0].name,@battlers[1].name))
# NOTE: Pokémon doesn't really lose HP, but the effect is mostly the
# same.
@battlers[0].hp = 0
@battlers[0].pbFaint(false)
@battlers[1].hp = 0
@battlers[1].pbFaint(false)
elsif points[0]>points[1]
pbDisplay(_INTL("{1} defeated the opponent\n{2} in a referee's decision!",
@battlers[0].name,@battlers[1].name))
@battlers[1].hp = 0
@battlers[1].pbFaint(false)
else
pbDisplay(_INTL("{1} lost to the opponent\n{2} in a referee's decision!",
@battlers[0].name,@battlers[1].name))
@battlers[0].hp = 0
@battlers[0].pbFaint(false)
end
pbGainExp
pbEORSwitch
end
end
end
#===============================================================================
#
#===============================================================================
class PokeBattle_AI
attr_accessor :battleArena
alias _battleArena_pbEnemyShouldWithdraw? pbEnemyShouldWithdraw?
def pbEnemyShouldWithdraw?(idxBattler)
return _battleArena_pbEnemyShouldWithdraw?(idxBattler) if !@battleArena
return false
end
end
#===============================================================================
#
#===============================================================================
class PokeBattle_Scene
def pbBattleArenaUpdate
pbGraphicsUpdate
end
def updateJudgment(window,phase,battler1,battler2,ratings1,ratings2)
total1 = 0
total2 = 0
for i in 0...phase
total1 += ratings1[i]
total2 += ratings2[i]
end
window.contents.clear
pbSetSystemFont(window.contents)
textpos = [
[battler1.name,64,-6,2,Color.new(248,0,0),Color.new(208,208,200)],
[_INTL("VS"),144,-6,2,Color.new(72,72,72),Color.new(208,208,200)],
[battler2.name,224,-6,2,Color.new(72,72,72),Color.new(208,208,200)],
[_INTL("Mind"),144,42,2,Color.new(72,72,72),Color.new(208,208,200)],
[_INTL("Skill"),144,74,2,Color.new(72,72,72),Color.new(208,208,200)],
[_INTL("Body"),144,106,2,Color.new(72,72,72),Color.new(208,208,200)],
[sprintf("%d",total1),64,154,2,Color.new(72,72,72),Color.new(208,208,200)],
[_INTL("Judgment"),144,154,2,Color.new(72,72,72),Color.new(208,208,200)],
[sprintf("%d",total2),224,154,2,Color.new(72,72,72),Color.new(208,208,200)]
]
pbDrawTextPositions(window.contents,textpos)
images = []
for i in 0...phase
y = [48,80,112][i]
x = (ratings1[i]==ratings2[i]) ? 64 : ((ratings1[i]>ratings2[i]) ? 0 : 32)
images.push(["Graphics/Pictures/judgment",64-16,y,x,0,32,32])
x = (ratings1[i]==ratings2[i]) ? 64 : ((ratings1[i]<ratings2[i]) ? 0 : 32)
images.push(["Graphics/Pictures/judgment",224-16,y,x,0,32,32])
end
pbDrawImagePositions(window.contents,images)
window.contents.fill_rect(16,150,256,4,Color.new(80,80,80))
end
def pbBattleArenaBattlers(battler1,battler2)
pbMessage(_INTL("REFEREE: {1} VS {2}!\nCommence battling!\\wtnp[20]",
battler1.name,battler2.name)) { pbBattleArenaUpdate }
end
def pbBattleArenaJudgment(battler1,battler2,ratings1,ratings2)
msgwindow = nil
dimmingvp = nil
infowindow = nil
begin
msgwindow = pbCreateMessageWindow
dimmingvp = Viewport.new(0,0,Graphics.width,Graphics.height-msgwindow.height)
pbMessageDisplay(msgwindow,
_INTL("REFEREE: That's it! We will now go to judging to determine the winner!\\wtnp[20]")) {
pbBattleArenaUpdate; dimmingvp.update }
dimmingvp.z = 99999
infowindow = SpriteWindow_Base.new(80,0,320,224)
infowindow.contents = Bitmap.new(infowindow.width-infowindow.borderX,
infowindow.height-infowindow.borderY)
infowindow.z = 99999
infowindow.visible = false
for i in 0..10
pbGraphicsUpdate
pbInputUpdate
msgwindow.update
dimmingvp.update
dimmingvp.color = Color.new(0,0,0,i*128/10)
end
updateJudgment(infowindow,0,battler1,battler2,ratings1,ratings2)
infowindow.visible = true
for i in 0..10
pbGraphicsUpdate
pbInputUpdate
msgwindow.update
dimmingvp.update
infowindow.update
end
updateJudgment(infowindow,1,battler1,battler2,ratings1,ratings2)
pbMessageDisplay(msgwindow,
_INTL("REFEREE: Judging category 1, Mind!\nThe Pokémon showing the most guts!\\wtnp[40]")) {
pbBattleArenaUpdate; dimmingvp.update; infowindow.update }
updateJudgment(infowindow,2,battler1,battler2,ratings1,ratings2)
pbMessageDisplay(msgwindow,
_INTL("REFEREE: Judging category 2, Skill!\nThe Pokémon using moves the best!\\wtnp[40]")) {
pbBattleArenaUpdate; dimmingvp.update; infowindow.update }
updateJudgment(infowindow,3,battler1,battler2,ratings1,ratings2)
pbMessageDisplay(msgwindow,
_INTL("REFEREE: Judging category 3, Body!\nThe Pokémon with the most vitality!\\wtnp[40]")) {
pbBattleArenaUpdate; dimmingvp.update; infowindow.update }
total1 = 0
total2 = 0
for i in 0...3
total1 += ratings1[i]
total2 += ratings2[i]
end
if total1==total2
pbMessageDisplay(msgwindow,
_INTL("REFEREE: Judgment: {1} to {2}!\nWe have a draw!\\wtnp[40]",total1,total2)) {
pbBattleArenaUpdate; dimmingvp.update; infowindow.update }
elsif total1>total2
pbMessageDisplay(msgwindow,
_INTL("REFEREE: Judgment: {1} to {2}!\nThe winner is {3}'s {4}!\\wtnp[40]",
total1,total2,@battle.pbGetOwnerName(battler1.index),battler1.name)) {
pbBattleArenaUpdate; dimmingvp.update; infowindow.update }
else
pbMessageDisplay(msgwindow,
_INTL("REFEREE: Judgment: {1} to {2}!\nThe winner is {3}!\\wtnp[40]",
total1,total2,battler2.name)) {
pbBattleArenaUpdate; dimmingvp.update; infowindow.update }
end
infowindow.visible = false
msgwindow.visible = false
for i in 0..10
pbGraphicsUpdate
pbInputUpdate
msgwindow.update
dimmingvp.update
dimmingvp.color = Color.new(0,0,0,(10-i)*128/10)
end
ensure
pbDisposeMessageWindow(msgwindow)
dimmingvp.dispose
infowindow.contents.dispose
infowindow.dispose
end
end
end

View File

@@ -0,0 +1,297 @@
#===============================================================================
#
#===============================================================================
module PokeBattle_RecordedBattleModule
attr_reader :randomnums
attr_reader :rounds
module Commands
Fight = 0
Bag = 1
Pokemon = 2
Run = 3
end
def initialize(*arg)
@randomnumbers = []
@rounds = []
@switches = []
@roundindex = -1
@properties = {}
super(*arg)
end
def pbGetBattleType
return 0 # Battle Tower
end
def pbGetTrainerInfo(trainer)
return nil if !trainer
if trainer.is_a?(Array)
ret = []
for i in 0...trainer.length
ret.push([trainer[i].trainer_type,trainer[i].name.clone,trainer[i].id,trainer[i].badges.clone])
end
return ret
else
return [
[trainer.trainer_type,trainer.name.clone,trainer.id,trainer.badges.clone]
]
end
end
def pbStartBattle
@properties = {}
@properties["internalBattle"] = @internalBattle
@properties["player"] = pbGetTrainerInfo(@player)
@properties["opponent"] = pbGetTrainerInfo(@opponent)
@properties["party1"] = Marshal.dump(@party1)
@properties["party2"] = Marshal.dump(@party2)
@properties["party1starts"] = Marshal.dump(@party1starts)
@properties["party2starts"] = Marshal.dump(@party2starts)
@properties["endSpeeches"] = (@endSpeeches) ? @endSpeeches.clone : ""
@properties["endSpeechesWin"] = (@endSpeechesWin) ? @endSpeechesWin.clone : ""
@properties["weather"] = @field.weather
@properties["weatherDuration"] = @field.weatherDuration
@properties["canRun"] = @canRun
@properties["switchStyle"] = @switchStyle
@properties["showAnims"] = @showAnims
@properties["items"] = Marshal.dump(@items)
@properties["environment"] = @environment
@properties["rules"] = Marshal.dump(@rules)
super
end
def pbDumpRecord
return Marshal.dump([pbGetBattleType,@properties,@rounds,@randomnumbers,@switches])
end
def pbSwitchInBetween(idxBattler,checkLaxOnly=false,canCancel=false)
ret = super
@switches.push(ret)
return ret
end
def pbRegisterMove(idxBattler,idxMove,showMessages=true)
if super
@rounds[@roundindex][idxBattler] = [Commands::Fight,idxMove]
return true
end
return false
end
def pbRegisterTarget(idxBattler,idxTarget)
super
@rounds[@roundindex][idxBattler][2] = idxTarget
end
def pbRun(idxBattler,duringBattle=false)
ret = super
@rounds[@roundindex][idxBattler] = [Commands::Run,@decision]
return ret
end
def pbAutoChooseMove(idxBattler,showMessages=true)
ret = super
@rounds[@roundindex][idxBattler] = [Commands::Fight,-1]
return ret
end
def pbRegisterSwitch(idxBattler,idxParty)
if super
@rounds[@roundindex][idxBattler] = [Commands::Pokemon,idxParty]
return true
end
return false
end
def pbRegisterItem(idxBattler,item,idxTarget=nil,idxMove=nil)
if super
@rounds[@roundindex][idxBattler] = [Commands::Bag,item,idxTarget,idxMove]
return true
end
return false
end
def pbCommandPhase
@roundindex += 1
@rounds[@roundindex] = [[],[],[],[]]
super
end
def pbStorePokemon(pkmn); end
def pbRandom(num)
ret = super(num)
@randomnumbers.push(ret)
return ret
end
end
#===============================================================================
#
#===============================================================================
module BattlePlayerHelper
def self.pbGetOpponent(battle)
return self.pbCreateTrainerInfo(battle[1]["opponent"])
end
def self.pbGetBattleBGM(battle)
return self.pbGetTrainerBattleBGM(self.pbGetOpponent(battle))
end
def self.pbCreateTrainerInfo(trainer)
return nil if !trainer
if trainer.length>1
ret = []
ret[0]=Player.new(trainer[0][1],trainer[0][0])
ret[0].id = trainer[0][2]
ret[0].badges = trainer[0][3]
ret[1] = Player.new(trainer[1][1],trainer[1][0])
ret[1].id = trainer[1][2]
ret[1].badges = trainer[1][3]
return ret
else
ret = Player.new(trainer[0][1],trainer[0][0])
ret.id = trainer[0][2]
ret.badges = trainer[0][3]
return ret
end
end
end
#===============================================================================
#
#===============================================================================
module PokeBattle_BattlePlayerModule
module Commands
Fight = 0
Bag = 1
Pokemon = 2
Run = 3
end
def initialize(scene,battle)
@battletype = battle[0]
@properties = battle[1]
@rounds = battle[2]
@randomnums = battle[3]
@switches = battle[4]
@roundindex = -1
@randomindex = 0
@switchindex = 0
super(scene,
Marshal.restore(StringInput.new(@properties["party1"])),
Marshal.restore(StringInput.new(@properties["party2"])),
BattlePlayerHelper.pbCreateTrainerInfo(@properties["player"]),
BattlePlayerHelper.pbCreateTrainerInfo(@properties["opponent"])
)
end
def pbStartBattle
@party1starts = @properties["party1starts"]
@party2starts = @properties["party2starts"]
@internalBattle = @properties["internalBattle"]
@endSpeeches = @properties["endSpeeches"]
@endSpeechesWin = @properties["endSpeechesWin"]
@field.weather = @properties["weather"]
@field.weatherDuration = @properties["weatherDuration"]
@canRun = @properties["canRun"]
@switchStyle = @properties["switchStyle"]
@showAnims = @properties["showAnims"]
@environment = @properties["environment"]
@items = Marshal.restore(StringInput.new(@properties["items"]))
@rules = Marshal.restore(StringInput.new(@properties["rules"]))
super
end
def pbSwitchInBetween(_idxBattler,_checkLaxOnly=false,_canCancel=false)
ret = @switches[@switchindex]
@switchindex += 1
return ret
end
def pbRandom(_num)
ret = @randomnums[@randomindex]
@randomindex += 1
return ret
end
def pbDisplayPaused(str)
pbDisplay(str)
end
def pbCommandPhaseCore
@roundindex += 1
for i in 0...4
next if @rounds[@roundindex][i].length==0
pbClearChoice(i)
case @rounds[@roundindex][i][0]
when Commands::Fight
if @rounds[@roundindex][i][1]==-1
pbAutoChooseMove(i,false)
else
pbRegisterMove(i,@rounds[@roundindex][i][1])
end
if @rounds[@roundindex][i][2]
pbRegisterTarget(i,@rounds[@roundindex][i][2])
end
when Commands::Bag
pbRegisterItem(i,@rounds[@roundindex][i][1],@rounds[@roundindex][i][2],@rounds[@roundindex][i][3])
when Commands::Pokemon
pbRegisterSwitch(i,@rounds[@roundindex][i][1])
when Commands::Run
@decision = @rounds[@roundindex][i][1]
end
end
end
end
#===============================================================================
#
#===============================================================================
class PokeBattle_RecordedBattle < PokeBattle_Battle
include PokeBattle_RecordedBattleModule
def pbGetBattleType; return 0; end
end
class PokeBattle_RecordedBattlePalace < PokeBattle_BattlePalace
include PokeBattle_RecordedBattleModule
def pbGetBattleType; return 1; end
end
class PokeBattle_RecordedBattleArena < PokeBattle_BattleArena
include PokeBattle_RecordedBattleModule
def pbGetBattleType; return 2; end
end
class PokeBattle_BattlePlayer < PokeBattle_Battle
include PokeBattle_BattlePlayerModule
end
class PokeBattle_BattlePalacePlayer < PokeBattle_BattlePalace
include PokeBattle_BattlePlayerModule
end
class PokeBattle_BattleArenaPlayer < PokeBattle_BattleArena
include PokeBattle_BattlePlayerModule
end

View File

@@ -0,0 +1,84 @@
#===============================================================================
# Used when generating new trainers for battle challenges
#===============================================================================
class PokeBattle_DebugSceneNoLogging
def initialize
@battle = nil
@lastCmd = [0,0,0,0]
@lastMove = [0,0,0,0]
end
# Called whenever the battle begins.
def pbStartBattle(battle)
@battle = battle
@lastCmd = [0,0,0,0]
@lastMove = [0,0,0,0]
end
def pbBlitz(keys)
return rand(30)
end
# Called whenever a new round begins.
def pbBeginCommandPhase; end
def pbBeginAttackPhase; end
def pbShowOpponent(idxTrainer); end
def pbDamageAnimation(battler,effectiveness=0); end
def pbCommonAnimation(animName,user=nil,target=nil); end
def pbAnimation(moveID,user,targets,hitNum=0); end
def pbEndBattle(result); end
def pbWildBattleSuccess; end
def pbTrainerBattleSuccess; end
def pbBattleArenaJudgment(b1,b2,r1,r2); end
def pbBattleArenaBattlers(b1,b2); end
def pbRefresh; end
def pbDisplayMessage(msg,brief=false); end
def pbDisplayPausedMessage(msg); end
def pbDisplayConfirmMessage(msg); return true; end
def pbShowCommands(msg,commands,defaultValue); return 0; end
def pbSendOutBattlers(sendOuts,startBattle=false); end
def pbRecall(idxBattler); end
def pbItemMenu(idxBattler,firstAction); return -1; end
def pbResetMoveIndex(idxBattler); end
def pbHPChanged(battler,oldHP,showAnim=false); end
def pbFaintBattler(battler); end
def pbEXPBar(battler,startExp,endExp,tempExp1,tempExp2); end
def pbLevelUp(pkmn,battler,oldTotalHP,oldAttack,oldDefense,
oldSpAtk,oldSpDef,oldSpeed); end
def pbForgetMove(pkmn,moveToLearn); return 0; end # Always forget first move
def pbCommandMenu(idxBattler,firstAction)
return 1 if rand(15)==0 # Bag
return 4 if rand(10)==0 # Call
return 0 # Fight
end
def pbFightMenu(idxBattler,megaEvoPossible=false)
battler = @battle.battlers[idxBattler]
50.times do
break if yield rand(battler.move.length)
end
end
def pbChooseTarget(idxBattler,target_data,visibleSprites=nil)
targets = []
@battle.eachOtherSideBattler(idxBattler) { |b| targets.push(b.index) }
return -1 if targets.length==0
return targets[rand(targets.length)]
end
def pbPartyScreen(idxBattler,canCancel=false)
replacements = []
@battle.eachInTeamFromBattlerIndex(idxBattler) do |_b,idxParty|
replacements.push(idxParty) if !@battle.pbFindBattler(idxParty,idxBattler)
end
return if replacements.length==0
50.times do
break if yield replacements[rand(replacements.length)],self
end
end
end

View File

@@ -0,0 +1,78 @@
#===============================================================================
#
#===============================================================================
# Unused class.
class PokeBattle_NullBattlePeer
def pbOnEnteringBattle(battle,pkmn,wild=false); end
def pbOnLeavingBattle(battle,pkmn,usedInBattle,endBattle=false); end
def pbStorePokemon(player,pkmn)
player.party[player.party.length] = pkmn if !player.party_full?
return -1
end
def pbGetStorageCreatorName; return nil; end
def pbCurrentBox; return -1; end
def pbBoxName(box); return ""; end
end
#===============================================================================
#
#===============================================================================
class PokeBattle_RealBattlePeer
def pbStorePokemon(player,pkmn)
if !player.party_full?
player.party[player.party.length] = pkmn
return -1
end
pkmn.heal
oldCurBox = pbCurrentBox
storedBox = $PokemonStorage.pbStoreCaught(pkmn)
if storedBox<0
# NOTE: Poké Balls can't be used if storage is full, so you shouldn't ever
# see this message.
pbDisplayPaused(_INTL("Can't catch any more..."))
return oldCurBox
end
return storedBox
end
def pbGetStorageCreatorName
return pbGetStorageCreator if $Trainer.seen_storage_creator
return nil
end
def pbCurrentBox
return $PokemonStorage.currentBox
end
def pbBoxName(box)
return (box<0) ? "" : $PokemonStorage[box].name
end
def pbOnEnteringBattle(_battle,pkmn,wild=false)
f = MultipleForms.call("getFormOnEnteringBattle",pkmn,wild)
pkmn.form = f if f
end
# For switching out, including due to fainting, and for the end of battle
def pbOnLeavingBattle(battle,pkmn,usedInBattle,endBattle=false)
return if !pkmn
f = MultipleForms.call("getFormOnLeavingBattle",pkmn,battle,usedInBattle,endBattle)
pkmn.form = f if f && pkmn.form!=f
pkmn.hp = pkmn.totalhp if pkmn.hp>pkmn.totalhp
end
end
#===============================================================================
#
#===============================================================================
class PokeBattle_BattlePeer
def self.create
return PokeBattle_RealBattlePeer.new
end
end

View File

@@ -0,0 +1,244 @@
#===============================================================================
# This script modifies the battle system to implement battle rules
#===============================================================================
class PokeBattle_Battle
unless @__clauses__aliased
alias __clauses__pbDecisionOnDraw pbDecisionOnDraw
alias __clauses__pbEndOfRoundPhase pbEndOfRoundPhase
@__clauses__aliased = true
end
def pbDecisionOnDraw
if @rules["selfkoclause"]
if self.lastMoveUser<0
# in extreme cases there may be no last move user
return 5 # game is a draw
elsif opposes?(self.lastMoveUser)
return 2 # loss
else
return 1 # win
end
end
return __clauses__pbDecisionOnDraw
end
def pbJudgeCheckpoint(user,move=nil)
if pbAllFainted?(0) && pbAllFainted?(1)
if @rules["drawclause"] # NOTE: Also includes Life Orb (not implemented)
if !(move && move.function=="0DD") # Not a draw if fainting occurred due to Liquid Ooze
@decision = (user.opposes?) ? 1 : 2 # win / loss
end
elsif @rules["modifiedselfdestructclause"]
if move && move.function=="0E0" # Self-Destruct
@decision = (user.opposes?) ? 1 : 2 # win / loss
end
end
end
end
def pbEndOfRoundPhase
__clauses__pbEndOfRoundPhase
if @rules["suddendeath"] && @decision==0
p1able = pbAbleCount(0)
p2able = pbAbleCount(1)
if p1able>p2able; @decision = 1 # loss
elsif p1able<p2able; @decision = 2 # win
end
end
end
end
class PokeBattle_Battler
unless @__clauses__aliased
alias __clauses__pbCanSleep? pbCanSleep?
alias __clauses__pbCanSleepYawn? pbCanSleepYawn?
alias __clauses__pbCanFreeze? pbCanFreeze?
alias __clauses__pbUseMove pbUseMove
@__clauses__aliased = true
end
def pbCanSleep?(user,showMessages,move=nil,ignoreStatus=false)
selfsleep = (user && user.index==@index)
if ((@battle.rules["modifiedsleepclause"]) || (!selfsleep && @battle.rules["sleepclause"])) &&
pbHasStatusPokemon?(:SLEEP)
if showMessages
@battle.pbDisplay(_INTL("But {1} couldn't sleep!",pbThis(true)))
end
return false
end
return __clauses__pbCanSleep?(user,showMessages,move,ignoreStatus)
end
def pbCanSleepYawn?
if (@battle.rules["sleepclause"] || @battle.rules["modifiedsleepclause"]) &&
pbHasStatusPokemon?(:SLEEP)
return false
end
return __clauses__pbCanSleepYawn?
end
def pbCanFreeze?(*arg)
if @battle.rules["freezeclause"] && pbHasStatusPokemon?(:FROZEN)
return false
end
return __clauses__pbCanFreeze?(*arg)
end
def pbHasStatusPokemon?(status)
count = 0
@battle.pbParty(@index).each do |pkmn|
next if !pkmn || pkmn.egg?
next if pkmn.status!=status
count += 1
end
return count>0
end
end
class PokeBattle_Move_022 # Double Team
alias __clauses__pbMoveFailed? pbMoveFailed?
def pbMoveFailed?(user,targets)
if !damagingMove? && @battle.rules["evasionclause"]
@battle.pbDisplay(_INTL("But it failed!"))
return true
end
return __clauses__pbMoveFailed?(user,targets)
end
end
class PokeBattle_Move_034 # Minimize
alias __clauses__pbMoveFailed? pbMoveFailed?
def pbMoveFailed?(user,targets)
if !damagingMove? && @battle.rules["evasionclause"]
@battle.pbDisplay(_INTL("But it failed!"))
return true
end
return __clauses__pbMoveFailed?(user,targets)
end
end
class PokeBattle_Move_067 # Skill Swap
alias __clauses__pbFailsAgainstTarget? pbFailsAgainstTarget?
def pbFailsAgainstTarget?(user,target)
if @battle.rules["skillswapclause"]
@battle.pbDisplay(_INTL("But it failed!"))
return true
end
return __clauses__pbFailsAgainstTarget?(user,target)
end
end
class PokeBattle_Move_06A # Sonic Boom
alias __clauses__pbFailsAgainstTarget? pbFailsAgainstTarget?
def pbFailsAgainstTarget?(user,target)
if @battle.rules["sonicboomclause"]
@battle.pbDisplay(_INTL("But it failed!"))
return true
end
return __clauses__pbFailsAgainstTarget?(user,target)
end
end
class PokeBattle_Move_06B # Dragon Rage
alias __clauses__pbFailsAgainstTarget? pbFailsAgainstTarget?
def pbFailsAgainstTarget?(user,target)
if @battle.rules["sonicboomclause"]
@battle.pbDisplay(_INTL("But it failed!"))
return true
end
return __clauses__pbFailsAgainstTarget?(user,target)
end
end
class PokeBattle_Move_070 # OHKO moves
alias __clauses__pbFailsAgainstTarget? pbFailsAgainstTarget?
def pbFailsAgainstTarget?(user,target)
if @battle.rules["ohkoclause"]
@battle.pbDisplay(_INTL("But it failed!"))
return true
end
return __clauses__pbFailsAgainstTarget?(user,target)
end
end
class PokeBattle_Move_0E0 # Self-Destruct
unless @__clauses__aliased
alias __clauses__pbMoveFailed? pbMoveFailed?
@__clauses__aliased = true
end
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.idxOpposingSide)
if count==0
@battle.pbDisplay("But it failed!")
return false
end
end
if @battle.rules["selfdestructclause"]
# Check whether no unfainted Pokemon remain in either party
count = @battle.pbAbleNonActiveCount(user.idxOwnSide)
count += @battle.pbAbleNonActiveCount(user.idxOpposingSide)
if count==0
@battle.pbDisplay(_INTL("{1}'s team was disqualified!",user.pbThis))
@battle.decision = (user.opposes?) ? 1 : 2
return false
end
end
return __clauses__pbMoveFailed?(user,targets)
end
end
class PokeBattle_Move_0E5 # Perish Song
alias __clauses__pbFailsAgainstTarget? pbFailsAgainstTarget?
def pbFailsAgainstTarget?(user,target)
if @battle.rules["perishsongclause"] &&
@battle.pbAbleNonActiveCount(user.idxOwnSide)==0
@battle.pbDisplay(_INTL("But it failed!"))
return true
end
return __clauses__pbFailsAgainstTarget?(user,target)
end
end
class PokeBattle_Move_0E7 # Destiny Bond
alias __clauses__pbFailsAgainstTarget? pbFailsAgainstTarget?
def pbFailsAgainstTarget?(user,target)
if @battle.rules["perishsongclause"] &&
@battle.pbAbleNonActiveCount(user.idxOwnSide)==0
@battle.pbDisplay(_INTL("But it failed!"))
return true
end
return __clauses__pbFailsAgainstTarget?(user,target)
end
end

View File

@@ -0,0 +1,90 @@
begin
class PokeBattle_ActiveField
attr_accessor :effects
attr_accessor :defaultWeather
attr_accessor :weather
attr_accessor :weatherDuration
attr_accessor :defaultTerrain
attr_accessor :terrain
attr_accessor :terrainDuration
def initialize
@effects = []
@effects[PBEffects::AmuletCoin] = false
@effects[PBEffects::FairyLock] = 0
@effects[PBEffects::FusionBolt] = false
@effects[PBEffects::FusionFlare] = false
@effects[PBEffects::Gravity] = 0
@effects[PBEffects::HappyHour] = false
@effects[PBEffects::IonDeluge] = false
@effects[PBEffects::MagicRoom] = 0
@effects[PBEffects::MudSportField] = 0
@effects[PBEffects::PayDay] = 0
@effects[PBEffects::TrickRoom] = 0
@effects[PBEffects::WaterSportField] = 0
@effects[PBEffects::WonderRoom] = 0
@defaultWeather = :None
@weather = :None
@weatherDuration = 0
@defaultTerrain = :None
@terrain = :None
@terrainDuration = 0
end
end
class PokeBattle_ActiveSide
attr_accessor :effects
def initialize
@effects = []
@effects[PBEffects::AuroraVeil] = 0
@effects[PBEffects::CraftyShield] = false
@effects[PBEffects::EchoedVoiceCounter] = 0
@effects[PBEffects::EchoedVoiceUsed] = false
@effects[PBEffects::LastRoundFainted] = -1
@effects[PBEffects::LightScreen] = 0
@effects[PBEffects::LuckyChant] = 0
@effects[PBEffects::MatBlock] = false
@effects[PBEffects::Mist] = 0
@effects[PBEffects::QuickGuard] = false
@effects[PBEffects::Rainbow] = 0
@effects[PBEffects::Reflect] = 0
@effects[PBEffects::Round] = false
@effects[PBEffects::Safeguard] = 0
@effects[PBEffects::SeaOfFire] = 0
@effects[PBEffects::Spikes] = 0
@effects[PBEffects::StealthRock] = false
@effects[PBEffects::StickyWeb] = false
@effects[PBEffects::Swamp] = 0
@effects[PBEffects::Tailwind] = 0
@effects[PBEffects::ToxicSpikes] = 0
@effects[PBEffects::WideGuard] = false
end
end
class PokeBattle_ActivePosition
attr_accessor :effects
def initialize
@effects = []
@effects[PBEffects::FutureSightCounter] = 0
@effects[PBEffects::FutureSightMove] = nil
@effects[PBEffects::FutureSightUserIndex] = -1
@effects[PBEffects::FutureSightUserPartyIndex] = -1
@effects[PBEffects::HealingWish] = false
@effects[PBEffects::LunarDance] = false
@effects[PBEffects::Wish] = 0
@effects[PBEffects::WishAmount] = 0
@effects[PBEffects::WishMaker] = -1
end
end
rescue Exception
if $!.is_a?(SystemExit) || "#{$!.class}"=="Reset"
raise $!
end
end

View File

@@ -0,0 +1,84 @@
class PokeBattle_DamageState
attr_accessor :initialHP
attr_accessor :typeMod # Type effectiveness
attr_accessor :unaffected
attr_accessor :protected
attr_accessor :magicCoat
attr_accessor :magicBounce
attr_accessor :totalHPLost # Like hpLost, but cumulative over all hits
attr_accessor :fainted # Whether battler was knocked out by the move
attr_accessor :missed # Whether the move failed the accuracy check
attr_accessor :calcDamage # Calculated damage
attr_accessor :hpLost # HP lost by opponent, inc. HP lost by a substitute
attr_accessor :critical # Critical hit flag
attr_accessor :substitute # Whether a substitute took the damage
attr_accessor :focusBand # Focus Band used
attr_accessor :focusSash # Focus Sash used
attr_accessor :sturdy # Sturdy ability used
attr_accessor :disguise # Disguise ability used
attr_accessor :endured # Damage was endured
attr_accessor :berryWeakened # Whether a type-resisting berry was used
def initialize; reset; end
def reset
@initialHP = 0
@typeMod = Effectiveness::INEFFECTIVE
@unaffected = false
@protected = false
@magicCoat = false
@magicBounce = false
@totalHPLost = 0
@fainted = false
resetPerHit
end
def resetPerHit
@missed = false
@calcDamage = 0
@hpLost = 0
@critical = false
@substitute = false
@focusBand = false
@focusSash = false
@sturdy = false
@disguise = false
@endured = false
@berryWeakened = false
end
end
################################################################################
# Success state (used for Battle Arena)
################################################################################
class PokeBattle_SuccessState
attr_accessor :typeMod
attr_accessor :useState # 0 - not used, 1 - failed, 2 - succeeded
attr_accessor :protected
attr_accessor :skill
def initialize; clear; end
def clear(full=true)
@typeMod = Effectiveness::NORMAL_EFFECTIVE
@useState = 0
@protected = false
@skill = 0 if full
end
def updateSkill
if @useState==1
@skill = -2 if !@protected
elsif @useState==2
if Effectiveness.super_effective?(@typeMod); @skill = 2
elsif Effectiveness.normal?(@typeMod); @skill = 1
elsif Effectiveness.not_very_effective?(@typeMod); @skill = -1
else; @skill = -2 # Ineffective
end
end
clear(false)
end
end