From 7c48148d35de7bb23caf477013e5a89cbf2f600a Mon Sep 17 00:00:00 2001 From: Alexander Pahn Date: Wed, 24 Nov 2021 20:24:13 +0100 Subject: [PATCH] Implemented Battle Debug Menu (#130) * initial commit to setup game.ini * renamed title of masters Game.ini * reenabled gitignore for Game.ini * Between Commit * Finished Battle Debug Menu * Clean up for pull request * Manual fixes * Fixed oversight where numerical min/max wasm't considered --- Data/Scripts.rxdata | Bin 576 -> 565 bytes .../003_Battle/010_Battle_Phase_Command.rb | 3 +- .../003_Debug menus/001_Debug_Menus.rb | 80 +++ .../006_Debug_BattleCommands.rb | 539 ++++++++++++++ .../007_Debug_BattleConstants.rb | 156 ++++ .../008_Debug_BattleExtraCode.rb | 673 ++++++++++++++++++ 6 files changed, 1449 insertions(+), 2 deletions(-) create mode 100644 Data/Scripts/020_Debug/003_Debug menus/006_Debug_BattleCommands.rb create mode 100644 Data/Scripts/020_Debug/003_Debug menus/007_Debug_BattleConstants.rb create mode 100644 Data/Scripts/020_Debug/003_Debug menus/008_Debug_BattleExtraCode.rb diff --git a/Data/Scripts.rxdata b/Data/Scripts.rxdata index cecb93c53643f6cdff1b3bea4db40a56e5d81781..c5d671ed09fa9775534ea937754e38fd3cc5e0f6 100644 GIT binary patch delta 27 icmX@WvXw=Mg(I3Rnj@3N;{GOPB~IVO%)E_~%1i)cIR`iZ delta 39 ucmdnWa)3pWg(I3Rnj@3N;{GOPPbE&@#LPT4D>l~<&y7OLOe}2H3?Tr>L "main", + "name" => _INTL("Battler Options"), + "description" => _INTL("Change things about a battler."), + "always_show" => true +}) + +#=============================================================================== +# Field Options +#=============================================================================== +BattleDebugMenuCommands.register("battlefield", { + "parent" => "main", + "name" => _INTL("Field Options"), + "description" => _INTL("Options that affect the whole battle field."), + "always_show" => true +}) + +BattleDebugMenuCommands.register("weather", { + "parent" => "battlefield", + "name" => _INTL("Weather"), + "description" => _INTL("Set weather and duration."), + "always_show" => true +}) + +BattleDebugMenuCommands.register("setweather", { + "parent" => "weather", + "name" => _INTL("Set Weather"), + "description" => _INTL("Will start a weather indefinitely. Make it run out by setting a duration."), + "always_show" => true +}) + +GameData::BattleWeather.each { |weather| + inGameName = weather.name + BattleDebugMenuCommands.register(_INTL("weather{1}",weather.name), + { + "parent" => "setweather", + "name" => _INTL("{1}",weather.name), + "description" => _INTL("Set weather to {1}.", inGameName), + "always_show" => true, + "effect" => proc { |battle, sprites| + if weather.id == :None + battle.field.weather = :None + battle.field.weatherDuration = 0 + pbMessage("Weather removed.") + next + end + + visibleSprites = pbFadeOutAndHide(sprites) + battle.pbStartWeather(nil, weather.id) + pbFadeInAndShow(sprites,visibleSprites) + } + }) +} + +BattleDebugMenuCommands.register("setweatherduration", { + "parent" => "weather", + "name" => _INTL("Set Duration"), + "description" => _INTL("Set the duration of weather."), + "always_show" => true, + "effect" => proc { |battle| + weatherduration = battle.field.weatherDuration + battle.field.weatherDuration = getNumericValue("Set weather duration. -1 makes it so that it never run out.", weatherduration,-1,99) + } +}) + +BattleDebugMenuCommands.register("terrain", + { + "parent" => "battlefield", + "name" => _INTL("Terrain"), + "description" => _INTL("Set terrain and duration."), + "always_show" => true, + }) + +BattleDebugMenuCommands.register("setterrain", + { + "parent" => "terrain", + "name" => _INTL("Set Terrain"), + "description" => _INTL("Will start a terrain indefinitely. Make it run out by setting a duration."), + "always_show" => true, + }) + +GameData::BattleTerrain.each { |terrain| + inGameName = terrain.name + if terrain.id != :None + inGameName = _INTL("{1} Terrain",terrain.name) + end + BattleDebugMenuCommands.register(_INTL("terrain{1}",terrain.name), + { + "parent" => "setterrain", + "name" => _INTL("{1}",terrain.name), + "description" => _INTL("Set terrain to {1}.", inGameName), + "always_show" => true, + "effect" => proc { |battle, sprites| + if terrain.id == :None + battle.field.terrain = :None + battle.field.terrainDuration = 0 + next + end + visibleSprites = pbFadeOutAndHide(sprites) + battle.pbStartTerrain(nil, terrain.id, false) + pbFadeInAndShow(sprites,visibleSprites) + } + }) +} + +BattleDebugMenuCommands.register("setterrainduration", +{ + "parent" => "terrain", + "name" => _INTL("Set Duration"), + "description" => _INTL("Set the duration of the terrain."), + "always_show" => true, + "effect" => proc { |battle| + terrainDuration = battle.field.terrainDuration + battle.field.terrainDuration = getNumericValue("Set duration. -1 makes it so that it never run out.", terrainDuration,-1,99) + } +}) + +BattleDebugMenuCommands.register("setfieldeffect", + { + "parent" => "battlefield", + "name" => _INTL("Set Field Effects"), + "description" => _INTL("Effects that apply to the whole field."), + "always_show" => true, + "effect" => proc { |battle| + viewport = Viewport.new(0, 0, Graphics.width, Graphics.height) + viewport.z = 99999 + sprites = {} + sprites["right_window"] = SpriteWindow_DebugBattleEffects.new(viewport, battle.field.effects, FIELD_EFFECTS) + right_window = sprites["right_window"] + right_window.active = true + loopHandler = DebugBattle_LoopHandler.new(sprites, right_window, battle.field.effects, @battlers) + loopHandler.startLoop + viewport.dispose + } + }) + +BattleDebugMenuCommands.register("playerside", + { + "parent" => "main", + "name" => _INTL("Player Side"), + "description" => _INTL("Effects that apply to the side the player is on."), + "always_show" => true, + "effect" => proc { |battle| + sides = battle.sides + battlers = battle.battlers + setSideEffects(0, sides, battlers) + } + }) + +BattleDebugMenuCommands.register("opposingside", + { + "parent" => "main", + "name" => _INTL("Opposing Side"), + "description" => _INTL("Effects that apply to the opposing side."), + "always_show" => true, + "effect" => proc { |battle| + sides = battle.sides + battlers = battle.battlers + setSideEffects(1, sides, battlers) + } + }) + +BattleDebugMenuCommands.register("battlemeta", + { + "parent" => "main", + "name" => _INTL("Battle Metadata"), + "description" => _INTL("Change things about the battle itself (turn counter, etc.)"), + "always_show" => true, + "effect" => proc { |battle| + viewport = Viewport.new(0, 0, Graphics.width, Graphics.height) + viewport.z = 99999 + sprites = {} + sprites["right_window"] = SpriteWindow_DebugBattleMetaData.new(viewport, battle, BATTLE_METADATA) + right_window = sprites["right_window"] + right_window.active = true + loopHandler = DebugBattleMeta_LoopHandler.new(sprites, right_window, battle, @battlers) + loopHandler.setBattle = battle + loopHandler.startLoop + viewport.dispose + } + }) + +def registerBattlerCommands(battle) + battlers = battle.battlers + battlers.each_with_index{|battler, index| + BattleDebugMenuCommands.register(_INTL("battler{1}",index), { + "parent" => "battlers", + "name" => _INTL("[{1}] {2}", index, battler.name), + "description" => _INTL("Change things about {1}.", battler.name), + "always_show" => true, + }) + BattleDebugMenuCommands.register(_INTL("hpStatus{1}",index), { + "parent" => _INTL("battler{1}",index), + "name" => _INTL("HP & Status"), + "description" => _INTL("Set HP and Status of {1}.", battler.name), + "always_show" => true, + }) + BattleDebugMenuCommands.register(_INTL("setHP{1}",index), { + "parent" => _INTL("hpStatus{1}",index), + "name" => _INTL("Set HP"), + "description" => _INTL("Set HP of {1}.", battler.name), + "always_show" => true, + "effect" => proc { + newHP = getNumericValue("Set HP.",battler.hp,0, battler.totalhp) + battler.hp = newHP + battle.scene.pbRefreshOne(battler.index) + } + }) + BattleDebugMenuCommands.register(_INTL("setTotalHP{1}",index), { + "parent" => _INTL("hpStatus{1}",index), + "name" => _INTL("Set Total HP"), + "description" => _INTL("Set total HP of {1}.", battler.name), + "always_show" => true, + "effect" => proc { + newHP = getNumericValue("Set total HP.",battler.totalhp,1,999) + battler.totalhp = newHP + if battler.hp > battler.totalhp + battler.hp = battler.totalhp + end + battle.scene.pbRefreshOne(battler.index) + } + }) + BattleDebugMenuCommands.register(_INTL("setStatus{1}",index), { + "parent" => _INTL("hpStatus{1}",index), + "name" => _INTL("Set Status"), + "description" => _INTL("Set Status of {1}.", battler.name), + "always_show" => true + }) + + GameData::Status.each{ |status| + BattleDebugMenuCommands.register(_INTL("{1}{2}",status.name,index), { + "parent" => _INTL("setStatus{1}",index), + "name" => _INTL("{1}",status.name), + "description" => _INTL("Set status condition to {1}.", status.name), + "always_show" => true, + "effect" => proc { |battle, sprites| + if status.id == :None + battler.pbCureStatus + pbMessage("Status condition removed.") + next + end + newStatusCount = 0 + if status.id == :SLEEP + newStatusCount = getNumericValue("Set the Pokémon's sleep count.",3,0,99) + end + visibleSprites = pbFadeOutAndHide(sprites) + battler.pbInflictStatus(status.id,newStatusCount) + pbFadeInAndShow(sprites,visibleSprites) + } + }) + if status.id == :POISON + BattleDebugMenuCommands.register(_INTL("Toxic{1}", index), { + "parent" => _INTL("setStatus{1}",index), + "name" => _INTL("Toxic"), + "description" => _INTL("Set status condition to Toxic.", status.name), + "always_show" => true, + "effect" => proc {|battle, sprites| + toxicCount = 1 + toxicCount = getNumericValue("Set the Pokémon's toxic count.",1,1,99) + visibleSprites = pbFadeOutAndHide(sprites) + battler.pbInflictStatus(:POISON,toxicCount) + battler.effects[PBEffects::Toxic] = toxicCount + pbFadeInAndShow(sprites,visibleSprites) + } + }) + end + } + + BattleDebugMenuCommands.register(_INTL("heal{1}",index), { + "parent" => _INTL("hpStatus{1}",index), + "name" => _INTL("Fully Heal"), + "description" => _INTL("Fully heal HP and Status of {1}.", battler.name), + "always_show" => true, + "effect" => proc { |battle, sprites| + visibleSprites = pbFadeOutAndHide(sprites) + battler.pbCureStatus(false) + pokemon = battler.pokemon + pbBattleHPItem(pokemon,battler,battler.totalhp,@scene) + battle.pbDisplay(_INTL("{1} was fully healed!", battler.name)) + pbFadeInAndShow(sprites,visibleSprites) + } + }) + BattleDebugMenuCommands.register(_INTL("level{1}",index), { + "parent" => _INTL("battler{1}",index), + "name" => _INTL("Level"), + "description" => _INTL("Change the level of {1} permanently.", battler.name), + "always_show" => true, + "effect" => proc { |battle, sprites| + visibleSprites = pbFadeOutAndHide(sprites) + newLevel = getNumericValue("Set Level",battler.level,1,Settings::MAXIMUM_LEVEL) + if newLevel == battler.level + pbFadeInAndShow(sprites,visibleSprites) + next + end + battler.pokemon.level = newLevel + battler.pbUpdate(false) + battle.scene.pbRefreshOne(battler.index) + + pkmn = battler.pokemon + party = self.pbParty(battler) + idxParty = party.index(battler.pokemon) + curLevel = battler.level + moveList = pkmn.getMoveList + moveList.each { |m| pbLearnMove(idxParty,m[1]) if m[0]==curLevel } + pbFadeInAndShow(sprites,visibleSprites) + } + }) + BattleDebugMenuCommands.register(_INTL("abillity{1}",index), { + "parent" => _INTL("battler{1}",index), + "name" => _INTL("Abillity"), + "description" => _INTL("Change the abillity of {1} and trigger it.", battler.name), + "always_show" => true, + }) + + BattleDebugMenuCommands.register(_INTL("setAbillity{1}",index), { + "parent" => _INTL("abillity{1}",index), + "name" => _INTL("Set Abillity"), + "description" => _INTL("Set any abillity for {1}.", battler.name), + "always_show" => true, + "effect" => proc { + newAbility = pbChooseAbilityList(battler.ability_id) + battler.ability = newAbility + } + }) + BattleDebugMenuCommands.register(_INTL("triggerAbillity{1}",index), { + "parent" => _INTL("abillity{1}",index), + "name" => _INTL("Trigger Abillity"), + "description" => _INTL("Trigger abillity of {1}, if possible.", battler.name), + "always_show" => true, + "effect" => proc { |battle, sprites| + visibleSprites = pbFadeOutAndHide(sprites) + ability = battler.ability + BattleHandlers.triggerAbilityOnSwitchIn(ability,battler,battle) + BattleHandlers.triggerStatusCureAbility(ability,battler) + BattleHandlers.triggerAbilityOnFlinch(ability,battler,battle) + BattleHandlers.triggerEORHealingAbility(ability,battler,battle) + BattleHandlers.triggerEOREffectAbility(ability,battler,battle) + pbFadeInAndShow(sprites,visibleSprites) + } + }) + + BattleDebugMenuCommands.register(_INTL("moves{1}",index), { + "parent" => _INTL("battler{1}",index), + "name" => _INTL("Moves"), + "description" => _INTL("Set {1}'s moves.", battler.name), + "always_show" => true, + "effect" => proc { + moveIdx = 0 + moveAction = 0 + loop do + moveCommands = generateMoveCommands(battler) + moveIdx = pbChooseList(moveCommands,moveIdx,-1,0) + break if moveIdx < 0 + if moveIdx == 4 + battler.moves.each{ |move| + move.pp = 0 + } + next + end + + if moveIdx == 5 + battler.moves.each{ |move| + move.pp = move.total_pp + } + next + end + + moveAction = pbChooseList(generateMoveActionCommands,moveAction,-1,0) + next if moveAction < 0 + move = battler.moves[moveIdx] + case moveAction + when 0 + newMove = pbChooseMoveList + next if !newMove + battler.moves[moveIdx] = PokeBattle_Move.from_pokemon_move(battle,Pokemon::Move.new(newMove)) + when 1 + newPP = getNumericValue("Set PP",move.pp,0,move.total_pp, false) + move.pp = newPP + when 2 + battler.moves[moveIdx] = nil + battler.moves.compact! + + + end + end + } + }) + BattleDebugMenuCommands.register(_INTL("item{1}",index), { + "parent" => _INTL("battler{1}",index), + "name" => _INTL("Item"), + "description" => _INTL("Modify {1}'s held item", battler.name), + "always_show" => true, + + }) + BattleDebugMenuCommands.register(_INTL("giveItem{1}",index), { + "parent" => _INTL("item{1}",index), + "name" => _INTL("Give Item"), + "description" => _INTL("Set {1}'s held item", battler.name), + "always_show" => true, + "effect" => proc { |battle, sprites| + pbListScreenBlock(_INTL("GIVE ITEM"),ItemLister.new(0)){|button,item| + if button==Input::USE && item + battler.item = item + pbMessage(_INTL("{1} is now holding {2}!",battler.name,GameData::Item.get(item).name)) + end + } + } + }) + BattleDebugMenuCommands.register(_INTL("removeItem{1}",index), { + "parent" => _INTL("item{1}",index), + "name" => _INTL("Remove Item"), + "description" => _INTL("Remove {1}'s held item", battler.name), + "always_show" => true, + "effect" => proc { + oldItem = battler.item + battler.item = 0 + pbMessage(_INTL("{1} was removed!",GameData::Item.get(oldItem).name)) + } + }) + BattleDebugMenuCommands.register(_INTL("type{1}",index), { + "parent" => _INTL("battler{1}",index), + "name" => _INTL("Type"), + "description" => _INTL("Change the typing of {1}.", battler.name), + "always_show" => true, + "effect" => proc { + typeToChange = 0 + loop do + typeCommands = generateTypeCommands(battler) + typeToChange = pbChooseList(typeCommands,typeToChange,-1,0) + break if typeToChange <= 0 + newType = pbChooseTypeList + next if !newType + + case typeToChange + when 1 + battler.type1 = newType + when 2 + battler.type2 = newType + when 3 + battler.effects[PBEffects::Type3] = newType + end + end + } + }) + BattleDebugMenuCommands.register(_INTL("stats{1}",index), { + "parent" => _INTL("battler{1}",index), + "name" => _INTL("Stat Changes"), + "description" => _INTL("Set Stat Changes of {1}.", battler.name), + "always_show" => true, + "effect" => proc { + viewport = Viewport.new(0,0,Graphics.width,Graphics.height) + viewport.z = 99999 + sprites = {} + sprites["right_window"] = SpriteWindow_DebugStatChanges.new(viewport,battler,BATTLE_STATS) + right_window = sprites["right_window"] + right_window.toggleSortMode + right_window.active = true + loopHandler = DebugBattle_LoopHandler.new(sprites,right_window,battler.stages,@battlers,nil,-6,6,false) + loopHandler.startLoop + viewport.dispose + } + }) + BattleDebugMenuCommands.register(_INTL("effects{1}",index), { + "parent" => _INTL("battler{1}",index), + "name" => _INTL("Battler Effects"), + "description" => _INTL("Set effects that apply to {1}.", battler.name), + "always_show" => true, + "effect" => proc { + viewport = Viewport.new(0,0,Graphics.width,Graphics.height) + viewport.z = 99999 + sprites = {} + sprites["right_window"] = SpriteWindow_DebugBattleEffects.new(viewport,battler.effects,BATTLER_EFFECTS,battlers) + right_window = sprites["right_window"] + right_window.active = true + loopHandler = DebugBattle_LoopHandler.new(sprites,right_window,battler.effects,@battlers,battler) + loopHandler.startLoop + viewport.dispose + } + }) + BattleDebugMenuCommands.register(_INTL("summary{1}",index), { + "parent" => _INTL("battler{1}",index), + "name" => _INTL("Summary"), + "description" => _INTL("View summary of {1}.", battler.name), + "always_show" => true, + "effect" => proc { + pokemon = fakePokemonForSummary(battler) + party = self.pbParty(battler) + partyIdx = party.index(battler.pokemon) + party[partyIdx] = pokemon + scene = PokemonSummary_Scene.new + screen = PokemonSummaryScreen.new(scene,true) + screen.pbStartScreen(party,partyIdx) + party[partyIdx] = battler.pokemon + } + }) + } +end diff --git a/Data/Scripts/020_Debug/003_Debug menus/007_Debug_BattleConstants.rb b/Data/Scripts/020_Debug/003_Debug menus/007_Debug_BattleConstants.rb new file mode 100644 index 0000000000..2bdc80a8fa --- /dev/null +++ b/Data/Scripts/020_Debug/003_Debug menus/007_Debug_BattleConstants.rb @@ -0,0 +1,156 @@ + +#=============================================================================== +# # Field Effects +#=============================================================================== + +# These effects apply to a battler +BATTLER_EFFECTS = { + PBEffects::AquaRing => {:name => "Aqua Ring"}, + PBEffects::BanefulBunker => {:name => "Baneful Bunker"}, + PBEffects::BeakBlast => {:name => "Beak Blast"}, + PBEffects::BideDamage => {:name => "Bide Damage"}, + PBEffects::BurnUp => {:name => "Burn Up"}, + PBEffects::Charge => {:name => "Charge"}, + PBEffects::ChoiceBand => {:name => "Choice Band",:type => :MOVEID}, # Move ID (default -1) + PBEffects::Confusion => {:name => "Confusion"}, + PBEffects::Curse => {:name => "Curse"}, + PBEffects::DefenseCurl => {:name => "Defense Curl"}, + PBEffects::Disable => {:name => "Disable"}, + PBEffects::DisableMove => {:name => "Disable Move",:type => :MOVEID}, # Move ID (default -1}) + PBEffects::Electrify => {:name => "Electrify"}, + PBEffects::Embargo => {:name => "Embargo"}, + PBEffects::Encore => {:name => "Encore"}, + PBEffects::EncoreMove => {:name => "Encore Move",:type => :MOVEID}, # Move ID (default -1) + PBEffects::Endure => {:name => "Endure"}, + PBEffects::FlashFire => {:name => "Flash Fire"}, + PBEffects::Flinch => {:name => "Flinch"}, + PBEffects::FocusEnergy => {:name => "Focus Energy"}, # is set to 2 by Essentials when active + PBEffects::FocusPunch => {:name => "Focus Punch"}, + PBEffects::FollowMe => {:name => "Follow Me"}, # ist set to 1 by Essentials when active + PBEffects::Foresight => {:name => "Foresight"}, + PBEffects::FuryCutter => {:name => "Fury Cutter"}, + PBEffects::GastroAcid => {:name => "Gastro Acid"}, + PBEffects::Grudge => {:name => "Grudge"}, + PBEffects::HealBlock => {:name => "Heal Block"}, + PBEffects::HelpingHand => {:name => "Helping Hand"}, + PBEffects::HyperBeam => {:name => "Hyper Beam"}, + PBEffects::Imprison => {:name => "Imprison"}, + PBEffects::Ingrain => {:name => "Ingrain"}, + PBEffects::Instruct => {:name => "Instruct"}, + PBEffects::Instructed => {:name => "Instructed"}, + PBEffects::KingsShield => {:name => "Kings Shield"}, + PBEffects::LaserFocus => {:name => "Laser Focus"}, # is set to 2 by Essentials when active + PBEffects::LeechSeed => {:name => "Leech Seed",:type => :USERINDEX}, # User index (so game knows where to put the HP) + PBEffects::LockOn => {:name => "Lock On"}, # is set to 2 by Essentials when active + PBEffects::LockOnPos => {:name => "Lock On Position"}, # Target Index + PBEffects::MagicBounce => {:name => "Magic Bounce"}, + PBEffects::MagicCoat => {:name => "Magic Coat"}, + PBEffects::MagnetRise => {:name => "Magnet Rise"}, + PBEffects::MeanLook => {:name => "Mean Look",:type => :USERINDEX}, # User Index (so the game knows when the user left the field and target can switch out again) (default -1) + PBEffects::MeFirst => {:name => "Me First"}, + PBEffects::Metronome => {:name => "Metronome (Item)"}, + PBEffects::Minimize => {:name => "Minimize"}, + PBEffects::MiracleEye => {:name => "Miracle Eye"}, + PBEffects::MudSport => {:name => "Mud Sport"}, + PBEffects::Nightmare => {:name => "Nightmare"}, + PBEffects::Outrage => {:name => "Outrage"}, + PBEffects::PerishSong => {:name => "Perish Song"}, + PBEffects::PerishSongUser => {:name => "Perish Song User",:type => :USERINDEX}, # User Index (so the game knows how to judge for win/loss) (default -1) + PBEffects::Powder => {:name => "Powder"}, + PBEffects::PowerTrick => {:name => "Power Trick"}, + PBEffects::Protect => {:name => "Protect"}, + PBEffects::ProtectRate => {:name => "Protect Rate"}, + PBEffects::Pursuit => {:name => "Pursuit"}, + PBEffects::Quash => {:name => "Quash"}, + PBEffects::Rage => {:name => "Rage"}, + PBEffects::RagePowder => {:name => "Rage Powder"}, # Used along with FollowMe + PBEffects::Rollout => {:name => "Rollout"}, + PBEffects::Roost => {:name => "Roost"}, + PBEffects::ShellTrap => {:name => "Shell Trap"}, + PBEffects::SlowStart => {:name => "Slow Start"}, + PBEffects::SmackDown => {:name => "Smack Down"}, + PBEffects::Snatch => {:name => "Snatch"}, # is set to 1 by Essentials when active + PBEffects::SpikyShield => {:name => "Spiky Shield"}, + PBEffects::Spotlight => {:name => "Spotlight"}, + PBEffects::Stockpile => {:name => "Stockpile"}, + PBEffects::StockpileDef => {:name => "Stockpile Def"}, + PBEffects::StockpileSpDef => {:name => "Stockpile Sp. Def"}, + PBEffects::Substitute => {:name => "Substitute"}, # Substitutes HP + PBEffects::Taunt => {:name => "Taunt"}, + PBEffects::Telekinesis => {:name => "Telekinesis"}, + PBEffects::ThroatChop => {:name => "Throat Chop"}, # is set to 3 by Essentials when active + PBEffects::Torment => {:name => "Torment"}, + PBEffects::Toxic => {:name => "Toxic"}, + PBEffects::Truant => {:name => "Truant"}, + PBEffects::Unburden => {:name => "Unburden"}, + PBEffects::Uproar => {:name => "Uproar"}, + PBEffects::WaterSport => {:name => "Water Sport"}, + PBEffects::WeightChange => {:name => "Weight Change"}, + PBEffects::Yawn => {:name => "Yawn"} +} + + +# These effects apply to a side +SIDE_EFFECTS = { + PBEffects::AuroraVeil => {:name => "Aurora Veil"}, + PBEffects::CraftyShield => {:name => "Crafty Shield"}, + PBEffects::EchoedVoiceCounter => {:name => "Echoed Voice Counter"}, + PBEffects::EchoedVoiceUsed => {:name => "Echoed Voice Used"}, + PBEffects::LastRoundFainted => {:name => "Last Round Fainted (Turn Count)"}, # Turn Count + PBEffects::LightScreen => {:name => "Light Screen"}, + PBEffects::LuckyChant => {:name => "Lucky Chant"}, + PBEffects::MatBlock => {:name => "Mat Block"}, + PBEffects::Mist => {:name => "Mist"}, + PBEffects::QuickGuard => {:name => "Quick Guard"}, + PBEffects::Rainbow => {:name => "Rainbow"}, + PBEffects::Reflect => {:name => "Reflect"}, + PBEffects::Round => {:name => "Round"}, + PBEffects::Safeguard => {:name => "Safeguard"}, + PBEffects::SeaOfFire => {:name => "Sea Of Fire"}, + PBEffects::Spikes => {:name => "Spikes"}, + PBEffects::StealthRock => {:name => "Stealth Rock"}, + PBEffects::StickyWeb => {:name => "Sticky Web"}, + PBEffects::Swamp => {:name => "Swamp"}, + PBEffects::Tailwind => {:name => "Tailwind"}, + PBEffects::ToxicSpikes => {:name => "Toxic Spikes"}, + PBEffects::WideGuard => {:name => "Wide Guard"}, +} + +# These effects apply to the battle (i.e. both sides) +FIELD_EFFECTS = { + PBEffects::AmuletCoin => { :name => "Amulet Coin"}, + PBEffects::FairyLock => { :name => "Fairy Lock"}, + PBEffects::FusionBolt => { :name => "Fusion Bolt"}, + PBEffects::FusionFlare => { :name => "Fusion Flare"}, + PBEffects::Gravity => { :name => "Gravity"}, + PBEffects::HappyHour => { :name => "Happy Hour"}, + PBEffects::IonDeluge => { :name => "Ion Deluge"}, + PBEffects::MagicRoom => { :name => "Magic Room"}, + PBEffects::MudSportField => { :name => "Mud Sport Field"}, + PBEffects::PayDay => { :name => "Pay Day"}, + PBEffects::TrickRoom => { :name => "Trick Room"}, + PBEffects::WaterSportField => { :name => "Water Sport Field"}, + PBEffects::WonderRoom => { :name => "Wonder Room"}, +} + + + +BATTLE_METADATA = { + :time => { :name => "Time of day (mechanic only)"}, + :environment => { :name => "Battle surrounding (mechanic only)"}, + :turnCount => { :name => "Turn count"}, + :items => { :name => "Opponent items"}, + :internalBattle => { :name => "Internal Battle"}, + :switchStyle => { :name => "Switch Style"}, + :expGain => { :name => "Allow EXP/EV Gain"}, + } + +BATTLE_STATS = { + :ATTACK => {:name => "Attack"}, + :DEFENSE => {:name => "Defense"}, + :SPECIAL_ATTACK => {:name => "Sp. Attack"}, + :SPECIAL_DEFENSE => {:name => "Sp. Defense"}, + :SPEED => {:name => "Speed"}, + :ACCURACY => {:name => "Accuracy"}, + :EVASION => {:name => "Evasion"}, +} diff --git a/Data/Scripts/020_Debug/003_Debug menus/008_Debug_BattleExtraCode.rb b/Data/Scripts/020_Debug/003_Debug menus/008_Debug_BattleExtraCode.rb new file mode 100644 index 0000000000..9dd66a7336 --- /dev/null +++ b/Data/Scripts/020_Debug/003_Debug menus/008_Debug_BattleExtraCode.rb @@ -0,0 +1,673 @@ +#=============================================================================== +# Additonal Methods for the Battle Debug Menu +#=============================================================================== +def setSideEffects(sideIdx, sides, battlers) + viewport = Viewport.new(0, 0, Graphics.width, Graphics.height) + viewport.z = 99_999 + sprites = {} + sprites['right_window'] = + SpriteWindow_DebugBattleEffects.new(viewport, sides[sideIdx].effects, SIDE_EFFECTS, battlers) + right_window = sprites['right_window'] + right_window.active = true + loopHandler = DebugBattle_LoopHandler.new(sprites, right_window, sides[sideIdx].effects, battlers) + loopHandler.startLoop + viewport.dispose +end + +#=============================================================================== +# +#=============================================================================== +class SpriteWindow_DebugBattleEffects < Window_DrawableCommand + include BattleDebugMixin + + def initialize(viewport, dataSource, mapSource, battlers = nil) + @dataSource = dataSource + @mapSource = mapSource + @keyIndexArray = [] + @sortByKey = false + @battlers = battlers + super(0, 0, Graphics.width, Graphics.height, viewport) + end + + def refresh + @item_max = itemCount + dwidth = width - borderX + dheight = height - borderY + self.contents = pbDoEnsureBitmap(contents, dwidth, dheight) + contents.clear + @keyIndexArray = [] + sortedEffects = @mapSource.sort_by { |key, value| @sortByKey ? key : value[:name] } + + sortedEffects.each_with_index do |dataArray, i| + next if i < top_item || i > top_item + page_item_max + + drawItem(dataArray, @item_max, itemRect(i), i) + @keyIndexArray[i] = dataArray[0] + end + end + + def drawItem(dataArray, _count, rect, idx) + pbSetNarrowFont(contents) + key = dataArray[0] + name = dataArray[1][:name] + colors = 0 + value = getValue(key) + if isSwitch(key) || isToBeFormatted(key) + statusColor = getFormattedStatusColor(value, key) + status = _INTL('{1}', statusColor[0]) + colors = statusColor[1] + else + status = _INTL('{1}', value) + status = '"__"' if !status || status == '' + end + name = '' if name.nil? + id_text = format('%04d:', key) + rect = drawCursor(idx, rect) + totalWidth = rect.width + idWidth = totalWidth * 15 / 100 + nameWidth = totalWidth * 65 / 100 + statusWidth = totalWidth * 20 / 100 + shadowtext(rect.x, rect.y, idWidth, rect.height, id_text) + shadowtext(rect.x + idWidth, rect.y, nameWidth, rect.height, name, 0, 0) + shadowtext(rect.x + idWidth + nameWidth, rect.y, statusWidth, rect.height, status, 1, colors) + end + + def getValue(key) + @dataSource[key] + end + + def isSwitch(key) + return false unless defined?(@dataSource) + + !!@dataSource[key] == @dataSource[key] + end + + def isMoveIDEffect?(key) + value = @mapSource[key] + defined?(value[:type]) && value[:type] == :MOVEID + end + + def isUserIndexEffect?(key) + value = @mapSource[key] + defined?(value[:type]) && value[:type] == :USERINDEX + end + + def isToBeFormatted(key) + isUserIndexEffect?(key) || isMoveIDEffect?(key) + end + + def getFormattedStatusColor(value, key) + status = value + value = 0 if value.nil? + color = 0 + isMoveIDEffect = isMoveIDEffect?(key) + if isMoveIDEffect + status = value > 0 ? GameData::Move.get(value).name : 'None' + color = 3 + return [status, color] + end + + isUserIdxEffect = isUserIndexEffect?(key) + if isUserIdxEffect + status = value >= 0 ? @battlers[value].name : 'None' + color = 3 + return [status, color] + end + + status = 'Disabled' + color = 1 + if value.nil? + status = '-' + color = 0 + elsif value + status = 'Enabled' + color = 2 + end + [status, color] + end + + def toggleSortMode + @sortByKey = !@sortByKey + refresh + end + + def getByIndex(index) + @keyIndexArray[index] + end + + def itemCount + @mapSource.size || 0 + end + + def shadowtext(x, y, w, h, t, align = 0, colors = 0) + width = contents.text_size(t).width + if align == 1 # Right aligned + x += (w - width) + elsif align == 2 # Centre aligned + x += (w / 2) - (width / 2) + end + base = Color.new(12 * 8, 12 * 8, 12 * 8) + if colors == 1 # Red + base = Color.new(168, 48, 56) + elsif colors == 2 # Green + base = Color.new(0, 144, 0) + elsif colors == 3 # Blue + base = Color.new(22, 111, 210) + end + pbDrawShadowText(contents, x, y, [width, w].max, h, t, base, Color.new(26 * 8, 26 * 8, 25 * 8)) + end +end + +#=============================================================================== +# +#=============================================================================== +class SpriteWindow_DebugBattleMetaData < SpriteWindow_DebugBattleEffects + include BattleDebugMixin + + def drawItem(dataArray, _count, rect, idx) + pbSetNarrowFont(contents) + key = dataArray[0] + name = dataArray[1][:name] + colors = 0 + value = getValue(key) + + if isSwitch(key) || isToBeFormatted(key) + statusColor = getFormattedStatusColor(value, key) + status = _INTL('{1}', statusColor[0]) + colors = statusColor[1] + else + status = _INTL('{1}', value) + status = '"__"' if !status || status == '' + end + name = '' if name.nil? + rect = drawCursor(idx, rect) + totalWidth = rect.width + idWidth = totalWidth * 15 / 100 + nameWidth = totalWidth * 65 / 100 + statusWidth = totalWidth * 20 / 100 + shadowtext(rect.x, rect.y, idWidth, rect.height, name) + shadowtext(rect.x + idWidth + nameWidth, rect.y, statusWidth, rect.height, status, 1, colors) + end + + def getItemNames(trainerIdx) + items = @dataSource.items[trainerIdx] + return 'None' if !items || items.length <= 0 + + itemString = '' + itemArray.each_with_index do |_itemID, idx| + itemString += _INTL('{1}', GameData::Item.get(item).name) + itemString += ',' if idx + 1 < itemArray.length + end + itemString + end + + def getTrainersWithItems + opponents = @dataSource.opponent + return 'None' unless opponents + + items = @dataSource.items + trainerNames = '' + opponents.each_with_index do |opponent, idx| + hasItems = items[idx].length > 0 + next unless hasItems + + trainerNames += opponent.name + trainerNames += ',' if idx + 1 < opponents.length + end + return 'None' if trainerNames.length == 0 + + trainerNames + end + + def getValue(key) + @dataSource.send(key) + end + + def isSwitch(key) + !!@dataSource.send(key) == @dataSource.send(key) + end + + def isToBeFormatted(_key) + true + end + + def getFormattedStatusColor(value, key) + status = value + color = 0 + case key + when :expGain + status = value ? 'Enabled' : 'Disabled' + color = value ? 2 : 1 + when :items + status = ' ' + when :switchStyle + status = value ? 'Switch' : 'Set' + color = value ? 2 : 1 + when :internalBattle + status = value ? '[ON]' : '[OFF]' + color = value ? 2 : 1 + when :time + status = if value == 0 + 'Day' + else + value == 1 ? 'Evening' : 'Night' + end + end + [status, color] + end +end + +#=============================================================================== +# +#=============================================================================== +class SpriteWindow_DebugStatChanges < SpriteWindow_DebugBattleEffects + include BattleDebugMixin + + # override method to prevent bug where some stats are treated as switches + def isSwitch(_key) + false + end + + def getValue(key) + @dataSource.stages[key] + end + + def drawItem(dataArray, _count, rect, idx) + pbSetNarrowFont(contents) + + stat = dataArray[0] + name = dataArray[1][:name] + value = getValue(stat) + status = _INTL('{1}', value) + colors = if value > 0 + 2 + else + value < 0 ? 1 : 0 + end + + rect = drawCursor(idx, rect) + totalWidth = rect.width + nameWidth = totalWidth * 65 / 100 + statusWidth = totalWidth * 20 / 100 + shadowtext(rect.x, rect.y, nameWidth, rect.height, name, 0, 0) + shadowtext(rect.x + nameWidth, rect.y, statusWidth, rect.height, status, 1, colors) + end +end + +class DebugBattle_LoopHandler + include BattleDebugMixin + + def initialize(sprites, window, dataSource, battlers, battler = nil, minNumeric = -1, maxNumeric = 99, allowSorting = true) + @sprites = sprites + @window = window + @dataSource = dataSource + @allowSorting = allowSorting + @battlers = battlers + @battler = battler + @battle = nil + setMinMaxValues(minNumeric, maxNumeric) + end + + def setMinMaxValues(minNumeric, maxNumeric) + @minNumeric = minNumeric + @maxNumeric = maxNumeric + end + + attr_writer :allowSorting + + def setBattle=(battle) + @battle = battle + end + + def startLoop + loop do + Graphics.update + Input.update + pbUpdateSpriteHash(@sprites) + if Input.trigger?(Input::BACK) + pbPlayCancelSE + break + end + index = @window.index + key = @window.getByIndex(index) + @window.toggleSortMode if Input.trigger?(Input::SPECIAL) && @allowSorting + if @window.isSwitch(key) # Switches + if Input.trigger?(Input::USE) + toggleSwitch(key) + @window.refresh + end + elsif isNumeric(key) && !Input.trigger?(Input::USE) # Numerics + if Input.repeat?(Input::LEFT) && leftInputConditions(key) + decreaseNumeric(key) + @window.refresh + elsif Input.repeat?(Input::RIGHT) && rightInputConditions(key) + increaseNumeric(key) + @window.refresh + end + elsif Input.trigger?(Input::USE) + pbPlayDecisionSE + handleCInput(key) + @window.refresh + end + end + pbDisposeSpriteHash(@sprites) + end + + def isNumeric(key) + @window.getValue(key).is_a?(Numeric) + end + + def leftInputConditions(key) + value = @window.getValue(key) + return false if @window.isMoveIDEffect?(key) + return value > -1 if @window.isUserIndexEffect?(key) + + value > @minNumeric + end + + def rightInputConditions(key) + value = @window.getValue(key) + return false if @window.isMoveIDEffect?(key) + return value < @battlers.length - 1 if @window.isUserIndexEffect?(key) + + value < @maxNumeric + end + + def toggleSwitch(key) + @dataSource[key] = !@dataSource[key] + end + + def increaseNumeric(key) + @dataSource[key] += 1 + end + + def decreaseNumeric(key) + @dataSource[key] -= 1 + end + + def handleCInput(key) + currentValue = @dataSource[key] + if @window.isMoveIDEffect?(key) && @battler + moveId = selectMoveForID(@battler, currentValue) + @dataSource[key] = moveId + return + end + + if @window.isUserIndexEffect?(key) + userIndex = selectUserForIndex(currentValue) + @dataSource[key] = userIndex + return + end + + return unless isNumeric(key) + + setNumeric(key) + end + + def setNumeric(key) + currentValue = @dataSource[key] + @dataSource[key] = getNumericValue('Enter value.', currentValue, @minNumeric, @maxNumeric) + end +end + +class DebugBattleMeta_LoopHandler < DebugBattle_LoopHandler + def isNumeric(key) + @dataSource.send(key).is_a?(Numeric) + end + + def leftInputConditions(key) + @dataSource.send(key) > @minNumeric && key != :time + end + + def rightInputConditions(key) + key != :time + end + + def toggleSwitch(key) + @dataSource.instance_variable_set("@#{key}", !@dataSource.send(key)) + end + + def increaseNumeric(key) + @dataSource.instance_variable_set("@#{key}", @dataSource.send(key) + 1) + end + + def decreaseNumeric(key) + @dataSource.instance_variable_set("@#{key}", @dataSource.send(key) - 1) + end + + def handleCInput(key) + currentValue = @dataSource.send(key) + if key == :time + newValue = setTime(currentValue) + @dataSource.instance_variable_set("@#{key}", newValue) + elsif key == :items + @dataSource.setTrainerItems + elsif key == :environment + setEnvironment(@battle) + elsif isNumeric(key) + setNumeric(key, currentValue) + end + end + + def setNumeric(key, currentValue) + newValue = getNumericValue('Enter value.', currentValue, @minNumeric, @maxNumeric) + @dataSource.instance_variable_set("@#{key}", newValue) + end +end + +class PokeBattle_FakePokemon < Pokemon + def initialize(originalPokemon, battler) + species = originalPokemon.species + level = battler.level + owner = originalPokemon.owner + super(species, level, owner, false) + @personalID = originalPokemon.personalID + @hp = originalPokemon.hp + @totalhp = originalPokemon.totalhp + @iv = originalPokemon.iv + @ivMaxed = originalPokemon.ivMaxed + @ev = originalPokemon.ev + @trainerID = originalPokemon.owner.id + @ot = originalPokemon.owner.name + @otgender = originalPokemon.owner.gender + @obtain_method = originalPokemon.obtain_method + @obtain_map = originalPokemon.obtain_map + @obtain_text = originalPokemon.obtain_text + @obtain_level = originalPokemon.obtain_level + @hatched_map = originalPokemon.hatched_map + @timeReceived = originalPokemon.timeReceived + @timeEggHatched = originalPokemon.timeEggHatched + @nature = originalPokemon.nature + @nature_for_stats = originalPokemon.nature_for_stats + @timeReceived = originalPokemon.timeReceived + @moves = battler.moves + @status = battler.status + @item = battler.item + @type1 = battler.type1 + @type2 = battler.type2 + @ability = battler.ability + calc_stats + end +end + +# Meta Data related methods +def pbGenerateTimeCommands(time) + timeCommands = [] + currentTime = time + times = %w[Day Evening Night] + (0..2).each do |i| + activeString = currentTime == i ? 'x' : ' ' + timeCommands.push([i, _INTL('[{1}] {2}', activeString, times[i])]) + end + timeCommands +end + +def setTime(currentTime) + timeCommands = pbGenerateTimeCommands(currentTime) + pbChooseList(timeCommands, currentTime, currentTime, 0) +end + +def pbGenerateItemCommands(items) + itemsCommands = [] + + items.each_with_index do |item, idx| + itemName = GameData::Item.get(item).name + itemsCommands.push([idx, _INTL('{1}', itemName)]) + end + itemsCommands.push([items.length + 1, _INTL('[Add item]')]) + itemsCommands.push([-1, _INTL('[Return]')]) + itemsCommands +end + +def generateOpponentCommands + opponentCommands = [] + opponent.each_with_index do |opponent, idx| + items = self.items[idx] + itemLength = items.length + opponentCommands.push([idx, _INTL('{1}: {2} items', opponent.name, itemLength)]) + end + opponentCommands +end + +def setTrainerItems + return pbMessage('No other trainers found!') unless opponent + + opponentCommands = generateOpponentCommands + opponentIdx = pbChooseList(opponentCommands, 0, -1, 0) + return if opponentIdx < 0 + + itemCmd = 0 + loop do + currentItems = items[opponentIdx] + itemCommands = pbGenerateItemCommands(currentItems) + itemCmd = pbChooseList(itemCommands, -1) + break if itemCmd < 0 + + if itemCmd == itemCommands.length - 1 # Add item + pbListScreenBlock(_INTL('ADD ITEMS'), ItemLister.new(0)) do |button, item| + if button == Input::USE && item + items[opponentIdx].push(item) + pbMessage(_INTL('Gave {1} to {2}.', GameData::Item.get(item).name, opponent[opponentIdx].name)) + end + end + elsif pbConfirmMessage(_INTL('Change this item?')) + item = pbListScreen(_INTL('CHOOSE AN ITEM'), + ItemLister.new(0)) + setTrainerItem(opponentIdx, itemCmd, item) if item + + elsif pbConfirmMessage(_INTL('Delete this item?')) + setTrainerItem(opponentIdx, itemCmd, nil) + end + end +end + +def setTrainerItem(opponentIdx, itemIdx, newItem) + items[opponentIdx][itemIdx] = newItem + items[opponentIdx].compact! +end + +def setEnvironment(battle) + environmentCommands = [] + currentEnvironment = battle.environment + environmentIdxMap = [] + counter = 0 + GameData::Environment.each do |environment| + environmentCommands.push([counter, _INTL('{1}', environment.name)]) + environmentIdxMap[counter] = environment.id + counter += 1 + end + currentEnvironment = battle.environment + currentEnvironmentIdx = environmentIdxMap.index(currentEnvironment) + + newEnvironmentIdx = pbChooseList(environmentCommands, currentEnvironmentIdx, currentEnvironmentIdx, 1) + newEnvironment = environmentIdxMap[newEnvironmentIdx] + environmentChanged = newEnvironment != currentEnvironment + + return unless environmentChanged + + battle.environment = newEnvironment +end + +# Creates a duplicate of the Pokemon object, to reflect changes from the debug menu +def fakePokemonForSummary(battler) + originalPokemon = battler.pokemon + PokeBattle_FakePokemon.new(originalPokemon, battler) +end + +def generateMoveCommands(battler, isSelectionOnly = false) + moves = [] + battler.moves.each_with_index do |move, idx| + moves.push([idx, _INTL('{1} {2}/{3}', move.name, move.pp, move.total_pp)]) + end + emptySlots = 4 - battler.moves.length + + emptySlots.times do |idx| + moves.push([idx + battler.moves.length, _INTL('-')]) unless isSelectionOnly + end + + unless isSelectionOnly + moves.push([4, _INTL('Deplete all PP')]) + moves.push([5, _INTL('Refill all PP')]) + end + moves +end + +def generateMoveActionCommands + [ + [0, _INTL('Change Move')], + [1, _INTL('Set PP')], + [2, _INTL('Delete Move')] + ] +end + +def generateTypeCommands(battler) + type1 = GameData::Type.get(battler.type1).name + type2 = !battler.type2 ? 'None' : GameData::Type.get(battler.type2).name + type3 = !battler.effects[PBEffects::Type3] ? 'None' : GameData::Type.get(battler.effects[PBEffects::Type3]).name + [ + [1, _INTL('{1}', type1)], + [2, _INTL('{1}', type2)], + [3, _INTL('{1} (Type effect)', type3)] + ] +end + +def selectMoveForID(battler, currentValue) + currentMove = battler.moves.detect { |move| move.id == currentValue } + currentIndex = currentMove ? battler.moves.index(currentMove) : -1 + moveCommands = generateMoveCommands(battler, true) + moveIdx = pbChooseList(moveCommands, currentIndex, -1, 0) + return -1 if moveIdx < 0 + + battler.moves[moveIdx].id +end + +def selectMoveForFunctionCode(battler, currentValue) + currentFunction = battler.moves.detect { |move| move.function == currentValue } + currentIndex = currentFunction ? battler.moves.index(currentFunction) : -1 + moveCommands = generateMoveCommands(battler, true) + moveIdx = pbChooseList(moveCommands, currentIndex, -1, 0) + return -1 if moveIdx < 0 + + battler.moves[moveIdx].function +end + +def selectUserForIndex(currentIndex) + battlerCommands = [] + (0...@battlers.length).each do |i| + battlerCommands.push([i, _INTL('[{1}] {2}', i, @battlers[i].name)]) + end + pbChooseList(battlerCommands, currentIndex, -1, 0) +end + +def getNumericValue(msg, currentValue, min = -1, max = 99, allowNegative = true) + params = ChooseNumberParams.new + params.setRange(min, max) + params.setNegativesAllowed(allowNegative) + params.setInitialValue(currentValue) + params.setCancelValue(currentValue) + pbMessageChooseNumber(_INTL('{1}', msg), params) +end + +class PokeBattle_Battler + attr_writer :totalhp +end