# 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