mirror of
https://github.com/infinitefusion/infinitefusion-e18.git
synced 2025-12-07 13:15:01 +00:00
470 lines
20 KiB
Ruby
470 lines
20 KiB
Ruby
class 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!"))
|
|
return false
|
|
end
|
|
if !pbIsOwner?(idxBattler, idxParty)
|
|
if partyScene
|
|
owner = pbGetOwnerFromPartyIndex(idxBattler, idxParty)
|
|
partyScene.pbDisplay(_INTL("You can't switch {1}'s Pokémon with one of yours!",
|
|
owner.name))
|
|
end
|
|
return false
|
|
end
|
|
if party[idxParty].fainted?
|
|
partyScene&.pbDisplay(_INTL("{1} has no energy left to battle!", party[idxParty].name))
|
|
return false
|
|
end
|
|
if pbFindBattler(idxParty, idxBattler)
|
|
partyScene&.pbDisplay(_INTL("{1} is already in battle!", party[idxParty].name))
|
|
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
|
|
allSameSideBattlers(idxBattler).each 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))
|
|
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? &&
|
|
Battle::AbilityEffects.triggerCertainSwitching(battler.ability, battler, self)
|
|
return true
|
|
end
|
|
if battler.itemActive? &&
|
|
Battle::ItemEffects.triggerCertainSwitching(battler.item, battler, self)
|
|
return true
|
|
end
|
|
# Other certain switching effects
|
|
return true if Settings::MORE_TYPE_EFFECTS && battler.pbHasType?(:GHOST)
|
|
# Other certain trapping effects
|
|
if battler.trappedInBattle?
|
|
partyScene&.pbDisplay(_INTL("{1} can't be switched out!", battler.pbThis))
|
|
return false
|
|
end
|
|
# Trapping abilities/items
|
|
allOtherSideBattlers(idxBattler).each do |b|
|
|
next if !b.abilityActive?
|
|
if Battle::AbilityEffects.triggerTrappingByTarget(b.ability, battler, b, self)
|
|
partyScene&.pbDisplay(_INTL("{1}'s {2} prevents switching!",
|
|
b.pbThis, b.abilityName))
|
|
return false
|
|
end
|
|
end
|
|
allOtherSideBattlers(idxBattler).each do |b|
|
|
next if !b.itemActive?
|
|
if Battle::ItemEffects.triggerTrappingByTarget(b.item, battler, b, self)
|
|
partyScene&.pbDisplay(_INTL("{1}'s {2} prevents switching!",
|
|
b.pbThis, b.itemName))
|
|
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)
|
|
elsif !pbCanSwitch?(idxBattler, idxParty, partyScene)
|
|
next false
|
|
end
|
|
if shouldRegister && (idxParty < 0 || !pbRegisterSwitch(idxBattler, idxParty))
|
|
next false
|
|
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 pbEORSwitch).
|
|
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 b.wild? # 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 && !pbCheckGlobalAbility(:NEUTRALIZINGGAS)
|
|
new_index = pbLastInTeam(idxBattler)
|
|
idxPartyForName = new_index if new_index >= 0 && new_index != idxPartyNew
|
|
end
|
|
if pbDisplayConfirm(_INTL("{1} is about to send out {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 = true
|
|
else
|
|
switch = (pbRun(idxBattler, true) <= 0)
|
|
end
|
|
if switch
|
|
idxPlayerPartyNew = pbGetReplacementPokemonIndex(idxBattler) # Owner chooses
|
|
pbRecallAndReplace(idxBattler, idxPlayerPartyNew)
|
|
switched.push(idxBattler)
|
|
end
|
|
end
|
|
end
|
|
break if switched.length == 0
|
|
pbOnBattlerEnteringBattle(switched)
|
|
end
|
|
end
|
|
|
|
def pbGetReplacementPokemonIndex(idxBattler, random = false)
|
|
if random
|
|
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 && !pbCheckGlobalAbility(:NEUTRALIZINGGAS)
|
|
new_index = pbLastInTeam(idxBattler)
|
|
newPkmnName = party[new_index].name if new_index >= 0 && new_index != idxParty
|
|
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, @battlers[b[0]], 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 pbOnAllBattlersEnteringBattle
|
|
pbCalculatePriority(true)
|
|
battler_indices = []
|
|
allBattlers.each { |b| battler_indices.push(b.index) }
|
|
pbOnBattlerEnteringBattle(battler_indices)
|
|
pbCalculatePriority
|
|
# Check forms are correct
|
|
allBattlers.each { |b| b.pbCheckForm }
|
|
end
|
|
|
|
# Called when one or more Pokémon switch in. Does a lot of things, including
|
|
# entry hazards, form changes and items/abilities that trigger upon switching
|
|
# in.
|
|
def pbOnBattlerEnteringBattle(battler_index, skip_event_reset = false)
|
|
battler_index = [battler_index] if !battler_index.is_a?(Array)
|
|
battler_index.flatten!
|
|
# NOTE: This isn't done for switch commands, because they previously call
|
|
# pbRecallAndReplace, which could cause Neutralizing Gas to end, which
|
|
# in turn could cause Intimidate to trigger another Pokémon's Eject
|
|
# Pack. That Eject Pack should trigger at the end of this method, but
|
|
# this resetting would prevent that from happening, so it is skipped
|
|
# and instead done earlier in def pbAttackPhaseSwitch.
|
|
if !skip_event_reset
|
|
allBattlers.each do |b|
|
|
b.droppedBelowHalfHP = false
|
|
b.statsDropped = false
|
|
end
|
|
end
|
|
# For each battler that entered battle, in speed order
|
|
pbPriority(true).each do |b|
|
|
next if !battler_index.include?(b.index) || b.fainted?
|
|
pbRecordBattlerAsParticipated(b)
|
|
pbMessagesOnBattlerEnteringBattle(b)
|
|
# Position/field effects triggered by the battler appearing
|
|
pbEffectsOnBattlerEnteringPosition(b) # Healing Wish/Lunar Dance
|
|
pbEntryHazards(b)
|
|
# Battler faints if it is knocked out because of an entry hazard above
|
|
if b.fainted?
|
|
b.pbFaint
|
|
pbGainExp
|
|
pbJudge
|
|
next
|
|
end
|
|
b.pbCheckForm
|
|
# Primal Revert upon entering battle
|
|
pbPrimalReversion(b.index)
|
|
# Ending primordial weather, checking Trace
|
|
b.pbContinualAbilityChecks(true)
|
|
# Abilities that trigger upon switching in
|
|
if (!b.fainted? && b.unstoppableAbility?) || b.abilityActive?
|
|
Battle::AbilityEffects.triggerOnSwitchIn(b.ability, b, self, true)
|
|
end
|
|
pbEndPrimordialWeather # Checking this again just in case
|
|
# Items that trigger upon switching in (Air Balloon message)
|
|
if b.itemActive?
|
|
Battle::ItemEffects.triggerOnSwitchIn(b.item, b, self)
|
|
end
|
|
# Berry check, status-curing ability check
|
|
b.pbHeldItemTriggerCheck
|
|
b.pbAbilityStatusCureCheck
|
|
end
|
|
# Check for triggering of Emergency Exit/Wimp Out/Eject Pack (only one will
|
|
# be triggered)
|
|
pbPriority(true).each do |b|
|
|
break if b.pbItemOnStatDropped
|
|
break if b.pbAbilitiesOnDamageTaken
|
|
end
|
|
allBattlers.each do |b|
|
|
b.droppedBelowHalfHP = false
|
|
b.statsDropped = false
|
|
end
|
|
end
|
|
|
|
def pbRecordBattlerAsParticipated(battler)
|
|
# 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)
|
|
allBattlers.each { |b| b.pbUpdateParticipants }
|
|
end
|
|
|
|
def pbMessagesOnBattlerEnteringBattle(battler)
|
|
# Introduce Shadow Pokémon
|
|
if battler.shadowPokemon?
|
|
pbCommonAnimation("Shadow", battler)
|
|
pbDisplay(_INTL("Oh!\nA Shadow Pokémon!")) if battler.opposes?
|
|
end
|
|
end
|
|
|
|
# Called when a Pokémon enters battle, and when Ally Switch is used.
|
|
def pbEffectsOnBattlerEnteringPosition(battler)
|
|
position = @positions[battler.index]
|
|
# Healing Wish
|
|
if position.effects[PBEffects::HealingWish]
|
|
if battler.canHeal? || battler.status != :NONE
|
|
pbCommonAnimation("HealingWish", battler)
|
|
pbDisplay(_INTL("The healing wish came true for {1}!", battler.pbThis(true)))
|
|
battler.pbRecoverHP(battler.totalhp)
|
|
battler.pbCureStatus(false)
|
|
position.effects[PBEffects::HealingWish] = false
|
|
elsif Settings::MECHANICS_GENERATION < 8
|
|
position.effects[PBEffects::HealingWish] = false
|
|
end
|
|
end
|
|
# Lunar Dance
|
|
if position.effects[PBEffects::LunarDance]
|
|
full_pp = true
|
|
battler.eachMove { |m| full_pp = false if m.pp < m.total_pp }
|
|
if battler.canHeal? || battler.status != :NONE || !full_pp
|
|
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 }
|
|
position.effects[PBEffects::LunarDance] = false
|
|
elsif Settings::MECHANICS_GENERATION < 8
|
|
position.effects[PBEffects::LunarDance] = false
|
|
end
|
|
end
|
|
end
|
|
|
|
def pbEntryHazards(battler)
|
|
battler_side = battler.pbOwnSide
|
|
# Stealth Rock
|
|
if battler_side.effects[PBEffects::StealthRock] && battler.takesIndirectDamage? &&
|
|
GameData::Type.exists?(:ROCK) && !battler.hasActiveItem?(:HEAVYDUTYBOOTS)
|
|
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
|
|
battler.pbReduceHP(battler.totalhp * eff / 8, false)
|
|
pbDisplay(_INTL("Pointed stones dug into {1}!", battler.pbThis))
|
|
battler.pbItemHPHealCheck
|
|
end
|
|
end
|
|
# Spikes
|
|
if battler_side.effects[PBEffects::Spikes] > 0 && battler.takesIndirectDamage? &&
|
|
!battler.airborne? && !battler.hasActiveItem?(:HEAVYDUTYBOOTS)
|
|
spikesDiv = [8, 6, 4][battler_side.effects[PBEffects::Spikes] - 1]
|
|
battler.pbReduceHP(battler.totalhp / spikesDiv, false)
|
|
pbDisplay(_INTL("{1} is hurt by the spikes!", battler.pbThis))
|
|
battler.pbItemHPHealCheck
|
|
end
|
|
# Toxic Spikes
|
|
if battler_side.effects[PBEffects::ToxicSpikes] > 0 && !battler.fainted? && !battler.airborne?
|
|
if battler.pbHasType?(:POISON)
|
|
battler_side.effects[PBEffects::ToxicSpikes] = 0
|
|
pbDisplay(_INTL("{1} absorbed the poison spikes!", battler.pbThis))
|
|
elsif battler.pbCanPoison?(nil, false) && !battler.hasActiveItem?(:HEAVYDUTYBOOTS)
|
|
if battler_side.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_side.effects[PBEffects::StickyWeb] && !battler.fainted? && !battler.airborne? &&
|
|
!battler.hasActiveItem?(:HEAVYDUTYBOOTS)
|
|
pbDisplay(_INTL("{1} was caught in a sticky web!", battler.pbThis))
|
|
if battler.pbCanLowerStatStage?(:SPEED)
|
|
battler.pbLowerStatStage(:SPEED, 1, nil)
|
|
battler.pbItemStatRestoreCheck
|
|
end
|
|
end
|
|
end
|
|
end
|