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

560 lines
22 KiB
Ruby

class 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 both
# sides.
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
2.times do |side| # 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
@sideSizes[side].times do |i|
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
case side
when 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])
when 1
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])
end
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
2.times do |side|
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] = Battler.new(self, idxBattler)
@positions[idxBattler] = ActivePosition.new
pbClearChoice(idxBattler)
@successStates[idxBattler] = SuccessState.new
@battlers[idxBattler].pbInitialize(pkmn, idxParty)
end
def pbSetUpSides
ret = [[], []]
2.times do |side|
# 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, @battlers[(2 * idxPkmn) + side], 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
@sideSizes[side].times do |i|
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
partyOrder = pbPartyOrder(side)
starts = pbPartyStarts(side)
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)
if idxPkmn != starts[idxTrainer] + battlerNumber
idxOther = starts[idxTrainer] + battlerNumber
partyOrder[idxPkmn], partyOrder[idxOther] = partyOrder[idxOther], partyOrder[idxPkmn]
end
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)
[1, 0].each do |side|
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
pbParty(0).each { |pkmn| @peer.pbOnStartingBattle(self, pkmn, wildBattle?) if pkmn }
pbParty(1).each { |pkmn| @peer.pbOnStartingBattle(self, pkmn, wildBattle?) if pkmn }
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
pbOnAllBattlersEnteringBattle
# 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
$stats.battle_money_gained += moneyGained
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
$stats.battle_money_gained += moneyGained
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
$stats.battle_money_lost += moneyLost
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 |trainer, i|
@scene.pbShowOpponent(i)
msg = trainer.lose_text
msg = "..." if !msg || msg.empty?
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 # Lost in a Battle Frontier battle
if @opponent
@opponent.each_with_index do |trainer, i|
@scene.pbShowOpponent(i)
msg = trainer.win_text
msg = "..." if !msg || msg.empty?
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 = []
$player.party.each_with_index do |pkmn, i|
infected.push(i) if pkmn.pokerusStage == 1
end
infected.each do |idxParty|
strain = $player.party[idxParty].pokerusStrain
if idxParty > 0 && $player.party[idxParty - 1].pokerusStage == 0 && rand(3) == 0 # 33%
$player.party[idxParty - 1].givePokerus(strain)
end
if idxParty < $player.party.length - 1 && $player.party[idxParty + 1].pokerusStage == 0 && rand(3) == 0 # 33%
$player.party[idxParty + 1].givePokerus(strain)
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
Battle::AbilityEffects.triggerOnSwitchOut(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]
2.times do |side|
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]
2.times do |side|
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