mirror of
https://github.com/infinitefusion/infinitefusion-e18.git
synced 2025-12-06 06:01:46 +00:00
Yet more script rearranging
This commit is contained in:
698
Data/Scripts/011_Battle/001_Battler/001_PokeBattle_Battler.rb
Normal file
698
Data/Scripts/011_Battle/001_Battler/001_PokeBattle_Battler.rb
Normal 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
|
||||
328
Data/Scripts/011_Battle/001_Battler/002_Battler_Initialize.rb
Normal file
328
Data/Scripts/011_Battle/001_Battler/002_Battler_Initialize.rb
Normal 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
|
||||
297
Data/Scripts/011_Battle/001_Battler/003_Battler_ChangeSelf.rb
Normal file
297
Data/Scripts/011_Battle/001_Battler/003_Battler_ChangeSelf.rb
Normal 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
|
||||
576
Data/Scripts/011_Battle/001_Battler/004_Battler_Statuses.rb
Normal file
576
Data/Scripts/011_Battle/001_Battler/004_Battler_Statuses.rb
Normal 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
|
||||
308
Data/Scripts/011_Battle/001_Battler/005_Battler_StatStages.rb
Normal file
308
Data/Scripts/011_Battle/001_Battler/005_Battler_StatStages.rb
Normal 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
|
||||
@@ -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
|
||||
728
Data/Scripts/011_Battle/001_Battler/007_Battler_UseMove.rb
Normal file
728
Data/Scripts/011_Battle/001_Battler/007_Battler_UseMove.rb
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
181
Data/Scripts/011_Battle/001_PBEffects.rb
Normal file
181
Data/Scripts/011_Battle/001_PBEffects.rb
Normal 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
|
||||
601
Data/Scripts/011_Battle/002_BattleHandlers.rb
Normal file
601
Data/Scripts/011_Battle/002_BattleHandlers.rb
Normal 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
|
||||
141
Data/Scripts/011_Battle/002_Move/001_PokeBattle_Move.rb
Normal file
141
Data/Scripts/011_Battle/002_Move/001_PokeBattle_Move.rb
Normal 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
|
||||
350
Data/Scripts/011_Battle/002_Move/002_Move_Usage.rb
Normal file
350
Data/Scripts/011_Battle/002_Move/002_Move_Usage.rb
Normal 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
|
||||
491
Data/Scripts/011_Battle/002_Move/003_Move_Usage_Calculations.rb
Normal file
491
Data/Scripts/011_Battle/002_Move/003_Move_Usage_Calculations.rb
Normal 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
|
||||
716
Data/Scripts/011_Battle/002_Move/004_Move_Effects_Generic.rb
Normal file
716
Data/Scripts/011_Battle/002_Move/004_Move_Effects_Generic.rb
Normal 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
|
||||
2714
Data/Scripts/011_Battle/002_Move/005_Move_Effects_000-07F.rb
Normal file
2714
Data/Scripts/011_Battle/002_Move/005_Move_Effects_000-07F.rb
Normal file
File diff suppressed because it is too large
Load Diff
3719
Data/Scripts/011_Battle/002_Move/006_Move_Effects_080-0FF.rb
Normal file
3719
Data/Scripts/011_Battle/002_Move/006_Move_Effects_080-0FF.rb
Normal file
File diff suppressed because it is too large
Load Diff
2604
Data/Scripts/011_Battle/002_Move/007_Move_Effects_100-17F.rb
Normal file
2604
Data/Scripts/011_Battle/002_Move/007_Move_Effects_100-17F.rb
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
785
Data/Scripts/011_Battle/003_Battle/002_PokeBattle_Battle.rb
Normal file
785
Data/Scripts/011_Battle/003_Battle/002_PokeBattle_Battle.rb
Normal 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
|
||||
539
Data/Scripts/011_Battle/003_Battle/003_Battle_StartAndEnd.rb
Normal file
539
Data/Scripts/011_Battle/003_Battle/003_Battle_StartAndEnd.rb
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
148
Data/Scripts/011_Battle/003_Battle/007_Battle_Action_UseItem.rb
Normal file
148
Data/Scripts/011_Battle/003_Battle/007_Battle_Action_UseItem.rb
Normal 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
|
||||
151
Data/Scripts/011_Battle/003_Battle/008_Battle_Action_Running.rb
Normal file
151
Data/Scripts/011_Battle/003_Battle/008_Battle_Action_Running.rb
Normal 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
|
||||
189
Data/Scripts/011_Battle/003_Battle/009_Battle_Action_Other.rb
Normal file
189
Data/Scripts/011_Battle/003_Battle/009_Battle_Action_Other.rb
Normal 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
|
||||
250
Data/Scripts/011_Battle/003_Battle/010_Battle_Phase_Command.rb
Normal file
250
Data/Scripts/011_Battle/003_Battle/010_Battle_Phase_Command.rb
Normal 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
|
||||
190
Data/Scripts/011_Battle/003_Battle/011_Battle_Phase_Attack.rb
Normal file
190
Data/Scripts/011_Battle/003_Battle/011_Battle_Phase_Attack.rb
Normal 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
|
||||
@@ -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
|
||||
2425
Data/Scripts/011_Battle/003_BattleHandlers_Abilities.rb
Normal file
2425
Data/Scripts/011_Battle/003_BattleHandlers_Abilities.rb
Normal file
File diff suppressed because it is too large
Load Diff
69
Data/Scripts/011_Battle/004_AI/001_PokeBattle_AI.rb
Normal file
69
Data/Scripts/011_Battle/004_AI/001_PokeBattle_AI.rb
Normal 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
|
||||
171
Data/Scripts/011_Battle/004_AI/002_AI_Item.rb
Normal file
171
Data/Scripts/011_Battle/004_AI/002_AI_Item.rb
Normal 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
|
||||
178
Data/Scripts/011_Battle/004_AI/003_AI_Switch.rb
Normal file
178
Data/Scripts/011_Battle/004_AI/003_AI_Switch.rb
Normal 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
|
||||
291
Data/Scripts/011_Battle/004_AI/004_AI_Move.rb
Normal file
291
Data/Scripts/011_Battle/004_AI/004_AI_Move.rb
Normal 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
|
||||
3028
Data/Scripts/011_Battle/004_AI/005_AI_Move_EffectScores.rb
Normal file
3028
Data/Scripts/011_Battle/004_AI/005_AI_Move_EffectScores.rb
Normal file
File diff suppressed because it is too large
Load Diff
669
Data/Scripts/011_Battle/004_AI/006_AI_Move_Utilities.rb
Normal file
669
Data/Scripts/011_Battle/004_AI/006_AI_Move_Utilities.rb
Normal 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
|
||||
1587
Data/Scripts/011_Battle/004_BattleHandlers_Items.rb
Normal file
1587
Data/Scripts/011_Battle/004_BattleHandlers_Items.rb
Normal file
File diff suppressed because it is too large
Load Diff
236
Data/Scripts/011_Battle/005_BallHandlers_PokeBallEffects.rb
Normal file
236
Data/Scripts/011_Battle/005_BallHandlers_PokeBallEffects.rb
Normal 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
|
||||
})
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
349
Data/Scripts/011_Battle/005_Battle scene/006_PokeBattle_Scene.rb
Normal file
349
Data/Scripts/011_Battle/005_Battle scene/006_PokeBattle_Scene.rb
Normal 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
|
||||
191
Data/Scripts/011_Battle/005_Battle scene/007_Scene_Initialize.rb
Normal file
191
Data/Scripts/011_Battle/005_Battle scene/007_Scene_Initialize.rb
Normal 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
|
||||
470
Data/Scripts/011_Battle/005_Battle scene/008_Scene_Commands.rb
Normal file
470
Data/Scripts/011_Battle/005_Battle scene/008_Scene_Commands.rb
Normal 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
|
||||
539
Data/Scripts/011_Battle/005_Battle scene/009_Scene_Animations.rb
Normal file
539
Data/Scripts/011_Battle/005_Battle scene/009_Scene_Animations.rb
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
90
Data/Scripts/011_Battle/006_PokeBattle_ActiveField.rb
Normal file
90
Data/Scripts/011_Battle/006_PokeBattle_ActiveField.rb
Normal 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
|
||||
84
Data/Scripts/011_Battle/007_PokeBattle_DamageState.rb
Normal file
84
Data/Scripts/011_Battle/007_PokeBattle_DamageState.rb
Normal 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
|
||||
Reference in New Issue
Block a user