Resource icon

[v18] Scripted and Simulated Battles 2.2

  • Fixed Bug, where end speeches wouldn't work on the player side in a simulated battle
  • Fixed Bug, where recompiling the trainers pbs wouldn't updated the scripted battles
  • Fixed Bug, where in simulated battles the player could still choose the next Pokemon for the player side

How to update (Clean installation only)
1. Replace your whole 14_Scripted_Battle_Compile script section with the following content
Ruby:
#===============================================================================
# Compile scripted battles
#===============================================================================
def pbCompileScriptedBattles(mustCompile = false)
  return if !$DEBUG
  # create a dummy trainer to prevent errors
  # just replace it with whatever trainer you have defined
  path = "PBS/battles/"
  name = "blueVSBrock2"
  files = Dir.entries(path)
  latestDataTime = 0
  latestTextTime = 0


  # Check data files and PBS files, and recompile if any PBS file was edited
  # more recently than the data files were last created
  files.each{ |f|
    next if !/\.(?:rb|txt)$/i.match(f)
    fData = f.gsub(/\.(?:rb|txt)$/i,".dat")
    begin
      File.open("Data/#{fData}") { |file|
        latestDataTime = [latestDataTime,file.mtime.to_i].max
      }
      rescue SystemCallError
      mustCompile = true
    end
  }

  files.each{ |f|
    next if !/\.(?:rb|txt)$/i.match(f)
    begin
      File.open("#{path}#{f}") { |file|
        latestTextTime = [latestTextTime,file.mtime.to_i].max
      }
      rescue SystemCallError
    end
  }

  mustCompile |= (latestTextTime>=latestDataTime)

  if mustCompile
    $Trainer = PokeBattle_Trainer.new(:RIVAL1,"Blue")
    files.each{ |f|
    next if !/\.(?:rb|txt)$/i.match(f)
      records   = []
      pbCompilerEachPreppedLine("#{path}#{f}") { |line,lineno|
      records.push(line)
    }
    code = ""
    for rec in records
      code += "#{rec}\r\n"
    end
    begin
      script = eval(code)
    rescue SyntaxError => e
      pbPrintException(e)
    end

    name = f.gsub(/\.(?:rb|txt)$/i,'')
    save_data(script,"Data/#{name}.dat")
    }
    $Trainer = nil
  end
end

2. In the Compiler script section (the one from Essentials), change
Ruby:
# Compile scripted battles
pbCompileScriptedBattles
to
Ruby:
# Compile scripted battles
pbCompileScriptedBattles(mustCompile)

