#=============================================================================== # Simple battler class for the wild Pokémon in a Safari Zone battle #=============================================================================== class PokeBattle_FakeBattler attr_reader :battle attr_reader :index attr_reader :pokemon attr_reader :owned def initialize(battle,index) @battle = battle @pokemon = battle.party2[0] @index = index end def pokemonIndex; return 0; end def species; return @pokemon.species; end def gender; return @pokemon.gender; end def status; return @pokemon.status; end def hp; return @pokemon.hp; end def level; return @pokemon.level; end def name; return @pokemon.name; end def totalhp; return @pokemon.totalhp; end def displayGender; return @pokemon.gender; end def shiny?; return @pokemon.shiny?; end def square_shiny?; return @pokemon.square_shiny?; end alias isShiny? shiny? def isSpecies?(check_species) return @pokemon && @pokemon.isSpecies?(check_species) end def fainted?; return false; end alias isFainted? fainted? def shadowPokemon?; return false; end alias isShadow? shadowPokemon? def hasMega?; return false; end def mega?; return false; end alias isMega? mega? def hasPrimal?; return false; end def primal?; return false; end alias isPrimal? primal? def captured; return false; end def captured=(value); end def owned? return $Trainer.owned?(pokemon.species) end def pbThis(lowerCase=false) return (lowerCase) ? _INTL("the wild {1}",name) : _INTL("The wild {1}",name) end def opposes?(i) i = i.index if i.is_a?(PokeBattle_FakeBattler) return (@index&1)!=(i&1) end def pbReset; end end #=============================================================================== # Data box for safari battles #=============================================================================== class SafariDataBox < SpriteWrapper attr_accessor :selected def initialize(battle,viewport=nil) super(viewport) @selected = 0 @battle = battle @databox = AnimatedBitmap.new("Graphics/Pictures/Battle/databox_safari") self.x = Graphics.width - 232 self.y = Graphics.height - 184 @contents = BitmapWrapper.new(@databox.width,@databox.height) self.bitmap = @contents self.visible = false self.z = 50 pbSetSystemFont(self.bitmap) refresh end def refresh self.bitmap.clear self.bitmap.blt(0,0,@databox.bitmap,Rect.new(0,0,@databox.width,@databox.height)) base = Color.new(72,72,72) shadow = Color.new(184,184,184) textpos = [] textpos.push([_INTL("Safari Balls"),30,2,false,base,shadow]) textpos.push([_INTL("Left: {1}",@battle.ballCount),30,32,false,base,shadow]) pbDrawTextPositions(self.bitmap,textpos) end def update(frameCounter=0) super() end end #=============================================================================== # Shows the player throwing bait at a wild Pokémon in a Safari battle. #=============================================================================== class ThrowBaitAnimation < PokeBattle_Animation include PokeBattle_BallAnimationMixin def initialize(sprites,viewport,battler) @battler = battler @trainer = battler.battle.pbGetOwnerFromBattlerIndex(battler.index) super(sprites,viewport) end def createProcesses # Calculate start and end coordinates for battler sprite movement batSprite = @sprites["pokemon_#{@battler.index}"] traSprite = @sprites["player_1"] ballPos = PokeBattle_SceneConstants.pbBattlerPosition(@battler.index,batSprite.sideSize) ballStartX = traSprite.x ballStartY = traSprite.y-traSprite.bitmap.height/2 ballMidX = 0 # Unused in arc calculation ballMidY = 122 ballEndX = ballPos[0]-40 ballEndY = ballPos[1]-4 # Set up trainer sprite trainer = addSprite(traSprite,PictureOrigin::Bottom) # Set up bait sprite ball = addNewSprite(ballStartX,ballStartY, "Graphics/Battle animations/safari_bait",PictureOrigin::Center) ball.setZ(0,batSprite.z+1) # Trainer animation if traSprite.bitmap.width>=traSprite.bitmap.height*2 ballStartX, ballStartY = trainerThrowingFrames(ball,trainer,traSprite) end delay = ball.totalDuration # 0 or 7 # Bait arc animation ball.setSE(delay,"Battle throw") createBallTrajectory(ball,delay,12, ballStartX,ballStartY,ballMidX,ballMidY,ballEndX,ballEndY) ball.setZ(9,batSprite.z+1) delay = ball.totalDuration ball.moveOpacity(delay+8,2,0) ball.setVisible(delay+10,false) # Set up battler sprite battler = addSprite(batSprite,PictureOrigin::Bottom) # Show Pokémon jumping before eating the bait delay = ball.totalDuration+3 2.times do battler.setSE(delay,"player jump") battler.moveDelta(delay,3,0,-16) battler.moveDelta(delay+4,3,0,16) delay = battler.totalDuration+1 end # Show Pokémon eating the bait delay = battler.totalDuration+3 2.times do battler.moveAngle(delay,7,5) battler.moveDelta(delay,7,0,6) battler.moveAngle(delay+7,7,0) battler.moveDelta(delay+7,7,0,-6) delay = battler.totalDuration end end end #=============================================================================== # Shows the player throwing a rock at a wild Pokémon in a Safari battle. #=============================================================================== class ThrowRockAnimation < PokeBattle_Animation include PokeBattle_BallAnimationMixin def initialize(sprites,viewport,battler) @battler = battler @trainer = battler.battle.pbGetOwnerFromBattlerIndex(battler.index) super(sprites,viewport) end def createProcesses # Calculate start and end coordinates for battler sprite movement batSprite = @sprites["pokemon_#{@battler.index}"] traSprite = @sprites["player_1"] ballStartX = traSprite.x ballStartY = traSprite.y-traSprite.bitmap.height/2 ballMidX = 0 # Unused in arc calculation ballMidY = 122 ballEndX = batSprite.x ballEndY = batSprite.y-batSprite.bitmap.height/2 # Set up trainer sprite trainer = addSprite(traSprite,PictureOrigin::Bottom) # Set up bait sprite ball = addNewSprite(ballStartX,ballStartY, "Graphics/Battle animations/safari_rock",PictureOrigin::Center) ball.setZ(0,batSprite.z+1) # Trainer animation if traSprite.bitmap.width>=traSprite.bitmap.height*2 ballStartX, ballStartY = trainerThrowingFrames(ball,trainer,traSprite) end delay = ball.totalDuration # 0 or 7 # Bait arc animation ball.setSE(delay,"Battle throw") createBallTrajectory(ball,delay,12, ballStartX,ballStartY,ballMidX,ballMidY,ballEndX,ballEndY) ball.setZ(9,batSprite.z+1) delay = ball.totalDuration ball.setSE(delay,"Battle damage weak") ball.moveOpacity(delay+2,2,0) ball.setVisible(delay+4,false) # Set up anger sprite anger = addNewSprite(ballEndX-42,ballEndY-36, "Graphics/Battle animations/safari_anger",PictureOrigin::Center) anger.setVisible(0,false) anger.setZ(0,batSprite.z+1) # Show anger appearing delay = ball.totalDuration+5 2.times do anger.setSE(delay,"Player jump") anger.setVisible(delay,true) anger.moveZoom(delay,3,130) anger.moveZoom(delay+3,3,100) anger.setVisible(delay+6,false) anger.setDelta(delay+6,96,-16) delay = anger.totalDuration+3 end end end #=============================================================================== # Safari Zone battle scene (the visuals of the battle) #=============================================================================== class PokeBattle_Scene def pbSafariStart @briefMessage = false @sprites["dataBox_0"] = SafariDataBox.new(@battle,@viewport) dataBoxAnim = DataBoxAppearAnimation.new(@sprites,@viewport,0) loop do dataBoxAnim.update pbUpdate break if dataBoxAnim.animDone? end dataBoxAnim.dispose pbRefresh end def pbSafariCommandMenu(index) pbCommandMenuEx(index,[ _INTL("What will\n{1} throw?",@battle.pbPlayer.name), _INTL("Ball"), _INTL("Bait"), _INTL("Rock"), _INTL("Run") ],3) end def pbThrowBait @briefMessage = false baitAnim = ThrowBaitAnimation.new(@sprites,@viewport,@battle.battlers[1]) loop do baitAnim.update pbUpdate break if baitAnim.animDone? end baitAnim.dispose end def pbThrowRock @briefMessage = false rockAnim = ThrowRockAnimation.new(@sprites,@viewport,@battle.battlers[1]) loop do rockAnim.update pbUpdate break if rockAnim.animDone? end rockAnim.dispose end alias __safari__pbThrowSuccess pbThrowSuccess def pbThrowSuccess __safari__pbThrowSuccess pbWildBattleSuccess if @battle.is_a?(PokeBattle_SafariZone) end end #=============================================================================== # Safari Zone battle class #=============================================================================== class PokeBattle_SafariZone attr_reader :battlers # Array of fake battler objects attr_accessor :sideSizes # Array of number of battlers per side attr_accessor :backdrop # Filename fragment used for background graphics attr_accessor :backdropBase # Filename fragment used for base graphics attr_accessor :time # Time of day (0=day, 1=eve, 2=night) attr_accessor :environment # Battle surroundings (for mechanics purposes) attr_reader :weather attr_reader :player attr_accessor :party2 attr_accessor :canRun # True if player can run from battle attr_accessor :canLose # True if player won't black out if they lose attr_accessor :switchStyle # Switch/Set "battle style" option attr_accessor :showAnims # "Battle scene" option (show anims) attr_accessor :expGain # Whether Pokémon can gain Exp/EVs attr_accessor :moneyGain # Whether the player can gain/lose money attr_accessor :rules attr_accessor :ballCount include PokeBattle_BattleCommon def pbRandom(x); return rand(x); end #============================================================================= # Initialize the battle class #============================================================================= def initialize(scene,player,party2) @scene = scene @peer = PokeBattle_BattlePeer.create() @backdrop = "" @backdropBase = nil @time = 0 @environment = :None # e.g. Tall grass, cave, still water @weather = :None @decision = 0 @caughtPokemon = [] @player = [player] @party2 = party2 @sideSizes = [1,1] @battlers = [ PokeBattle_FakeBattler.new(self,0), PokeBattle_FakeBattler.new(self,1) ] @rules = {} @ballCount = 0 end def defaultWeather=(value); @weather = value; end def defaultTerrain=(value); end #============================================================================= # Information about the type and size of the battle #============================================================================= def wildBattle?; return true; end def trainerBattle?; return false; end def setBattleMode(mode); end def pbSideSize(index) return @sideSizes[index%2] end #============================================================================= # Trainers and owner-related #============================================================================= def pbPlayer; return @player[0]; end def opponent; return nil; end def pbGetOwnerFromBattlerIndex(idxBattler); return pbPlayer; end #============================================================================= # Get party info (counts all teams on the same side) #============================================================================= def pbParty(idxBattler) return (opposes?(idxBattler)) ? @party2 : nil end def pbAllFainted?(idxBattler=0); return false; end #============================================================================= # Battler-related #============================================================================= def opposes?(idxBattler1,idxBattler2=0) idxBattler1 = idxBattler1.index if idxBattler1.respond_to?("index") idxBattler2 = idxBattler2.index if idxBattler2.respond_to?("index") return (idxBattler1&1)!=(idxBattler2&1) end def pbRemoveFromParty(idxBattler,idxParty); end def pbGainExp; end #============================================================================= # Messages and animations #============================================================================= def pbDisplay(msg,&block) @scene.pbDisplayMessage(msg,&block) end def pbDisplayPaused(msg,&block) @scene.pbDisplayPausedMessage(msg,&block) end def pbDisplayBrief(msg) @scene.pbDisplayMessage(msg,true) end def pbDisplayConfirm(msg) return @scene.pbDisplayConfirmMessage(msg) end #============================================================================= # Pokedex Registration #============================================================================= def pbSetBattled(battler) return if !battler || !@internalBattle if battler.respond_to?(:opposes?) return if battler.opposes? pbPlayer.pokedex.register_battled(battler.displaySpecies) else pbPlayer.pokedex.register_battled(battler.species) end end class BattleAbortedException < Exception; end def pbAbort raise BattleAbortedException.new("Battle aborted") end #============================================================================= # Safari battle-specific methods #============================================================================= def pbEscapeRate(catch_rate) return 125 if catch_rate <= 45 # Escape factor 9 (45%) return 100 if catch_rate <= 60 # Escape factor 7 (35%) return 75 if catch_rate <= 120 # Escape factor 5 (25%) return 50 if catch_rate <= 250 # Escape factor 3 (15%) return 25 # Escape factor 2 (10%) end def pbStartBattle begin pkmn = @party2[0] self.pbPlayer.pokedex.register(pkmn) @scene.pbStartBattle(self) pbDisplayPaused(_INTL("Wild {1} appeared!",pkmn.name)) @scene.pbSafariStart weather_data = GameData::BattleWeather.try_get(@weather) @scene.pbCommonAnimation(weather_data.animation) if weather_data safariBall = GameData::Item.get(:SAFARIBALL).id catch_rate = pkmn.species_data.catch_rate catchFactor = (catch_rate*100)/1275 catchFactor = [[catchFactor,3].max,20].min escapeFactor = (pbEscapeRate(catch_rate)*100)/1275 escapeFactor = [[escapeFactor,2].max,20].min loop do cmd = @scene.pbSafariCommandMenu(0) case cmd when 0 # Ball if pbBoxesFull? pbDisplay(_INTL("The boxes are full! You can't catch any more Pokémon!")) next end @ballCount -= 1 @scene.pbRefresh rare = (catchFactor*1275)/100 if safariBall pbThrowPokeBall(1,safariBall,rare,true) if @caughtPokemon.length>0 pbRecordAndStoreCaughtPokemon @decision = 4 end end when 1 # Bait pbDisplayBrief(_INTL("{1} threw some bait at the {2}!",self.pbPlayer.name,pkmn.name)) @scene.pbThrowBait catchFactor /= 2 if pbRandom(100)<90 # Harder to catch escapeFactor /= 2 # Less likely to escape when 2 # Rock pbDisplayBrief(_INTL("{1} threw a rock at the {2}!",self.pbPlayer.name,pkmn.name)) @scene.pbThrowRock catchFactor *= 2 # Easier to catch escapeFactor *= 2 if pbRandom(100)<90 # More likely to escape when 3 # Run pbSEPlay("Battle flee") pbDisplayPaused(_INTL("You got away safely!")) @decision = 3 end catchFactor = [[catchFactor,3].max,20].min escapeFactor = [[escapeFactor,2].max,20].min # End of round if @decision==0 if @ballCount<=0 pbDisplay(_INTL("PA: You have no Safari Balls left! Game over!")) @decision = 2 elsif pbRandom(100)<5*escapeFactor pbSEPlay("Battle flee") pbDisplay(_INTL("{1} fled!",pkmn.name)) @decision = 3 elsif cmd==1 # Bait pbDisplay(_INTL("{1} is eating!",pkmn.name)) elsif cmd==2 # Rock pbDisplay(_INTL("{1} is angry!",pkmn.name)) else pbDisplay(_INTL("{1} is watching carefully!",pkmn.name)) end # Weather continues weather_data = GameData::BattleWeather.try_get(@weather) @scene.pbCommonAnimation(weather_data.animation) if weather_data end break if @decision > 0 end @scene.pbEndBattle(@decision) rescue BattleAbortedException @decision = 0 @scene.pbEndBattle(@decision) end return @decision end end