From 95916e242e9d213b263cca0444a2853ef53938eb Mon Sep 17 00:00:00 2001 From: Maruno17 Date: Sun, 5 Dec 2021 20:24:20 +0000 Subject: [PATCH] Added battle debug menu (access with F9) --- .../001_Battle/009_Battle_CommandPhase.rb | 9 +- .../011_Battle/004_Scene/001_Battle_Scene.rb | 12 +- .../004_Scene/006_Battle_Scene_Objects.rb | 3 +- .../003_Debug menus/001_Debug_Menus.rb | 163 ++++ .../007_Debug_BattleCommands.rb | 372 +++++++++ .../008_Debug_BattlerCommands.rb | 785 ++++++++++++++++++ .../009_Debug_BattleExtraCode.rb | 436 ++++++++++ 7 files changed, 1776 insertions(+), 4 deletions(-) create mode 100644 Data/Scripts/020_Debug/003_Debug menus/007_Debug_BattleCommands.rb create mode 100644 Data/Scripts/020_Debug/003_Debug menus/008_Debug_BattlerCommands.rb create mode 100644 Data/Scripts/020_Debug/003_Debug menus/009_Debug_BattleExtraCode.rb diff --git a/Data/Scripts/011_Battle/001_Battle/009_Battle_CommandPhase.rb b/Data/Scripts/011_Battle/001_Battle/009_Battle_CommandPhase.rb index 05b7e4e09..00c6e2438 100644 --- a/Data/Scripts/011_Battle/001_Battle/009_Battle_CommandPhase.rb +++ b/Data/Scripts/011_Battle/001_Battle/009_Battle_CommandPhase.rb @@ -157,8 +157,13 @@ class Battle end def pbDebugMenu - # NOTE: This doesn't do anything yet. Maybe you can write your own debugging - # options! + pbBattleDebug(self) + @scene.pbRefreshEverything + allBattlers.each { |b| b.pbCheckFormOnWeatherChange } + pbEndPrimordialWeather + allBattlers.each { |b| b.pbAbilityOnTerrainChange } + allBattlers.each { |b| b.pbCheckFormOnMovesetChange } + allBattlers.each { |b| b.pbCheckFormOnStatusChange } end #============================================================================= diff --git a/Data/Scripts/011_Battle/004_Scene/001_Battle_Scene.rb b/Data/Scripts/011_Battle/004_Scene/001_Battle_Scene.rb index a36ca7c6b..12b81eb7a 100644 --- a/Data/Scripts/011_Battle/004_Scene/001_Battle_Scene.rb +++ b/Data/Scripts/011_Battle/004_Scene/001_Battle_Scene.rb @@ -132,6 +132,16 @@ class Battle::Scene @sprites["dataBox_#{idxBattler}"].refresh if @sprites["dataBox_#{idxBattler}"] end + def pbRefreshEverything + pbCreateBackdropSprites + @battle.battlers.each_with_index do |battler, i| + next if !battler + pbChangePokemon(i, @sprites["pokemon_#{i}"].pkmn) + @sprites["dataBox_#{i}"].initializeDataBoxGraphic(@battle.pbSideSize(i)) + @sprites["dataBox_#{i}"].refresh + end + end + #============================================================================= # Party lineup #============================================================================= @@ -305,7 +315,7 @@ class Battle::Scene # Sprites #============================================================================= def pbAddSprite(id,x,y,filename,viewport) - sprite = IconSprite.new(x,y,viewport) + sprite = @sprites[id] || IconSprite.new(x,y,viewport) if filename sprite.setBitmap(filename) rescue nil end diff --git a/Data/Scripts/011_Battle/004_Scene/006_Battle_Scene_Objects.rb b/Data/Scripts/011_Battle/004_Scene/006_Battle_Scene_Objects.rb index d8c7a8f27..17826dfa2 100644 --- a/Data/Scripts/011_Battle/004_Scene/006_Battle_Scene_Objects.rb +++ b/Data/Scripts/011_Battle/004_Scene/006_Battle_Scene_Objects.rb @@ -52,7 +52,8 @@ class Battle::Scene::PokemonDataBox < SpriteWrapper bgFilename = ["Graphics/Pictures/Battle/databox_thin", "Graphics/Pictures/Battle/databox_thin_foe"][@battler.index%2] end - @databoxBitmap = AnimatedBitmap.new(bgFilename) + @databoxBitmap.dispose if @databoxBitmap + @databoxBitmap = AnimatedBitmap.new(bgFilename) # Determine the co-ordinates of the data box and the left edge padding width if onPlayerSide @spriteX = Graphics.width - 244 diff --git a/Data/Scripts/020_Debug/003_Debug menus/001_Debug_Menus.rb b/Data/Scripts/020_Debug/003_Debug menus/001_Debug_Menus.rb index c51626f78..37d9c6a1b 100644 --- a/Data/Scripts/020_Debug/003_Debug menus/001_Debug_Menus.rb +++ b/Data/Scripts/020_Debug/003_Debug menus/001_Debug_Menus.rb @@ -169,6 +169,165 @@ module PokemonDebugMixin end end +#=============================================================================== +# +#=============================================================================== +module Battle::DebugMixin + def pbBattleDebug(battle, show_all = true) + commands = CommandMenuList.new + BattleDebugMenuCommands.each do |option, hash| + commands.add(option, hash) if show_all || hash["always_show"] + end + viewport = Viewport.new(0, 0, Graphics.width, Graphics.height) + viewport.z = 99999 + sprites = {} + sprites["textbox"] = pbCreateMessageWindow + sprites["textbox"].letterbyletter = false + sprites["cmdwindow"] = Window_CommandPokemonEx.new(commands.list) + cmdwindow = sprites["cmdwindow"] + cmdwindow.x = 0 + cmdwindow.y = 0 + cmdwindow.height = Graphics.height - sprites["textbox"].height + cmdwindow.viewport = viewport + cmdwindow.visible = true + sprites["textbox"].text = commands.getDesc(cmdwindow.index) + ret = -1 + refresh = true + loop do + loop do + oldindex = cmdwindow.index + cmdwindow.update + if refresh || cmdwindow.index != oldindex + sprites["textbox"].text = commands.getDesc(cmdwindow.index) + refresh = false + end + Graphics.update + Input.update + if Input.trigger?(Input::BACK) + parent = commands.getParent + if parent + pbPlayCancelSE + commands.currentList = parent[0] + cmdwindow.commands = commands.list + cmdwindow.index = parent[1] + refresh = true + else + ret = -1 + break + end + elsif Input.trigger?(Input::USE) + ret = cmdwindow.index + break + end + end + break if ret < 0 + cmd = commands.getCommand(ret) + if commands.hasSubMenu?(cmd) + pbPlayDecisionSE + commands.currentList = cmd + cmdwindow.commands = commands.list + cmdwindow.index = 0 + refresh = true + else + BattleDebugMenuCommands.call("effect", cmd, battle) + end + end + pbPlayCloseMenuSE + pbDisposeMessageWindow(sprites["textbox"]) + pbDisposeSpriteHash(sprites) + viewport.dispose + end + + def pbBattleBattlerDebug(battler, show_all = true) + commands = CommandMenuList.new + BattlerDebugMenuCommands.each do |option, hash| + commands.add(option, hash) if show_all || hash["always_show"] + end + viewport = Viewport.new(0, 0, Graphics.width, Graphics.height) + viewport.z = 99999 + sprites = {} + sprites["infowindow"] = Window_AdvancedTextPokemon.new("") + infowindow = sprites["infowindow"] + infowindow.x = 0 + infowindow.y = 0 + infowindow.width = Graphics.width / 2 + infowindow.height = Graphics.height + infowindow.viewport = viewport + infowindow.visible = true + sprites["dummywindow"] = Window_AdvancedTextPokemon.new("") + sprites["dummywindow"].y = Graphics.height + sprites["dummywindow"].width = Graphics.width + sprites["dummywindow"].height = 0 + need_refresh = true + cmd = 0 + loop do + if need_refresh + help_text = "" + help_text += sprintf("[%d] %s", battler.index, battler.pbThis) + help_text += "\r\n" + help_text += _INTL("Species: {1}", GameData::Species.get(battler.species).name) + help_text += "\r\n" + help_text += _INTL("Form: {1}", battler.form) + help_text += "\r\n" + help_text += _INTL("Level {1}, {2}", battler.level, + (battler.pokemon.male?) ? "♂" : (battler.pokemon.female?) ? "♀" : "genderless") + help_text += ", " + _INTL("Shiny") if battler.pokemon.shiny? + help_text += "\r\n" + help_text += _INTL("HP: {1}/{2} ({3}%)", battler.hp, battler.totalhp, (100.0 * battler.hp / battler.totalhp).to_i) + help_text += "\r\n" + help_text += _INTL("Status: {1}", GameData::Status.get(battler.status).name) + case battler.status + when :SLEEP + help_text += " " + _INTL("({1} rounds left)", battler.statusCount) + when :POISON + if battler.statusCount > 0 + help_text += " " + _INTL("(toxic, {1}/16)", battler.effects[PBEffects::Toxic]) + end + end + help_text += "\r\n" + stages = [] + GameData::Stat.each_battle do |stat| + next if battler.stages[stat.id] == 0 + stage_text = "" + stage_text += "+" if battler.stages[stat.id] > 0 + stage_text += battler.stages[stat.id].to_s + stage_text += " " + stat.name_brief + stages.push(stage_text) + end + help_text += _INTL("Stat stages: {1}", (stages.empty?) ? "-" : stages.join(", ")) + help_text += "\r\n" + help_text += _INTL("Ability: {1}", (battler.ability) ? battler.abilityName : "-") + help_text += "\r\n" + help_text += _INTL("Item: {1}", (battler.item) ? battler.itemName : "-") + sprites["infowindow"].text = help_text + need_refresh = false + end + # Choose a command + cmd = Kernel.pbShowCommands(sprites["dummywindow"], commands.list, -1, cmd) + if cmd < 0 # Cancel + parent = commands.getParent + if parent # Go up a level + commands.currentList = parent[0] + cmd = parent[1] + else # Exit + break + end + else + real_cmd = commands.getCommand(cmd) + if commands.hasSubMenu?(real_cmd) + commands.currentList = real_cmd + cmd = 0 + else + BattlerDebugMenuCommands.call("effect", real_cmd, battler, battler.pokemon, battler.battle) + need_refresh = true + end + end + end + pbDisposeSpriteHash(sprites) + viewport.dispose + end +end + #=============================================================================== # #=============================================================================== @@ -183,3 +342,7 @@ end class PokemonDebugPartyScreen include PokemonDebugMixin end + +class Battle + include Battle::DebugMixin +end diff --git a/Data/Scripts/020_Debug/003_Debug menus/007_Debug_BattleCommands.rb b/Data/Scripts/020_Debug/003_Debug menus/007_Debug_BattleCommands.rb new file mode 100644 index 000000000..8d3595e07 --- /dev/null +++ b/Data/Scripts/020_Debug/003_Debug menus/007_Debug_BattleCommands.rb @@ -0,0 +1,372 @@ +=begin +# TODO: + +Positions (Battle::ActivePosition) + PBEffects::HealingWish + PBEffects::LunarDance +backdrop, backdropBase +turnCount +items (of foe trainers) +initialItems - Array of two arrays, each with one value per party index +recycleItems - Array of two arrays, each with one value per party index +belch - Array of two arrays, each with one value per party index +corrosiveGas - Array of two arrays, each with one value per party index +first_poke_ball - both for Ball Fetch +poke_ball_failed - both for Ball Fetch + +View party screen for each trainer's team, be able to edit properties of Pokémon +that aren't in battle. + +=end + +#=============================================================================== +# +#=============================================================================== +module BattleDebugMenuCommands + @@commands = HandlerHashBasic.new + + def self.register(option, hash) + @@commands.add(option, hash) + end + + def self.registerIf(condition, hash) + @@commands.addIf(condition, hash) + end + + def self.copy(option, *new_options) + @@commands.copy(option, *new_options) + end + + def self.each + @@commands.each { |key, hash| yield key, hash } + end + + def self.hasFunction?(option, function) + option_hash = @@commands[option] + return option_hash && option_hash.keys.include?(function) + end + + def self.getFunction(option, function) + option_hash = @@commands[option] + return (option_hash && option_hash[function]) ? option_hash[function] : nil + end + + def self.call(function, option, *args) + option_hash = @@commands[option] + return nil if !option_hash || !option_hash[function] + return (option_hash[function].call(*args) == true) + end +end + +#=============================================================================== +# Battler Options +#=============================================================================== +BattleDebugMenuCommands.register("battlers", { + "parent" => "main", + "name" => _INTL("Battlers..."), + "description" => _INTL("Look at Pokémon in battle and change their properties."), + "always_show" => true +}) + +BattleDebugMenuCommands.register("list_player_battlers", { + "parent" => "battlers", + "name" => _INTL("Player-Side Battlers"), + "description" => _INTL("Edit Pokémon on the player's side of battle."), + "always_show" => true, + "effect" => proc { |battle| + battlers = [] + cmds = [] + battle.allSameSideBattlers.each do |b| + battlers.push(b) + text = "[#{b.index}] #{b.name} " + if b.pbOwnedByPlayer? + text += " (yours)" + else + text += " (ally's)" + end + cmds.push(text) + end + cmd = 0 + loop do + cmd = pbMessage("\\ts[]" + _INTL("Choose a Pokémon."), cmds, -1, nil, cmd) + break if cmd < 0 + battle.pbBattleBattlerDebug(battlers[cmd]) + end + } +}) + +BattleDebugMenuCommands.register("list_foe_battlers", { + "parent" => "battlers", + "name" => _INTL("Foe-Side Battlers"), + "description" => _INTL("Edit Pokémon on the opposing side of battle."), + "always_show" => true, + "effect" => proc { |battle| + battlers = [] + cmds = [] + battle.allOtherSideBattlers.each do |b| + battlers.push(b) + cmds.push("[#{b.index}] #{b.name} ") + end + cmd = 0 + loop do + cmd = pbMessage("\\ts[]" + _INTL("Choose a Pokémon."), cmds, -1, nil, cmd) + break if cmd < 0 + battle.pbBattleBattlerDebug(battlers[cmd]) + end + } +}) + +#=============================================================================== +# Field Options +#=============================================================================== +BattleDebugMenuCommands.register("field", { + "parent" => "main", + "name" => _INTL("Field Effects..."), + "description" => _INTL("Effects that apply to the whole battlefield."), + "always_show" => true +}) + +BattleDebugMenuCommands.register("weather", { + "parent" => "field", + "name" => _INTL("Weather"), + "description" => _INTL("Set weather and duration."), + "always_show" => true, + "effect" => proc { |battle| + weather_types = [] + weather_cmds = [] + GameData::BattleWeather.each do |weather| + next if weather.id == :None + weather_types.push(weather.id) + weather_cmds.push(weather.name) + end + cmd = 0 + loop do + weather_data = GameData::BattleWeather.try_get(battle.field.weather) + msg = _INTL("Current weather: {1}", weather_data.name || _INTL("Unknown")) + if weather_data.id != :None + if battle.field.weatherDuration > 0 + msg += "\r\n" + msg += _INTL("Duration : {1} more round(s)", battle.field.weatherDuration) + elsif battle.field.weatherDuration < 0 + msg += "\r\n" + msg += _INTL("Duration : Infinite") + end + end + cmd = pbMessage("\\ts[]" + msg, [ + _INTL("Change type"), + _INTL("Change duration"), + _INTL("Clear weather")], -1, nil, cmd) + break if cmd < 0 + case cmd + when 0 # Change type + weather_cmd = weather_types.index(battle.field.weather) || 0 + new_weather = pbMessage( + "\\ts[]" + _INTL("Choose the new weather type."), weather_cmds, -1, nil, weather_cmd) + if new_weather >= 0 + battle.field.weather = weather_types[new_weather] + battle.field.weatherDuration = 5 if battle.field.weatherDuration == 0 + end + when 1 # Change duration + if battle.field.weather == :None + pbMessage("\\ts[]" + _INTL("There is no weather.")) + next + end + params = ChooseNumberParams.new + params.setRange(0, 99) + params.setInitialValue([battle.field.weatherDuration, 0].max) + params.setCancelValue([battle.field.weatherDuration, 0].max) + new_duration = pbMessageChooseNumber( + "\\ts[]" + _INTL("Choose the new weather duration (0=infinite)."), params) + if new_duration != [battle.field.weatherDuration, 0].max + battle.field.weatherDuration = (new_duration == 0) ? -1 : new_duration + end + when 2 # Clear weather + battle.field.weather = :None + battle.field.weatherDuration = 0 + end + end + } +}) + +BattleDebugMenuCommands.register("terrain", { + "parent" => "field", + "name" => _INTL("Terrain"), + "description" => _INTL("Set terrain and duration."), + "always_show" => true, + "effect" => proc { |battle| + terrain_types = [] + terrain_cmds = [] + GameData::BattleTerrain.each do |terrain| + next if terrain.id == :None + terrain_types.push(terrain.id) + terrain_cmds.push(terrain.name) + end + cmd = 0 + loop do + terrain_data = GameData::BattleTerrain.try_get(battle.field.terrain) + msg = _INTL("Current terrain: {1}", terrain_data.name || _INTL("Unknown")) + if terrain_data.id != :None + if battle.field.terrainDuration > 0 + msg += "\r\n" + msg += _INTL("Duration : {1} more round(s)", battle.field.terrainDuration) + elsif battle.field.terrainDuration < 0 + msg += "\r\n" + msg += _INTL("Duration : Infinite") + end + end + cmd = pbMessage("\\ts[]" + msg, [ + _INTL("Change type"), + _INTL("Change duration"), + _INTL("Clear terrain")], -1, nil, cmd) + break if cmd < 0 + case cmd + when 0 # Change type + terrain_cmd = terrain_types.index(battle.field.terrain) || 0 + new_terrain = pbMessage( + "\\ts[]" + _INTL("Choose the new terrain type."), terrain_cmds, -1, nil, terrain_cmd) + if new_terrain >= 0 + battle.field.terrain = terrain_types[new_terrain] + battle.field.terrainDuration = 5 if battle.field.terrainDuration == 0 + end + when 1 # Change duration + if battle.field.terrain == :None + pbMessage("\\ts[]" + _INTL("There is no terrain.")) + next + end + params = ChooseNumberParams.new + params.setRange(0, 99) + params.setInitialValue([battle.field.terrainDuration, 0].max) + params.setCancelValue([battle.field.terrainDuration, 0].max) + new_duration = pbMessageChooseNumber( + "\\ts[]" + _INTL("Choose the new terrain duration (0=infinite)."), params) + if new_duration != [battle.field.terrainDuration, 0].max + battle.field.terrainDuration = (new_duration == 0) ? -1 : new_duration + end + when 2 # Clear terrain + battle.field.terrain = :None + battle.field.terrainDuration = 0 + end + end + } +}) + +BattleDebugMenuCommands.register("environment", { + "parent" => "field", + "name" => _INTL("Environment/Time"), + "description" => _INTL("Set the battle's environment and time of day."), + "always_show" => true, + "effect" => proc { |battle| + environment_types = [] + environment_cmds = [] + GameData::Environment.each do |environment| + environment_types.push(environment.id) + environment_cmds.push(environment.name) + end + cmd = 0 + loop do + environment_data = GameData::Environment.try_get(battle.environment) + msg = _INTL("Environment: {1}", environment_data.name || _INTL("Unknown")) + msg += "\r\n" + msg += _INTL("Time of day: {1}", [_INTL("Day"), _INTL("Evening"), _INTL("Night")][battle.time]) + cmd = pbMessage("\\ts[]" + msg, [ + _INTL("Change environment"), + _INTL("Change time of day")], -1, nil, cmd) + break if cmd < 0 + case cmd + when 0 # Change environment + environment_cmd = environment_types.index(battle.environment) || 0 + new_environment = pbMessage( + "\\ts[]" + _INTL("Choose the new environment."), environment_cmds, -1, nil, environment_cmd) + if new_environment >= 0 + battle.environment = environment_types[new_environment] + end + when 1 # Change time of day + new_time = pbMessage("\\ts[]" + _INTL("Choose the new time."), + [_INTL("Day"), _INTL("Evening"), _INTL("Night")], -1, nil, battle.time) + battle.time = new_time if new_time >= 0 && new_time != battle.time + end + end + } +}) + +BattleDebugMenuCommands.register("set_field_effects", { + "parent" => "field", + "name" => _INTL("Other Field Effects..."), + "description" => _INTL("View/set other effects that apply to the whole battlefield."), + "always_show" => true, + "effect" => proc { |battle| + editor = Battle::DebugSetEffects.new(battle, :field) + editor.update + editor.dispose + } +}) + +BattleDebugMenuCommands.register("player_side", { + "parent" => "field", + "name" => _INTL("Player's Side Effects..."), + "description" => _INTL("Effects that apply to the side the player is on."), + "always_show" => true, + "effect" => proc { |battle| + editor = Battle::DebugSetEffects.new(battle, :side, 0) + editor.update + editor.dispose + } +}) + +BattleDebugMenuCommands.register("opposing_side", { + "parent" => "field", + "name" => _INTL("Foe's Side Effects..."), + "description" => _INTL("Effects that apply to the opposing side."), + "always_show" => true, + "effect" => proc { |battle| + editor = Battle::DebugSetEffects.new(battle, :side, 1) + editor.update + editor.dispose + } +}) + +#=============================================================================== +# Trainer Options +#=============================================================================== +BattleDebugMenuCommands.register("trainers", { + "parent" => "main", + "name" => _INTL("Trainer Options..."), + "description" => _INTL("Variables that apply to trainers."), + "always_show" => true +}) + +BattleDebugMenuCommands.register("mega_evolution", { + "parent" => "trainers", + "name" => _INTL("Mega Evolution"), + "description" => _INTL("Whether each trainer is allowed to Mega Evolve."), + "always_show" => true, + "effect" => proc { |battle| + cmd = 0 + loop do + commands = [] + cmds = [] + battle.megaEvolution.each_with_index do |side_values, side| + trainers = (side == 0) ? battle.player : battle.opponent + next if !trainers + side_values.each_with_index do |value, i| + next if !trainers[i] + text = (side == 0) ? "Your side:" : "Foe side:" + text += sprintf(" %d: %s", i, trainers[i].name) + text += sprintf(" [ABLE]") if value == -1 + text += sprintf(" [UNABLE]") if value == -2 + commands.push(text) + cmds.push([side, i]) + end + end + cmd = pbMessage("\\ts[]" + _INTL("Choose trainer to toggle whether they can Mega Evolve."), + commands, -1, nil, cmd) + break if cmd < 0 + real_cmd = cmds[cmd] + if battle.megaEvolution[real_cmd[0]][real_cmd[1]] == -1 + battle.megaEvolution[real_cmd[0]][real_cmd[1]] = -2 # Make unable + else + battle.megaEvolution[real_cmd[0]][real_cmd[1]] = -1 # Make able + end + end + } +}) diff --git a/Data/Scripts/020_Debug/003_Debug menus/008_Debug_BattlerCommands.rb b/Data/Scripts/020_Debug/003_Debug menus/008_Debug_BattlerCommands.rb new file mode 100644 index 000000000..113b9d5b4 --- /dev/null +++ b/Data/Scripts/020_Debug/003_Debug menus/008_Debug_BattlerCommands.rb @@ -0,0 +1,785 @@ +=begin +# TODO: + +Trigger ability (probably not) +Some stuff relating to Shadow Pokémon? +Actual stats? @attack, @defense, etc. +@turnCount + +=end + +#=============================================================================== +# +#=============================================================================== +module BattlerDebugMenuCommands + @@commands = HandlerHashBasic.new + + def self.register(option, hash) + @@commands.add(option, hash) + end + + def self.registerIf(condition, hash) + @@commands.addIf(condition, hash) + end + + def self.copy(option, *new_options) + @@commands.copy(option, *new_options) + end + + def self.each + @@commands.each { |key, hash| yield key, hash } + end + + def self.hasFunction?(option, function) + option_hash = @@commands[option] + return option_hash && option_hash.keys.include?(function) + end + + def self.getFunction(option, function) + option_hash = @@commands[option] + return (option_hash && option_hash[function]) ? option_hash[function] : nil + end + + def self.call(function, option, *args) + option_hash = @@commands[option] + return nil if !option_hash || !option_hash[function] + return (option_hash[function].call(*args) == true) + end +end + +#=============================================================================== +# HP/Status options +#=============================================================================== +BattlerDebugMenuCommands.register("hpstatusmenu", { + "parent" => "main", + "name" => _INTL("HP/Status..."), + "always_show" => true +}) + +BattlerDebugMenuCommands.register("sethp", { + "parent" => "hpstatusmenu", + "name" => _INTL("Set HP"), + "always_show" => true, + "effect" => proc { |battler, pkmn, battle| + if pkmn.egg? + pbMessage("\\ts[]" + _INTL("{1} is an egg.", pkmn.name)) + next + elsif battler.totalhp == 1 + pbMessage("\\ts[]" + _INTL("Can't change HP, {1}'s maximum HP is 1.", pkmn.name)) + next + end + params = ChooseNumberParams.new + params.setRange(1, battler.totalhp) + params.setDefaultValue(battler.hp) + new_hp = pbMessageChooseNumber( + "\\ts[]" + _INTL("Set {1}'s HP (1-{2}).", battler.pbThis(true), battler.totalhp), params) + battler.hp = new_hp if new_hp != battler.hp + } +}) + +BattlerDebugMenuCommands.register("setstatus", { + "parent" => "hpstatusmenu", + "name" => _INTL("Set status"), + "always_show" => true, + "effect" => proc { |battler, pkmn, battle| + if pkmn.egg? + pbMessage("\\ts[]" + _INTL("{1} is an egg.", pkmn.name)) + next + elsif pkmn.hp <= 0 + pbMessage("\\ts[]" + _INTL("{1} is fainted, can't change status.", pkmn.name)) + next + end + cmd = 0 + commands = [_INTL("[Cure]")] + ids = [:NONE] + GameData::Status.each do |s| + next if s.id == :NONE + commands.push(_INTL("Set {1}", s.name)) + ids.push(s.id) + end + loop do + msg = _INTL("Current status: {1}", GameData::Status.get(battler.status).name) + if battler.status == :SLEEP + msg += " " + _INTL("(turns: {1})", battler.statusCount) + elsif battler.status == :POISON && battler.statusCount > 0 + msg += " " + _INTL("(toxic, count: {1})", battler.statusCount) + end + cmd = pbMessage("\\ts[]" + msg, commands, -1, nil, cmd) + break if cmd < 0 + case cmd + when 0 # Cure + battler.status = :NONE + else # Give status problem + case ids[cmd] + when :SLEEP + params = ChooseNumberParams.new + params.setRange(0, 99) + params.setDefaultValue((battler.status == :SLEEP) ? battler.statusCount : 3) + params.setCancelValue(-1) + count = pbMessageChooseNumber("\\ts[]" + _INTL("Set {1}'s sleep count (0-99).", battler.pbThis(true)), params) + next if count < 0 + battler.statusCount = count + when :POISON + if pbConfirmMessage("\\ts[]" + _INTL("Make {1} badly poisoned (toxic)?", battler.pbThis(true))) + params = ChooseNumberParams.new + params.setRange(0, 15) + params.setDefaultValue(0) + params.setCancelValue(-1) + count = pbMessageChooseNumber( + "\\ts[]" + _INTL("Set {1}'s toxic count (0-15).", battler.pbThis(true)), params) + next if count < 0 + battler.statusCount = 1 + battler.effects[PBEffects::Toxic] = count + else + battler.statusCount = 0 + end + end + battler.status = ids[cmd] + end + end + } +}) + +BattlerDebugMenuCommands.register("fullheal", { + "parent" => "hpstatusmenu", + "name" => _INTL("Heal HP and status"), + "always_show" => true, + "effect" => proc { |battler, pkmn, battle| + if pkmn.egg? + pbMessage("\\ts[]" + _INTL("{1} is an egg.", pkmn.name)) + next + end + battler.hp = battler.totalhp + battler.status = :NONE + } +}) + +#=============================================================================== +# Level/stats options +#=============================================================================== +BattlerDebugMenuCommands.register("levelstats", { + "parent" => "main", + "name" => _INTL("Stats/level..."), + "always_show" => true +}) + +BattlerDebugMenuCommands.register("setstatstages", { + "parent" => "levelstats", + "name" => _INTL("Set stat stages"), + "always_show" => true, + "effect" => proc { |battler, pkmn, battle| + if pkmn.egg? + pbMessage("\\ts[]" + _INTL("{1} is an egg.", pkmn.name)) + next + end + cmd = 0 + loop do + commands = [] + stat_ids = [] + GameData::Stat.each_battle do |stat| + command_name = stat.name + ": " + command_name += "+" if battler.stages[stat.id] > 0 + command_name += battler.stages[stat.id].to_s + commands.push(command_name) + stat_ids.push(stat.id) + end + commands.push(_INTL("[Reset all]")) + cmd = pbMessage("\\ts[]" + _INTL("Choose a stat stage to change."), commands, -1, nil, cmd) + break if cmd < 0 + if cmd < stat_ids.length # Set a stat + params = ChooseNumberParams.new + params.setRange(-6, 6) + params.setNegativesAllowed(true) + params.setDefaultValue(battler.stages[stat_ids[cmd]]) + value = pbMessageChooseNumber( + "\\ts[]" + _INTL("Set the stage for {1}.", GameData::Stat.get(stat_ids[cmd]).name), params) + battler.stages[stat_ids[cmd]] = value + else # Reset all stats + GameData::Stat.each_battle { |stat| battler.stages[stat.id] = 0 } + end + end + } +}) + +BattlerDebugMenuCommands.register("setlevel", { + "parent" => "levelstats", + "name" => _INTL("Set level"), + "always_show" => true, + "effect" => proc { |battler, pkmn, battle| + if pkmn.egg? + pbMessage("\\ts[]" + _INTL("{1} is an egg.", pkmn.name)) + next + end + params = ChooseNumberParams.new + params.setRange(1, GameData::GrowthRate.max_level) + params.setDefaultValue(pkmn.level) + level = pbMessageChooseNumber( + "\\ts[]" + _INTL("Set the Pokémon's level (max. {1}).", params.maxNumber), params) + if level != pkmn.level + pkmn.level = level + pkmn.calc_stats + battler.pbUpdate + end + } +}) + +BattlerDebugMenuCommands.register("setexp", { + "parent" => "levelstats", + "name" => _INTL("Set Exp"), + "always_show" => true, + "effect" => proc { |battler, pkmn, battle| + if pkmn.egg? + pbMessage("\\ts[]" + _INTL("{1} is an egg.", pkmn.name)) + next + end + min_exp = pkmn.growth_rate.minimum_exp_for_level(pkmn.level) + max_exp = pkmn.growth_rate.minimum_exp_for_level(pkmn.level + 1) + if min_exp == max_exp + pbMessage("\\ts[]" + _INTL("{1} is at the maximum level.", pkmn.name)) + next + end + params = ChooseNumberParams.new + params.setRange(min_exp, max_exp - 1) + params.setDefaultValue(pkmn.exp) + new_exp = pbMessageChooseNumber( + "\\ts[]" + _INTL("Set the Pokémon's Exp (range {1}-{2}).", min_exp, max_exp - 1), params) + pkmn.exp = new_exp if new_exp != pkmn.exp + } +}) + +BattlerDebugMenuCommands.register("hiddenvalues", { + "parent" => "levelstats", + "name" => _INTL("EV/IV..."), + "always_show" => true, + "effect" => proc { |battler, pkmn, battle| + cmd = 0 + loop do + persid = sprintf("0x%08X", pkmn.personalID) + cmd = pbMessage("\\ts[]" + _INTL("Personal ID is {1}.", persid), [ + _INTL("Set EVs"), + _INTL("Set IVs")], -1, nil, cmd) + break if cmd < 0 + case cmd + when 0 # Set EVs + cmd2 = 0 + loop do + total_evs = 0 + ev_commands = [] + ev_id = [] + GameData::Stat.each_main do |s| + ev_commands.push(s.name + " (#{pkmn.ev[s.id]})") + ev_id.push(s.id) + total_evs += pkmn.ev[s.id] + end + ev_commands.push(_INTL("Randomise all")) + ev_commands.push(_INTL("Max randomise all")) + cmd2 = pbMessage("\\ts[]" + _INTL("Change which EV?\nTotal: {1}/{2} ({3}%)", + total_evs, Pokemon::EV_LIMIT, 100 * total_evs / Pokemon::EV_LIMIT), + ev_commands, -1, nil, cmd2) + break if cmd2 < 0 + if cmd2 < ev_id.length + params = ChooseNumberParams.new + upperLimit = 0 + GameData::Stat.each_main { |s| upperLimit += pkmn.ev[s.id] if s.id != ev_id[cmd2] } + upperLimit = Pokemon::EV_LIMIT - upperLimit + upperLimit = [upperLimit, Pokemon::EV_STAT_LIMIT].min + thisValue = [pkmn.ev[ev_id[cmd2]], upperLimit].min + params.setRange(0, upperLimit) + params.setDefaultValue(thisValue) + params.setCancelValue(thisValue) + f = pbMessageChooseNumber("\\ts[]" + _INTL("Set the EV for {1} (max. {2}).", + GameData::Stat.get(ev_id[cmd2]).name, upperLimit), params) + if f != pkmn.ev[ev_id[cmd2]] + pkmn.ev[ev_id[cmd2]] = f + pkmn.calc_stats + battler.pbUpdate + end + else # (Max) Randomise all + evTotalTarget = Pokemon::EV_LIMIT + if cmd2 == evcommands.length - 2 # Randomize all (not max) + evTotalTarget = rand(Pokemon::EV_LIMIT) + end + GameData::Stat.each_main { |s| pkmn.ev[s.id] = 0 } + while evTotalTarget > 0 + r = rand(ev_id.length) + next if pkmn.ev[ev_id[r]] >= Pokemon::EV_STAT_LIMIT + addVal = 1 + rand(Pokemon::EV_STAT_LIMIT / 4) + addVal = addVal.clamp(0, evTotalTarget) + addVal = addVal.clamp(0, Pokemon::EV_STAT_LIMIT - pkmn.ev[ev_id[r]]) + next if addVal == 0 + pkmn.ev[ev_id[r]] += addVal + evTotalTarget -= addVal + end + pkmn.calc_stats + battler.pbUpdate + end + end + when 1 # Set IVs + cmd2 = 0 + loop do + hiddenpower = pbHiddenPower(pkmn) + totaliv = 0 + ivcommands = [] + iv_id = [] + GameData::Stat.each_main do |s| + ivcommands.push(s.name + " (#{pkmn.iv[s.id]})") + iv_id.push(s.id) + totaliv += pkmn.iv[s.id] + end + msg = _INTL("Change which IV?\nHidden Power:\n{1}, power {2}\nTotal: {3}/{4} ({5}%)", + GameData::Type.get(hiddenpower[0]).name, hiddenpower[1], totaliv, + iv_id.length * Pokemon::IV_STAT_LIMIT, 100 * totaliv / (iv_id.length * Pokemon::IV_STAT_LIMIT)) + ivcommands.push(_INTL("Randomise all")) + cmd2 = pbMessage("\\ts[]" + msg, ivcommands, -1, nil, cmd2) + break if cmd2 < 0 + if cmd2 < iv_id.length + params = ChooseNumberParams.new + params.setRange(0, Pokemon::IV_STAT_LIMIT) + params.setDefaultValue(pkmn.iv[iv_id[cmd2]]) + params.setCancelValue(pkmn.iv[iv_id[cmd2]]) + f = pbMessageChooseNumber("\\ts[]" + _INTL("Set the IV for {1} (max. 31).", + GameData::Stat.get(iv_id[cmd2]).name), params) + if f != pkmn.iv[iv_id[cmd2]] + pkmn.iv[iv_id[cmd2]] = f + pkmn.calc_stats + battler.pbUpdate + end + else # Randomise all + GameData::Stat.each_main { |s| pkmn.iv[s.id] = rand(Pokemon::IV_STAT_LIMIT + 1) } + pkmn.calc_stats + battler.pbUpdate + end + end + end + end + } +}) + +BattlerDebugMenuCommands.register("sethappiness", { + "parent" => "levelstats", + "name" => _INTL("Set happiness"), + "always_show" => true, + "effect" => proc { |battler, pkmn, battle| + params = ChooseNumberParams.new + params.setRange(0, 255) + params.setDefaultValue(pkmn.happiness) + h = pbMessageChooseNumber("\\ts[]" + _INTL("Set the Pokémon's happiness (max. 255)."), params) + pkmn.happiness = h if h != pkmn.happiness + } +}) + +#=============================================================================== +# Types +#=============================================================================== +BattlerDebugMenuCommands.register("settypes", { + "parent" => "main", + "name" => _INTL("Set types"), + "always_show" => true, + "effect" => proc { |battler, pkmn, battle| + max_main_types = 2 # The most types a Pokémon can have normally + cmd = 0 + loop do + commands = [] + types = [] + (0...max_main_types).each do |i| + type = battler.types[i] + type_name = (type) ? GameData::Type.get(type).name : "-" + commands.push(_INTL("Type {1}: {2}", i + 1, type_name)) + types.push(type) + end + extra_type = battler.effects[PBEffects::Type3] + extra_type_name = (extra_type) ? GameData::Type.get(extra_type).name : "-" + commands.push(_INTL("Extra type: {1}", extra_type_name)) + types.push(extra_type) + msg = _INTL("Effective types: {1}", battler.pbTypes(true).map { |t| GameData::Type.get(t).name }.join("/")) + msg += "\r\n" + _INTL("(Change a type to itself to remove it.)") + cmd = pbMessage("\\ts[]" + msg, commands, -1, nil, cmd) + break if cmd < 0 + old_type = types[cmd] + new_type = pbChooseTypeList(old_type) + if new_type + if new_type == old_type + if pbConfirmMessage(_INTL("Remove this type?")) + if cmd < max_main_types + battler.types[cmd] = nil + else + battler.effects[PBEffects::Type3] = nil + end + battler.types.compact! + end + else + if cmd < max_main_types + battler.types[cmd] = new_type + else + battler.effects[PBEffects::Type3] = new_type + end + end + end + end + } +}) + +#=============================================================================== +# Moves options +#=============================================================================== +BattlerDebugMenuCommands.register("moves", { + "parent" => "main", + "name" => _INTL("Moves..."), + "always_show" => true +}) + +BattlerDebugMenuCommands.register("teachmove", { + "parent" => "moves", + "name" => _INTL("Teach move"), + "always_show" => true, + "effect" => proc { |battler, pkmn, battle| + if pkmn.numMoves >= Pokemon::MAX_MOVES + pbMessage("\\ts[]" + _INTL("{1} already knows {2} moves. It needs to forget one first.", + pkmn.name, pkmn.numMoves)) + next + end + new_move = pbChooseMoveList + next if !new_move + move_name = GameData::Move.get(new_move).name + if pkmn.hasMove?(new_move) + pbMessage("\\ts[]" + _INTL("{1} already knows {2}.", pkmn.name, move_name)) + next + end + pkmn.learn_move(new_move) + battler.moves.push(Move.from_pokemon_move(self, pkmn.moves.last)) if battler + pbMessage("\\ts[]" + _INTL("{1} learned {2}!", pkmn.name, move_name)) + } +}) + +BattlerDebugMenuCommands.register("forgetmove", { + "parent" => "moves", + "name" => _INTL("Forget move"), + "always_show" => true, + "effect" => proc { |battler, pkmn, battle| + move_names = [] + move_indices = [] + pkmn.moves.each_with_index do |move, index| + next if !move || !move.id + if move.total_pp <= 0 + move_names.push(_INTL("{1} (PP: ---)", move.name)) + else + move_names.push(_INTL("{1} (PP: {2}/{3})", move.name, move.pp, move.total_pp)) + end + move_indices.push(index) + end + cmd = pbMessage("\\ts[]" + _INTL("Forget which move?"), move_names, -1) + next if cmd < 0 + old_move_name = pkmn.moves[move_indices[cmd]].name + pkmn.forget_move_at_index(move_indices[cmd]) + battler.moves.delete_at(move_indices[cmd]) if battler + pbMessage("\\ts[]" + _INTL("{1} forgot {2}.", pkmn.name, old_move_name)) + } +}) + +BattlerDebugMenuCommands.register("setmovepp", { + "parent" => "moves", + "name" => _INTL("Set move PP"), + "always_show" => true, + "effect" => proc { |battler, pkmn, battle| + cmd = 0 + loop do + move_names = [] + move_indices = [] + pkmn.moves.each_with_index do |move, index| + next if !move || !move.id + if move.total_pp <= 0 + move_names.push(_INTL("{1} (PP: ---)", move.name)) + else + move_names.push(_INTL("{1} (PP: {2}/{3})", move.name, move.pp, move.total_pp)) + end + move_indices.push(index) + end + commands = move_names + [_INTL("Restore all PP")] + cmd = pbMessage("\\ts[]" + _INTL("Alter PP of which move?"), commands, -1, nil, cmd) + break if cmd < 0 + if cmd >= 0 && cmd < move_names.length # Move + move = pkmn.moves[move_indices[cmd]] + move_name = move.name + if move.total_pp <= 0 + pbMessage("\\ts[]" + _INTL("{1} has infinite PP.", move_name)) + else + cmd2 = 0 + loop do + msg = _INTL("{1}: PP {2}/{3} (PP Up {4}/3)", move_name, move.pp, move.total_pp, move.ppup) + cmd2 = pbMessage("\\ts[]" + msg, [ + _INTL("Set PP"), + _INTL("Full PP"), + _INTL("Set PP Up")], -1, nil, cmd2) + break if cmd2 < 0 + case cmd2 + when 0 # Change PP + params = ChooseNumberParams.new + params.setRange(0, move.total_pp) + params.setDefaultValue(move.pp) + h = pbMessageChooseNumber( + "\\ts[]" + _INTL("Set PP of {1} (max. {2}).", move_name, move.total_pp), params) + move.pp = h + if battler && battler.moves[move_indices[cmd]].id == move.id + battler.moves[move_indices[cmd]].pp = move.pp + end + when 1 # Full PP + move.pp = move.total_pp + if battler && battler.moves[move_indices[cmd]].id == move.id + battler.moves[move_indices[cmd]].pp = move.pp + end + when 2 # Change PP Up + params = ChooseNumberParams.new + params.setRange(0, 3) + params.setDefaultValue(move.ppup) + h = pbMessageChooseNumber( + "\\ts[]" + _INTL("Set PP Up of {1} (max. 3).", move_name), params) + move.ppup = h + move.pp = move.total_pp if move.pp > move.total_pp + if battler && battler.moves[move_indices[cmd]].id == move.id + battler.moves[move_indices[cmd]].pp = move.pp + end + end + end + end + elsif cmd == commands.length - 1 # Restore all PP + pkmn.heal_PP + if battler + battler.moves.each { |move| move.pp = move.total_pp } + end + end + end + } +}) + +#=============================================================================== +# Other options +#=============================================================================== +BattlerDebugMenuCommands.register("setitem", { + "parent" => "main", + "name" => _INTL("Set item"), + "always_show" => true, + "effect" => proc { |battler, pkmn, battle| + cmd = 0 + commands = [ + _INTL("Change item"), + _INTL("Remove item") + ] + loop do + msg = (pkmn.hasItem?) ? _INTL("Item is {1}.", pkmn.item.name) : _INTL("No item.") + cmd = pbMessage("\\ts[]" + msg, commands, -1, nil, cmd) + break if cmd < 0 + case cmd + when 0 # Change item + item = pbChooseItemList(pkmn.item_id) + if item && item != pkmn.item_id + battler.item = item + if GameData::Item.get(item).is_mail? + pkmn.mail = Mail.new(item, _INTL("Text"), $player.name) + end + end + when 1 # Remove item + if pkmn.hasItem? + battler.item = nil + pkmn.mail = nil + end + else + break + end + end + } +}) + +BattlerDebugMenuCommands.register("setability", { + "parent" => "main", + "name" => _INTL("Set ability"), + "always_show" => true, + "effect" => proc { |battler, pkmn, battle| + cmd = 0 + commands = [ + _INTL("Set ability for battler"), + _INTL("Set ability for Pokémon"), + _INTL("Reset") + ] + loop do + msg = _INTL("Battler's ability is {1}. Pokémon's ability is {2}.", + battler.abilityName, pkmn.ability.name) + cmd = pbMessage("\\ts[]" + msg, commands, -1, nil, cmd) + break if cmd < 0 + case cmd + when 0 # Set ability for battler + new_ability = pbChooseAbilityList(pkmn.ability_id) + if new_ability && new_ability != battler.ability_id + battler.ability = new_ability + end + when 1 # Set ability for Pokémon + new_ability = pbChooseAbilityList(pkmn.ability_id) + if new_ability && new_ability != pkmn.ability_id + pkmn.ability = new_ability + battler.ability = pkmn.ability + end + when 2 # Reset + pkmn.ability_index = nil + pkmn.ability = nil + battler.ability = pkmn.ability + end + end + } +}) + +BattlerDebugMenuCommands.register("setnature", { + "parent" => "main", + "name" => _INTL("Set nature"), + "always_show" => true, + "effect" => proc { |battler, pkmn, battle| + commands = [] + ids = [] + GameData::Nature.each do |nature| + if nature.stat_changes.length == 0 + commands.push(_INTL("{1} (---)", nature.real_name)) + else + plus_text = "" + minus_text = "" + nature.stat_changes.each do |change| + if change[1] > 0 + plus_text += "/" if !plus_text.empty? + plus_text += GameData::Stat.get(change[0]).name_brief + elsif change[1] < 0 + minus_text += "/" if !minus_text.empty? + minus_text += GameData::Stat.get(change[0]).name_brief + end + end + commands.push(_INTL("{1} (+{2}, -{3})", nature.real_name, plus_text, minus_text)) + end + ids.push(nature.id) + end + commands.push(_INTL("[Reset]")) + cmd = ids.index(pkmn.nature_id || ids[0]) + loop do + msg = _INTL("Nature is {1}.", pkmn.nature.name) + cmd = pbMessage("\\ts[]" + msg, commands, -1, nil, cmd) + break if cmd < 0 + if cmd >= 0 && cmd < commands.length - 1 # Set nature + pkmn.nature = ids[cmd] + elsif cmd == commands.length - 1 # Reset + pkmn.nature = nil + end + battler.pbUpdate + end + } +}) + +BattlerDebugMenuCommands.register("setgender", { + "parent" => "main", + "name" => _INTL("Set gender"), + "always_show" => true, + "effect" => proc { |battler, pkmn, battle| + if pkmn.singleGendered? + pbMessage("\\ts[]" + _INTL("{1} is single-gendered or genderless.", pkmn.speciesName)) + next + end + cmd = 0 + loop do + msg = [_INTL("Gender is male."), _INTL("Gender is female.")][pkmn.male? ? 0 : 1] + cmd = pbMessage("\\ts[]" + msg, [ + _INTL("Make male"), + _INTL("Make female"), + _INTL("Reset")], -1, nil, cmd) + break if cmd < 0 + case cmd + when 0 # Make male + pkmn.makeMale + pbMessage("\\ts[]" + _INTL("{1}'s gender couldn't be changed.", pkmn.name)) if !pkmn.male? + when 1 # Make female + pkmn.makeFemale + pbMessage("\\ts[]" + _INTL("{1}'s gender couldn't be changed.", pkmn.name)) if !pkmn.female? + when 2 # Reset + pkmn.gender = nil + end + end + } +}) + +BattlerDebugMenuCommands.register("speciesform", { + "parent" => "main", + "name" => _INTL("Set form"), + "always_show" => true, + "effect" => proc { |battler, pkmn, battle| + cmd = 0 + formcmds = [[], []] + GameData::Species.each do |sp| + next if sp.species != pkmn.species + form_name = sp.form_name + form_name = _INTL("Unnamed form") if !form_name || form_name.empty? + form_name = sprintf("%d: %s", sp.form, form_name) + formcmds[0].push(sp.form) + formcmds[1].push(form_name) + cmd = formcmds[0].length - 1 if pkmn.form == sp.form + end + if formcmds[0].length <= 1 + pbMessage("\\ts[]" + _INTL("Species {1} only has one form.", pkmn.speciesName)) + next + end + loop do + cmd = pbMessage("\\ts[]" + _INTL("Form is {1}.", pkmn.form), formcmds[1], -1, nil, cmd) + next if cmd < 0 + f = formcmds[0][cmd] + if f != pkmn.form + pkmn.forced_form = nil + if MultipleForms.hasFunction?(pkmn, "getForm") + next if !pbConfirmMessage(_INTL("This species decides its own form. Override?")) + pkmn.forced_form = f + end + pkmn.form_simple = f + end + end + } +}) + +#=============================================================================== +# Shininess +#=============================================================================== +BattlerDebugMenuCommands.register("setshininess", { + "parent" => "main", + "name" => _INTL("Set shininess"), + "always_show" => true, + "effect" => proc { |battler, pkmn, battle| + cmd = 0 + loop do + msg_idx = pkmn.shiny? ? (pkmn.super_shiny? ? 1 : 0) : 2 + msg = [_INTL("Is shiny."), _INTL("Is super shiny."), _INTL("Is normal (not shiny).")][msg_idx] + cmd = pbMessage("\\ts[]" + msg, [ + _INTL("Make shiny"), + _INTL("Make super shiny"), + _INTL("Make normal"), + _INTL("Reset")], -1, nil, cmd) + break if cmd < 0 + case cmd + when 0 # Make shiny + pkmn.shiny = true + pkmn.super_shiny = false + when 1 # Make super shiny + pkmn.super_shiny = true + when 2 # Make normal + pkmn.shiny = false + pkmn.super_shiny = false + when 3 # Reset + pkmn.shiny = nil + pkmn.super_shiny = nil + end + end + } +}) + +#=============================================================================== +# Set effects +#=============================================================================== +BattlerDebugMenuCommands.register("set_effects", { + "parent" => "main", + "name" => _INTL("Set effects"), + "always_show" => true, + "effect" => proc { |battler, pkmn, battle| + editor = Battle::DebugSetEffects.new(battle, :battler, battler.index) + editor.update + editor.dispose + } +}) diff --git a/Data/Scripts/020_Debug/003_Debug menus/009_Debug_BattleExtraCode.rb b/Data/Scripts/020_Debug/003_Debug menus/009_Debug_BattleExtraCode.rb new file mode 100644 index 000000000..75ead9818 --- /dev/null +++ b/Data/Scripts/020_Debug/003_Debug menus/009_Debug_BattleExtraCode.rb @@ -0,0 +1,436 @@ +#=============================================================================== +# Effect values that can be edited via the battle debug menu. +#=============================================================================== +module Battle::DebugVariables + BATTLER_EFFECTS = { + PBEffects::AquaRing => {name: "Aqua Ring applies", default: false}, + PBEffects::Attract => {name: "Battler that self is attracted to", default: -1}, # Battler index + PBEffects::BanefulBunker => {name: "Baneful Bunker applies this round", default: false}, +# PBEffects::BeakBlast - only applies to use of specific move, not suitable for setting via debug + PBEffects::Bide => {name: "Bide number of rounds remaining", default: 0}, + PBEffects::BideDamage => {name: "Bide damage accumulated", default: 0, max: 999}, + PBEffects::BideTarget => {name: "Bide last battler to hurt self", default: -1}, # Battler index + PBEffects::BurnUp => {name: "Burn Up has removed self's Fire type", default: false}, + PBEffects::Charge => {name: "Charge number of rounds remaining", default: 0}, + PBEffects::ChoiceBand => {name: "Move locked into by Choice items", default: nil, type: :move}, + PBEffects::Confusion => {name: "Confusion number of rounds remaining", default: 0}, +# PBEffects::Counter - not suitable for setting via debug +# PBEffects::CounterTarget - not suitable for setting via debug + PBEffects::Curse => {name: "Curse damaging applies", default: false}, +# PBEffects::Dancer - only used while Dancer is running, not suitable for setting via debug + PBEffects::DefenseCurl => {name: "Used Defense Curl", default: false}, +# PBEffects::DestinyBond - not suitable for setting via debug +# PBEffects::DestinyBondPrevious - not suitable for setting via debug +# PBEffects::DestinyBondTarget - not suitable for setting via debug + PBEffects::Disable => {name: "Disable number of rounds remaining", default: 0}, + PBEffects::DisableMove => {name: "Disabled move", default: nil, type: :move}, + PBEffects::Electrify => {name: "Electrify making moves Electric", default: false}, + PBEffects::Embargo => {name: "Embargo number of rounds remaining", default: 0}, + PBEffects::Encore => {name: "Encore number of rounds remaining", default: 0}, + PBEffects::EncoreMove => {name: "Encored move", default: nil, type: :move}, + PBEffects::Endure => {name: "Endures all lethal damage this round", default: false}, +# PBEffects::FirstPledge - only applies to use of specific move, not suitable for setting via debug + PBEffects::FlashFire => {name: "Flash Fire powering up Fire moves", default: false}, + PBEffects::Flinch => {name: "Will flinch this round", default: false}, + PBEffects::FocusEnergy => {name: "Focus Energy critical hit stages (0-4)", default: 0, max: 4}, +# PBEffects::FocusPunch - only applies to use of specific move, not suitable for setting via debug + PBEffects::FollowMe => {name: "Follow Me drawing in attacks (if 1+)", default: 0}, # Order of use, lowest takes priority + PBEffects::RagePowder => {name: "Rage Powder applies (use with Follow Me)", default: false}, + PBEffects::Foresight => {name: "Foresight applies (Ghost loses immunities)", default: false}, + PBEffects::FuryCutter => {name: "Fury Cutter power multiplier 2**x (0-4)", default: 0, max: 4}, + PBEffects::GastroAcid => {name: "Gastro Acid is negating self's ability", default: false}, +# PBEffects::GemConsumed - only applies during use of move, not suitable for setting via debug + PBEffects::Grudge => {name: "Grudge will apply if self faints", default: false}, + PBEffects::HealBlock => {name: "Heal Block number of rounds remaining", default: 0}, + PBEffects::HelpingHand => {name: "Helping Hand will power up self's move", default: false}, + PBEffects::HyperBeam => {name: "Hyper Beam recharge rounds remaining", default: 0}, +# PBEffects::Illusion - is a Pokémon object, too complex to be worth bothering with + PBEffects::Imprison => {name: "Imprison disables others' moves known by self", default: false}, + PBEffects::Ingrain => {name: "Ingrain applies", default: false}, +# PBEffects::Instruct - only used while Instruct is running, not suitable for setting via debug +# PBEffects::Instructed - only used while Instruct is running, not suitable for setting via debug + PBEffects::JawLock => {name: "Battler trapping self with Jaw Lock", default: -1}, # Battler index + PBEffects::KingsShield => {name: "King's Shield applies this round", default: false}, + PBEffects::LaserFocus => {name: "Laser Focus certain critial hit duration", default: 0}, + PBEffects::LeechSeed => {name: "Battler that used Leech Seed on self", default: -1}, # Battler index + PBEffects::LockOn => {name: "Lock-On number of rounds remaining", default: 0}, + PBEffects::LockOnPos => {name: "Battler that self is targeting with Lock-On", default: -1}, # Battler index +# PBEffects::MagicBounce - only applies during use of move, not suitable for setting via debug +# PBEffects::MagicCoat - only applies to use of specific move, not suitable for setting via debug + PBEffects::MagnetRise => {name: "Magnet Rise number of rounds remaining", default: 0}, + PBEffects::MeanLook => {name: "Battler trapping self with Mean Look, etc.", default: -1}, # Battler index +# PBEffects::MeFirst - only applies to use of specific move, not suitable for setting via debug + PBEffects::Metronome => {name: "Metronome item power multiplier 1 + 0.2*x (0-5)", default: 0, max: 5}, + PBEffects::MicleBerry => {name: "Micle Berry boosting next move's accuracy", default: false}, + PBEffects::Minimize => {name: "Used Minimize", default: false}, + PBEffects::MiracleEye => {name: "Miracle Eye applies (Dark loses immunities)", default: false}, +# PBEffects::MirrorCoat - not suitable for setting via debug +# PBEffects::MirrorCoatTarget - not suitable for setting via debug +# PBEffects::MoveNext - not suitable for setting via debug + PBEffects::MudSport => {name: "Used Mud Sport (Gen 5 and older)", default: false}, + PBEffects::Nightmare => {name: "Taking Nightmare damage", default: false}, + PBEffects::NoRetreat => {name: "No Retreat trapping self in battle", default: false}, + PBEffects::Obstruct => {name: "Obstruct applies this round", default: false}, + PBEffects::Octolock => {name: "Battler trapping self with Octolock", default: -1}, # Battler index + PBEffects::Outrage => {name: "Outrage number of rounds remaining", default: 0}, +# PBEffects::ParentalBond - only applies during use of move, not suitable for setting via debug + PBEffects::PerishSong => {name: "Perish Song number of rounds remaining", default: 0}, + PBEffects::PerishSongUser => {name: "Battler that used Perish Song on self", default: -1}, # Battler index + PBEffects::PickupItem => {name: "Item retrievable by Pickup", default: nil, type: :item}, + PBEffects::PickupUse => {name: "Pickup item consumed time (higher=more recent)", default: 0}, + PBEffects::Pinch => {name: "(Battle Palace) Behavior changed at <50% HP", default: false}, + PBEffects::Powder => {name: "Powder will explode self's Fire move this round", default: false}, +# PBEffects::PowerTrick - doesn't actually swap the stats therefore does nothing, not suitable for setting via debug +# PBEffects::Prankster - not suitable for setting via debug +# PBEffects::PriorityAbility - not suitable for setting via debug +# PBEffects::PriorityItem - not suitable for setting via debug + PBEffects::Protect => {name: "Protect applies this round", default: false}, + PBEffects::ProtectRate => {name: "Protect success chance 1/x", default: 1, max: 999}, +# PBEffects::Pursuit - not suitable for setting via debug +# PBEffects::Quash - not suitable for setting via debug +# PBEffects::Rage - only applies to use of specific move, not suitable for setting via debug + PBEffects::Rollout => {name: "Rollout rounds remaining (lower=stronger)", default: 0}, + PBEffects::Roost => {name: "Roost removing Flying type this round", default: false}, +# PBEffects::ShellTrap - only applies to use of specific move, not suitable for setting via debug +# PBEffects::SkyDrop - only applies to use of specific move, not suitable for setting via debug + PBEffects::SlowStart => {name: "Slow Start rounds remaining", default: 0}, + PBEffects::SmackDown => {name: "Smack Down is grounding self", default: false}, +# PBEffects::Snatch - only applies to use of specific move, not suitable for setting via debug + PBEffects::SpikyShield => {name: "Spiky Shield applies this round", default: false}, + PBEffects::Spotlight => {name: "Spotlight drawing in attacks (if 1+)", default: 0}, + PBEffects::Stockpile => {name: "Stockpile count (0-3)", default: 0, max: 3}, + PBEffects::StockpileDef => {name: "Def stages gained by Stockpile (0-12)", default: 0, max: 12}, + PBEffects::StockpileSpDef => {name: "Sp. Def stages gained by Stockpile (0-12)", default: 0, max: 12}, + PBEffects::Substitute => {name: "Substitute's HP", default: 0, max: 999}, + PBEffects::TarShot => {name: "Tar Shot weakening self to Fire", default: false}, + PBEffects::Taunt => {name: "Taunt number of rounds remaining", default: 0}, + PBEffects::Telekinesis => {name: "Telekinesis number of rounds remaining", default: 0}, + PBEffects::ThroatChop => {name: "Throat Chop number of rounds remaining", default: 0}, + PBEffects::Torment => {name: "Torment preventing repeating moves", default: false}, +# PBEffects::Toxic - set elsewhere +# PBEffects::Transform - too complex to be worth bothering with +# PBEffects::TransformSpecies - too complex to be worth bothering with + PBEffects::Trapping => {name: "Trapping number of rounds remaining", default: 0}, + PBEffects::TrappingMove => {name: "Move that is trapping self", default: nil, type: :move}, + PBEffects::TrappingUser => {name: "Battler trapping self (for Binding Band)", default: -1}, # Battler index + PBEffects::Truant => {name: "Truant will loaf around this round", default: false}, +# PBEffects::TwoTurnAttack - only applies to use of specific moves, not suitable for setting via debug +# PBEffects::Type3 - set elsewhere + PBEffects::Unburden => {name: "Self lost its item (for Unburden)", default: false}, + PBEffects::Uproar => {name: "Uproar number of rounds remaining", default: 0}, + PBEffects::WaterSport => {name: "Used Water Sport (Gen 5 and older)", default: false}, + PBEffects::WeightChange => {name: "Weight change +0.1*x kg", default: 0, min: -99999, max: 99999}, + PBEffects::Yawn => {name: "Yawn rounds remaining until falling asleep", default: 0} + } + + SIDE_EFFECTS = { + PBEffects::AuroraVeil => {name: "Aurora Veil duration", default: 0}, + PBEffects::CraftyShield => {name: "Crafty Shield applies this round", default: false}, + PBEffects::EchoedVoiceCounter => {name: "Echoed Voice rounds used (max. 5)", default: 0, max: 5}, + PBEffects::EchoedVoiceUsed => {name: "Echoed Voice used this round", default: false}, + PBEffects::LastRoundFainted => {name: "Round when side's battler last fainted", default: -2}, # Treated as -1, isn't a battler index + PBEffects::LightScreen => {name: "Light Screen duration", default: 0}, + PBEffects::LuckyChant => {name: "Lucky Chant duration", default: 0}, + PBEffects::MatBlock => {name: "Mat Block applies this round", default: false}, + PBEffects::Mist => {name: "Mist duration", default: 0}, + PBEffects::QuickGuard => {name: "Quick Guard applies this round", default: false}, + PBEffects::Rainbow => {name: "Rainbow duration", default: 0}, + PBEffects::Reflect => {name: "Reflect duration", default: 0}, + PBEffects::Round => {name: "Round was used this round", default: false}, + PBEffects::Safeguard => {name: "Safeguard duration", default: 0}, + PBEffects::SeaOfFire => {name: "Sea Of Fire duration", default: 0}, + PBEffects::Spikes => {name: "Spikes layers (0-3)", default: 0, max: 3}, + PBEffects::StealthRock => {name: "Stealth Rock exists", default: false}, + PBEffects::StickyWeb => {name: "Sticky Web exists", default: false}, + PBEffects::Swamp => {name: "Swamp duration", default: 0}, + PBEffects::Tailwind => {name: "Tailwind duration", default: 0}, + PBEffects::ToxicSpikes => {name: "Toxic Spikes layers (0-2)", default: 0, max: 2}, + PBEffects::WideGuard => {name: "Wide Guard applies this round", default: false} + } + + FIELD_EFFECTS = { + PBEffects::AmuletCoin => {name: "Amulet Coin doubling prize money", default: false}, + PBEffects::FairyLock => {name: "Fairy Lock trapping duration", default: 0}, + PBEffects::FusionBolt => {name: "Fusion Bolt was used", default: false}, + PBEffects::FusionFlare => {name: "Fusion Flare was used", default: false}, + PBEffects::Gravity => {name: "Gravity duration", default: 0}, + PBEffects::HappyHour => {name: "Happy Hour doubling prize money", default: false}, + PBEffects::IonDeluge => {name: "Ion Deluge making moves Electric", default: false}, + PBEffects::MagicRoom => {name: "Magic Room duration", default: 0}, + PBEffects::MudSportField => {name: "Mud Sport duration (Gen 6+)", default: 0}, + PBEffects::PayDay => {name: "Pay Day additional prize money", default: 0, max: Settings::MAX_MONEY}, + PBEffects::TrickRoom => {name: "Trick Room duration", default: 0}, + PBEffects::WaterSportField => {name: "Water Sport duration (Gen 6+)", default: 0}, + PBEffects::WonderRoom => {name: "Wonder Room duration", default: 0} + } +end + +#=============================================================================== +# Screen for listing the above battle variables for modifying. +#=============================================================================== +class SpriteWindow_DebugBattleFieldEffects < Window_DrawableCommand + BASE_TEXT_COLOR = Color.new(96, 96, 96) + RED_TEXT_COLOR = Color.new(168, 48, 56) + GREEN_TEXT_COLOR = Color.new(0, 144, 0) + TEXT_SHADOW_COLOR = Color.new(208, 208, 200) + + def initialize(viewport, battle, variables, variables_data) + @battle = battle + @variables = variables + @variables_data = variables_data + super(0, 0, Graphics.width, Graphics.height, viewport) + end + + def itemCount + return @variables_data.length + end + + def shadowtext(x, y, w, h, t, align = 0, colors = 0) + width = self.contents.text_size(t).width + if align == 1 # Right aligned + x += w - width + elsif align == 2 # Centre aligned + x += (w - width) / 2 + end + base_color = BASE_TEXT_COLOR + case colors + when 1 then base_color = RED_TEXT_COLOR + when 2 then base_color = GREEN_TEXT_COLOR + end + pbDrawShadowText(self.contents, x, y, [width, w].max, h, t, base_color, TEXT_SHADOW_COLOR) + end + + def drawItem(index, _count, rect) + pbSetNarrowFont(self.contents) + variable_data = @variables_data[@variables_data.keys[index]] + variable = @variables[@variables_data.keys[index]] + # Variables which aren't their default value are colored differently + default = variable_data[:default] + default = -1 if default == -2 + different = (variable || default) != default + color = (different) ? 2 : 0 + # Draw cursor + rect = drawCursor(index, rect) + # Get value's text to draw + variable_text = variable.to_s + if variable_data[:default] == -1 # Battler + if variable >= 0 + battler_name = @battle.battlers[variable].name + battler_name = "-" if nil_or_empty?(battler_name) + variable_text = sprintf("[%d] %s", variable, battler_name) + else + variable_text = _INTL("[None]") + end + elsif variable_data[:default] == nil # Move, item + variable_text = _INTL("[None]") if !variable + end + # Draw text + total_width = rect.width + name_width = total_width * 80 / 100 + value_width = total_width * 20 / 100 + self.shadowtext(rect.x, rect.y, name_width, rect.height, variable_data[:name], 0, color) + self.shadowtext(rect.x + name_width, rect.y, value_width, rect.height, variable_text, 1, color) + end +end + +#=============================================================================== +# +#=============================================================================== +class Battle::DebugSetEffects + def initialize(battle, mode, side = 0) + @battle = battle + @mode = mode + @side = side + case @mode + when :field + @variables_data = Battle::DebugVariables::FIELD_EFFECTS + @variables = @battle.field.effects + when :side + @variables_data = Battle::DebugVariables::SIDE_EFFECTS + @variables = @battle.sides[@side].effects + when :battler + @variables_data = Battle::DebugVariables::BATTLER_EFFECTS + @variables = @battle.battlers[@side].effects + end + @viewport = Viewport.new(0, 0, Graphics.width, Graphics.height) + @viewport.z = 99999 + @window = SpriteWindow_DebugBattleFieldEffects.new(@viewport, @battle, @variables, @variables_data) + @window.active = true + end + + def dispose + @window.dispose + @viewport.dispose + end + + def choose_number(default, min, max) + params = ChooseNumberParams.new + params.setRange(min, max) + params.setDefaultValue(default) + params.setNegativesAllowed(true) if min < 0 + return pbMessageChooseNumber(_INTL("Set value ({1}-{2}).", min, max), params) + end + + def choose_battler(default) + commands = [_INTL("[None]")] + cmds = [-1] + cmd = 0 + @battle.battlers.each_with_index do |battler, i| + next if battler.nil? # Position doesn't exist + name = battler.pbThis + name = "-" if battler.fainted? || nil_or_empty?(name) + commands.push(sprintf("[%d] %s", i, name)) + cmds.push(i) + cmd = cmds.length - 1 if default == i + end + cmd = pbMessage("\\ts[]" + _INTL("Choose a battler/position."), commands, -1, nil, cmd) + return (cmd >= 0) ? cmds[cmd] : default + end + + def update_input_for_boolean(effect, variable_data) + if Input.trigger?(Input::USE) + pbPlayDecisionSE + @variables[effect] = !@variables[effect] + return true + elsif Input.trigger?(Input::ACTION) && @variables[effect] + pbPlayDecisionSE + @variables[effect] = false + return true + elsif Input.repeat?(Input::LEFT) && @variables[effect] + pbPlayCursorSE + @variables[effect] = false + return true + elsif Input.repeat?(Input::RIGHT) && !@variables[effect] + pbPlayCursorSE + @variables[effect] = true + return true + end + return false + end + + def update_input_for_integer(effect, default, variable_data) + true_default = (default == -2) ? -1 : default + min = variable_data[:min] || true_default + max = variable_data[:max] || 99 + if Input.trigger?(Input::USE) + pbPlayDecisionSE + new_value = choose_number(@variables[effect], min, max) + if new_value != @variables[effect] + @variables[effect] = new_value + return true + end + elsif Input.trigger?(Input::ACTION) && @variables[effect] != true_default + pbPlayDecisionSE + @variables[effect] = true_default + return true + elsif Input.repeat?(Input::LEFT) && @variables[effect] > min + pbPlayCursorSE + @variables[effect] -= 1 + return true + elsif Input.repeat?(Input::RIGHT) && @variables[effect] < max + pbPlayCursorSE + @variables[effect] += 1 + return true + end + return false + end + + def update_input_for_battler_index(effect, variable_data) + if Input.trigger?(Input::USE) + pbPlayDecisionSE + new_value = choose_battler(@variables[effect]) + if new_value != @variables[effect] + @variables[effect] = new_value + return true + end + elsif Input.trigger?(Input::ACTION) && @variables[effect] != -1 + pbPlayDecisionSE + @variables[effect] = -1 + return true + elsif Input.repeat?(Input::LEFT) + if @variables[effect] > -1 + pbPlayCursorSE + loop do + @variables[effect] -= 1 + break if @variables[effect] == -1 || @battle.battlers[@variables[effect]] + end + return true + end + elsif Input.repeat?(Input::RIGHT) + if @variables[effect] < @battle.battlers.length - 1 + pbPlayCursorSE + loop do + @variables[effect] += 1 + break if @battle.battlers[@variables[effect]] + end + return true + end + end + return false + end + + def update_input_for_move(effect, variable_data) + if Input.trigger?(Input::USE) + pbPlayDecisionSE + new_value = pbChooseMoveList(@variables[effect]) + if new_value && new_value != @variables[effect] + @variables[effect] = new_value + return true + end + elsif Input.trigger?(Input::ACTION) && @variables[effect] + pbPlayDecisionSE + @variables[effect] = nil + return true + end + return false + end + + def update_input_for_item(effect, variable_data) + if Input.trigger?(Input::USE) + pbPlayDecisionSE + new_value = pbChooseItemList(@variables[effect]) + if new_value && new_value != @variables[effect] + @variables[effect] = new_value + return true + end + elsif Input.trigger?(Input::ACTION) && @variables[effect] + pbPlayDecisionSE + @variables[effect] = nil + return true + end + return false + end + + def update + loop do + Graphics.update + Input.update + @window.update + if Input.trigger?(Input::BACK) + pbPlayCancelSE + break + end + index = @window.index + effect = @variables_data.keys[index] + variable_data = @variables_data[effect] + if variable_data[:default] == false + @window.refresh if update_input_for_boolean(effect, variable_data) + elsif [0, 1, -2].include?(variable_data[:default]) + @window.refresh if update_input_for_integer(effect, variable_data[:default], variable_data) + elsif variable_data[:default] == -1 + @window.refresh if update_input_for_battler_index(effect, variable_data) + elsif variable_data[:default] == nil + case variable_data[:type] + when :move + @window.refresh if update_input_for_move(effect, variable_data) + when :item + @window.refresh if update_input_for_item(effect, variable_data) + else + raise "Unknown kind of variable!" + end + else + raise "Unknown kind of variable!" + end + end + end +end