3. Replace your whole 2_PField_Simulated_Battles script section with the following content
Ruby:
#===============================================================================
# Start a trainer battle
#===============================================================================
def pbSimulatedTrainerBattleCore(players,opponents,fullNames=[false,false])
  outcomeVar = $PokemonTemp.battleRules["outcomeVar"] || 1
  canLose    = $PokemonTemp.battleRules["canLose"] || false
  # Skip battle if the player has no able Pokémon, or if holding Ctrl in Debug mode
  if $Trainer.ablePokemonCount==0 || ($DEBUG && Input.press?(Input::CTRL))
    pbMessage(_INTL("SKIPPING BATTLE...")) if $DEBUG
    pbMessage(_INTL("AFTER WINNING...")) if $DEBUG && $Trainer.ablePokemonCount>0
    pbSet(outcomeVar,($Trainer.ablePokemonCount==0) ? 0 : 1)   # Treat it as undecided/a win
    $PokemonTemp.clearBattleRules
    $PokemonGlobal.nextBattleBGM       = nil
    $PokemonGlobal.nextBattleME        = nil
    $PokemonGlobal.nextBattleCaptureME = nil
    $PokemonGlobal.nextBattleBack      = nil
    return ($Trainer.ablePokemonCount==0) ? 0 : 1   # Treat it as undecided/a win
  end
  # Record information about party Pokémon to be used at the end of battle (e.g.
  # comparing levels for an evolution check)
  Events.onStartBattle.trigger(nil)
  # Generate trainers and their parties based on the arguments given
  foeTrainers    = []
  foeItems       = []
  foeEndSpeeches = []
  foeParty       = []
  foePartyStarts = []
  for opponent in opponents
    raise _INTL("Expected an array of trainer data, got {1}.",opponent) if !opponent.is_a?(Array)
  
    if opponent[0].is_a?(PokeBattle_Trainer)
      # [trainer object, party, end speech, items]
      foeTrainers.push(opponent[0])
      foePartyStarts.push(foeParty.length)
      opponent[1].each { |pkmn| foeParty.push(pkmn) }
      foeEndSpeeches.push(opponent[2])
      foeItems.push(opponent[3])
    else
      # [trainer type, trainer name, ID, speech (optional)]
      trainer = pbLoadTrainer(opponent[0],opponent[1],opponent[2])
      pbMissingTrainer(opponent[0],opponent[1],opponent[2]) if !trainer
      return 0 if !trainer
      Events.onTrainerPartyLoad.trigger(nil,trainer)
      foeTrainers.push(trainer[0])
      foePartyStarts.push(foeParty.length)
      trainer[2].each { |pkmn| foeParty.push(pkmn) }
      foeEndSpeeches.push(opponent[3] || trainer[3])
      foeItems.push(trainer[1])
    end
  end
  # Calculate who the player trainer(s) and their party are
  playerTrainers    = []
  playerParty       = []
  playerPartyStarts = []
  playerItems       = []
  playerEndSpeeches = []

  for player in players
    raise _INTL("Expected an array of trainer data, got {1}.",player) if !player.is_a?(Array)
    if player[0].is_a?(PokeBattle_Trainer)
      # [trainer object, party, end speech, items]
      playerTrainers.push(player[0])
      playerPartyStarts.push(playerParty.length)
      player[1].each { |pkmn| playerParty.push(pkmn) }
      playerEndSpeeches.push(player[2])
      playerItems.push(player[3])
      echo("Was trainer object")
      echo(player[2])
    else
      # [trainer type, trainer name, ID, speech (optional)]
      trainer = pbLoadTrainer(player[0],player[1],player[2])
      pbMissingTrainer(player[0],player[1],player[2]) if !trainer
      return 0 if !trainer
      Events.onTrainerPartyLoad.trigger(nil,trainer)
      playerTrainers.push(trainer[0])
      playerPartyStarts.push(playerParty.length)
      trainer[2].each { |pkmn| playerParty.push(pkmn) }
      playerEndSpeeches.push(player[3] || trainer[3])

      playerItems.push(trainer[1])
    end
  end

  # Create the battle scene (the visual side of it)
  scene = pbNewBattleScene
  # Create the battle class (the mechanics side of it)
  battle = PokeBattle_SimulatedBattle.new(scene,playerParty,foeParty,playerTrainers,foeTrainers,fullNames)
  battle.party1starts = playerPartyStarts
  battle.party2starts = foePartyStarts
  battle.items        = foeItems
  battle.endSpeeches  = foeEndSpeeches
  battle.playerEndspeeches  = playerEndSpeeches
  # Set various other properties in the battle class
  pbPrepareBattle(battle)
  $PokemonTemp.clearBattleRules
  # End the trainer intro music
  Audio.me_stop
  # Perform the battle itself
  decision = 0
  pbScriptedBattleAnimation(pbGetTrainerBattleBGM(foeTrainers),(battle.singleBattle?) ? 1 : 3,foeTrainers, playerTrainers) {
    pbSceneStandby {
      decision = battle.pbStartBattle
    }
    pbAfterSimulatedBattle(decision,canLose)
  }
  Input.update
  # Save the result of the battle in a Game Variable (1 by default)
  #    0 - Undecided or aborted
  #    1 - Player won
  #    2 - Player lost
  #    3 - Player or wild Pokémon ran from battle, or player forfeited the match
  #    5 - Draw
  pbSet(outcomeVar,decision)
  return decision
end

#===============================================================================
# Standard methods that start a simulated trainer battle of various sizes
#===============================================================================

