mirror of
https://github.com/infinitefusion/infinitefusion-e18.git
synced 2025-12-06 06:01:46 +00:00
854 lines
32 KiB
Ruby
854 lines
32 KiB
Ruby
# 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 Battle
|
|
# def setBattleMode
|
|
# def pbGetOwnerIndexFromBattlerIndex
|
|
# def pbGetOpposingIndicesInOrder
|
|
# def nearBattlers?
|
|
# def pbStartBattleSendOut
|
|
# def pbEORShiftDistantBattlers
|
|
# def pbCanShift?
|
|
# def pbEndOfRoundPhase
|
|
# class Battle::Scene::TargetMenu
|
|
# def initialize
|
|
# class Battle::Scene::PokemonDataBox
|
|
# def initializeDataBoxGraphic
|
|
# class Battle::Scene
|
|
# def self.pbBattlerPosition
|
|
# def self.pbTrainerPosition
|
|
# class Game_Temp
|
|
# def add_battle_rule
|
|
# (There is no guarantee that this list is complete.)
|
|
|
|
class 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 :corrosiveGas
|
|
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_accessor :first_poke_ball # ID of the first thrown Poké Ball that failed
|
|
attr_accessor :poke_ball_failed # Set after first_poke_ball to prevent it being set again
|
|
attr_reader :switching # True if during the switching phase of the round
|
|
attr_reader :futureSight # True if Future Sight is hitting
|
|
attr_reader :endOfRound # True during the end of round
|
|
attr_accessor :moldBreaker # True if Mold Breaker applies
|
|
attr_reader :struggle # The Struggle move
|
|
|
|
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 = Peer.new
|
|
@battleAI = AI.new(self)
|
|
@field = ActiveField.new # Whole field (gravity/rooms)
|
|
@sides = [ActiveSide.new, # Player's side
|
|
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)]
|
|
@corrosiveGas = [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 = Move.from_pokemon_move(self, Pokemon::Move.new(:STRUGGLE))
|
|
else
|
|
@struggle = Move::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)
|
|
trainer = (opposes?(idxBattler)) ? @opponent : @player
|
|
return (trainer.nil?) ? nil : trainer[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)
|
|
trainer = (opposes?(idxBattler)) ? @opponent : @player
|
|
return (trainer.nil?) ? nil : trainer[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
|
|
pbSideSize(side).times do |i|
|
|
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&.able? }
|
|
return count
|
|
end
|
|
|
|
def pbAbleNonActiveCount(idxBattler = 0)
|
|
party = pbParty(idxBattler)
|
|
inBattleIndices = allSameSideBattlers(idxBattler).map { |b| 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
|
|
|
|
def pbTeamAbleNonActiveCount(idxBattler = 0)
|
|
inBattleIndices = allSameSideBattlers(idxBattler).map { |b| b.pokemonIndex }
|
|
count = 0
|
|
eachInTeamFromBattlerIndex(idxBattler) do |pkmn, i|
|
|
next if !pkmn || !pkmn.able?
|
|
next if inBattleIndices.include?(i)
|
|
count += 1
|
|
end
|
|
return count
|
|
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 ret < 0 || 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
|
|
#=============================================================================
|
|
# Unused
|
|
def eachBattler
|
|
@battlers.each { |b| yield b if b && !b.fainted? }
|
|
end
|
|
|
|
def allBattlers
|
|
return @battlers.select { |b| b && !b.fainted? }
|
|
end
|
|
|
|
# Unused
|
|
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 allSameSideBattlers(idxBattler = 0)
|
|
idxBattler = idxBattler.index if idxBattler.respond_to?("index")
|
|
return @battlers.select { |b| b && !b.fainted? && !b.opposes?(idxBattler) }
|
|
end
|
|
|
|
# Unused
|
|
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 allOtherSideBattlers(idxBattler = 0)
|
|
idxBattler = idxBattler.index if idxBattler.respond_to?("index")
|
|
return @battlers.select { |b| b && !b.fainted? && b.opposes?(idxBattler) }
|
|
end
|
|
|
|
def pbSideBattlerCount(idxBattler = 0)
|
|
return allSameSideBattlers(idxBattler).length
|
|
end
|
|
|
|
def pbOpposingBattlerCount(idxBattler = 0)
|
|
return allOtherSideBattlers(idxBattler).length
|
|
end
|
|
|
|
# This method only counts the player's Pokémon, not a partner trainer's.
|
|
def pbPlayerBattlerCount
|
|
return allSameSideBattlers(idxBattler).select { |b| b.pbOwnedByPlayer? }.length
|
|
end
|
|
|
|
def pbCheckGlobalAbility(abil)
|
|
allBattlers.each { |b| return b if b.hasActiveAbility?(abil) }
|
|
return nil
|
|
end
|
|
|
|
def pbCheckOpposingAbility(abil, idxBattler = 0, nearOnly = false)
|
|
allOtherSideBattlers(idxBattler).each 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::JawLock,
|
|
PBEffects::LockOnPos,
|
|
PBEffects::MeanLook,
|
|
PBEffects::MirrorCoatTarget,
|
|
PBEffects::Octolock,
|
|
PBEffects::SkyDrop,
|
|
PBEffects::TrappingUser]
|
|
allBattlers.each do |b|
|
|
effectsToSwap.each do |i|
|
|
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)
|
|
allSameSideBattlers(idxBattlerOther).each { |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
|
|
if battler.is_a?(Battler)
|
|
pbPlayer.pokedex.register(battler.displaySpecies, battler.displayGender,
|
|
battler.displayForm, battler.shiny?)
|
|
else
|
|
pbPlayer.pokedex.register(battler)
|
|
end
|
|
end
|
|
|
|
def pbSetCaught(battler)
|
|
return if !battler || !@internalBattle
|
|
if battler.is_a?(Battler)
|
|
pbPlayer.pokedex.register_caught(battler.displaySpecies)
|
|
else
|
|
pbPlayer.pokedex.register_caught(battler.species)
|
|
end
|
|
end
|
|
|
|
def pbSetDefeated(battler)
|
|
return if !battler || !@internalBattle
|
|
if battler.is_a?(Battler)
|
|
pbPlayer.pokedex.register_defeated(battler.displaySpecies)
|
|
else
|
|
pbPlayer.pokedex.register_defeated(battler.species)
|
|
end
|
|
end
|
|
|
|
def nextPickupUse
|
|
@nextPickupUse += 1
|
|
return @nextPickupUse
|
|
end
|
|
|
|
#=============================================================================
|
|
# Weather
|
|
#=============================================================================
|
|
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
|
|
return :None if allBattlers.any? { |b| 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 = Battle::ItemEffects.triggerWeatherExtender(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
|
|
allBattlers.each { |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
|
|
allBattlers.each { |b| b.pbCheckFormOnWeatherChange }
|
|
# Start up the default weather
|
|
pbStartWeather(nil, @field.defaultWeather) if @field.defaultWeather != :None
|
|
end
|
|
end
|
|
|
|
def pbStartWeatherAbility(new_weather, battler, ignore_primal = false)
|
|
return if !ignore_primal && [:HarshSun, :HeavyRain, :StrongWinds].include?(@field.weather)
|
|
return if @field.weather == new_weather
|
|
pbShowAbilitySplash(battler)
|
|
if !Scene::USE_ABILITY_SPLASH
|
|
pbDisplay(_INTL("{1}'s {2} activated!", battler.pbThis, battler.abilityName))
|
|
end
|
|
fixed_duration = false
|
|
fixed_duration = true if Settings::FIXED_DURATION_WEATHER_FROM_ABILITY &&
|
|
![:HarshSun, :HeavyRain, :StrongWinds].include?(new_weather)
|
|
pbStartWeather(battler, new_weather, fixed_duration)
|
|
# NOTE: The ability splash is hidden again in def pbStartWeather.
|
|
end
|
|
|
|
#=============================================================================
|
|
# Terrain
|
|
#=============================================================================
|
|
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 = Battle::ItemEffects.triggerTerrainExtender(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 abilities/items that trigger upon the terrain changing
|
|
allBattlers.each { |b| b.pbAbilityOnTerrainChange }
|
|
allBattlers.each { |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 !Scene::USE_ABILITY_SPLASH
|
|
@scene.pbShowAbilitySplash(battler)
|
|
if delay
|
|
Graphics.frame_rate.times { @scene.pbUpdate } # 1 second
|
|
end
|
|
end
|
|
|
|
def pbHideAbilitySplash(battler)
|
|
return if !Scene::USE_ABILITY_SPLASH
|
|
@scene.pbHideAbilitySplash(battler)
|
|
end
|
|
|
|
def pbReplaceAbilitySplash(battler)
|
|
return if !Scene::USE_ABILITY_SPLASH
|
|
@scene.pbReplaceAbilitySplash(battler)
|
|
end
|
|
end
|