def pbSimulatedTrainerBattle(player,opponent,size0=1,size1=1,canLose=true,outcomeVar=1,fullNames=[false,false])
  # Set some battle rules
  setBattleRule("outcomeVar",outcomeVar) if outcomeVar!=1
  setBattleRule("canLose") if canLose
  setBattleRule(sprintf("%dv%d",size0,size1))
  # Perform the battle
  if player.is_a?(Array)
    players = []
    for pl in player
        players.push([pl.trainerID,pl.trainerName,pl.trainerPartyID,pl.endSpeech])
    end
  else
    players = [[player.trainerID,player.trainerName,player.trainerPartyID,player.endSpeech]]
  end

  if opponent.is_a?(Array)
    opponents = []
    for op in opponent
        opponents.push([op.trainerID,op.trainerName,op.trainerPartyID,op.endSpeech])
    end
  else
    opponents = [[opponent.trainerID,opponent.trainerName,opponent.trainerPartyID,opponent.endSpeech]]
  end

  decision = pbSimulatedTrainerBattleCore(players,opponents,fullNames)
  $PokemonTemp.waitingTrainer = nil
  # Return true if the player won the battle, and false if any other result
  return (decision==1)
end


#===============================================================================
# After battles
#===============================================================================
def pbAfterSimulatedBattle(decision,canLose)
  if decision==2 || decision==5   # if loss or draw
    if canLose
      $Trainer.party.each { |pkmn| pkmn.heal }
      (Graphics.frame_rate/4).times { Graphics.update }
    end
  end
  Events.onEndBattle.trigger(nil,decision,canLose)
end

Events.onEndBattle += proc { |sender,e|
  decision = e[0]
  canLose  = e[1]
  case decision
  when 1, 4   # Win, capture
  when 2, 5   # Lose, draw
    if !canLose
      $game_system.bgm_unpause
      $game_system.bgs_unpause
      pbStartOver
    end
  end
}

4. Replace your whole 5_Simulated_Battle_Switching script section with the following content
Ruby:
class PokeBattle_SimulatedBattle
  def pbGetOwnerName(idxBattler)
    owner = pbGetOwnerFromBattlerIndex(idxBattler)
    return pbGetNameOf(owner,opposes?(idxBattler))
  end

  # For choosing a replacement Pokémon when prompted in the middle of other
  # things happening (U-turn, Baton Pass, in def pbSwitch).
  def pbSwitchInBetween(idxBattler,checkLaxOnly=false,canCancel=false)
    return @battleAI.pbDefaultChooseNewEnemy(idxBattler,pbParty(idxBattler))
  end

  def pbOwnedByPlayer?(idxBattler)
    return false if opposes?(idxBattler)
    return pbGetOwnerIndexFromBattlerIndex(idxBattler)==0 && !@controlPlayer
  end

end
This new version contains a permanent solution to this bug, which was caused by my script trying to access trainer data before that was compiled. With that bug fix your scripted battles will only recompile when they have changed and compiling them will always happen after compiling everything else.

How to update (Clean Installation Only)
1. Replace your whole 14_Scripted_Battle_Compile script section with the following content
Ruby:
#===============================================================================
# Compile scripted battles
#===============================================================================
def pbCompileScriptedBattles
  return if !$DEBUG
  # create a dummy trainer to prevent errors
  # just replace it with whatever trainer you have defined
  path = "PBS/battles/"
  name = "blueVSBrock2"
  files = Dir.entries(path)
  latestDataTime = 0
  latestTextTime = 0
 
  mustCompile = false
  # Check data files and PBS files, and recompile if any PBS file was edited
  # more recently than the data files were last created
  files.each{ |f|
    next if !/\.(?:rb|txt)$/i.match(f)
    fData = f.gsub(/\.(?:rb|txt)$/i,".dat")
    begin
      File.open("Data/#{fData}") { |file|
        latestDataTime = [latestDataTime,file.mtime.to_i].max
      }
      rescue SystemCallError
      mustCompile = true
    end
  }
 
  files.each{ |f|
    next if !/\.(?:rb|txt)$/i.match(f)
    begin
      File.open("#{path}#{f}") { |file|
        latestTextTime = [latestTextTime,file.mtime.to_i].max
      }
      rescue SystemCallError
    end
  }
 
  mustCompile |= (latestTextTime>=latestDataTime)
 
  if mustCompile
    $Trainer = PokeBattle_Trainer.new(:RIVAL1,"Blue")
    files.each{ |f|
    next if !/\.(?:rb|txt)$/i.match(f)
      records   = []
      pbCompilerEachPreppedLine("#{path}#{f}") { |line,lineno|
      records.push(line)
    }
    code = ""
    for rec in records
      code += "#{rec}\r\n"
    end
    begin
      script = eval(code)
    rescue SyntaxError => e
      pbPrintException(e)
    end
 
    name = f.gsub(/\.(?:rb|txt)$/i,'')
    save_data(script,"Data/#{name}.dat")
    }
    $Trainer = nil
  end
end

2. In the Compiler script section (the one from Essentials), right after
Ruby:
if !$INEDITOR && LANGUAGES.length>=2
  pbLoadMessages("Data/"+LANGUAGES[$PokemonSystem.language][1])
end
add
Ruby:
# Compile scripted battles
  pbCompileScriptedBattles
With this update you can now use Z-Moves, Ultra Burst, Dynamax and GMax in scripted battles. To make this work you will need the ZUD Plugin by Lucidious89 and StCooler.

Say thanks to the two people who suggested this support :)

There won't be support for Raid Battles, as that requires too much rework, for the time that I got.

For this major update, I highly recommend doing a complete new installation. Just remove the old script sections and replace them with the new ones.
With this version I added full VS Animation support. Just make sure to have all files set up (see here).
With this version there is one more file added: Shared_PField_Visuals.txt

Update Guide (Clean installation only)
If you have already installed the script and want to update you can do either of the following things. In both cases you need to add a new script section named Shared_PField_Visuals and copy over the content of the file with the same name.

Option 1:
Change pbBattleAnimation to pbScriptedBattleAnimation and add ,player after ,foe
in the following places

Line Numbers are assuming no changes were made:
PField_Simulated_Battles, Line 96
PField_Scripted_Battles, Line 77
PField_Scripted_Battles, Line 219

Option 2:
Replace the whole PField_Scripted_Battles and PField_Simulated_Battles script sections with the new files
Summary
  • Merged :moveMessage with :tryUseMoveMessage
  • They can both be used to show a message, whenever a Pokemon tries to use a move, regardless of success
  • I lost (or removed on purpose) the code that was used for :moveMessage and after investigating, decided that there is no need to differentiate between those two
  • Updated docs

Changed files/sections
  • Scripted_Battle_Battler_2
  • Scripted_Battle_Docs

Changed methods
- def pbTryUseMove

To update your project copy the contents of the changed files/sections or replace the changed methods.
There was an oversight in the first release, that would break protect and multihit moves in normal battles. This is fixed now.

Either redownload the folder and replace everything with dirty.txt if you are using the dirty approach or replace the contents of Scripted_Battle_Moves Script Section with the content of the file.

Or just copy the new version here and replace the whole methods.


Update:
class PokeBattle_Move_0C0 < PokeBattle_Move
  def pbNumHits(user,targets)
    if isConst?(@id,PBMoves,:WATERSHURIKEN) &&
       isConst?(user.species,PBSpecies,:GRENINJA) && user.form==1
      return 3
    end
    
    if defined?(@battle.script)
      hits = user.roundParams[:multiHit]
      return hits if !hits.nil?
    end
    
    hitChances = [2,2,3,3,4,5]
    r = @battle.pbRandom(hitChances.length)
    r = hitChances.length-1 if user.hasActiveAbility?(:SKILLLINK)
    return hitChances[r]
  end
end


class PokeBattle_ProtectMove < PokeBattle_Move

  def pbMoveFailed?(user,targets)
    success = nil
    if defined?(@battle.script)
      success = user.roundParams[:success]
      return false if success
    end
    if @sidedEffect
      if user.pbOwnSide.effects[@effect]
        user.effects[PBEffects::ProtectRate] = 1
        @battle.pbDisplay(_INTL("But it failed!"))
        return true
      end
    elsif user.effects[@effect] || (!success.nil? && success == false)
      user.effects[PBEffects::ProtectRate] = 1
      @battle.pbDisplay(_INTL("But it failed!"))
      return true
    end
    if !(@sidedEffect && NEWEST_BATTLE_MECHANICS) &&
       user.effects[PBEffects::ProtectRate]>1 &&
       @battle.pbRandom(user.effects[PBEffects::ProtectRate])!=0
      user.effects[PBEffects::ProtectRate] = 1
      @battle.pbDisplay(_INTL("But it failed!"))
      return true
    end
    if pbMoveFailedLastInRound?(user)
      user.effects[PBEffects::ProtectRate] = 1
      return true
    end
    return false
  end
end
  • Like
Reactions: Rohit Yadav
Top